Handling POST Requests
Parse JSON-RPC requests from HTTP POST bodies.
The MCP POST Endpoint
All MCP requests arrive as HTTP POST requests to a single endpoint
(typically /mcp). The request body contains a JSON-RPC
message that we need to parse and route.
Basic POST Handler
Let's build a POST handler using Python's built-in http.server:
"""
mcp_http_handler.py - HTTP handler for MCP requests.
"""
import json
from http.server import BaseHTTPRequestHandler
class MCPHTTPHandler(BaseHTTPRequestHandler):
"""Handle HTTP requests for MCP protocol."""
def log_message(self, format, *args):
"""Custom logging - suppress default HTTP logs."""
pass
def read_request_body(self):
"""Read and decode the request body."""
content_length = int(self.headers.get("Content-Length", 0))
if content_length == 0:
return None
body = self.rfile.read(content_length)
return body.decode("utf-8")
def parse_json_rpc(self, body):
"""Parse JSON-RPC request from body string."""
try:
request = json.loads(body)
except json.JSONDecodeError as e:
return None, {
"jsonrpc": "2.0",
"id": None,
"error": {
"code": -32700,
"message": f"Parse error: {e}"
}
}
# Validate JSON-RPC structure
if not isinstance(request, dict):
return None, {
"jsonrpc": "2.0",
"id": None,
"error": {
"code": -32600,
"message": "Invalid Request: expected object"
}
}
if request.get("jsonrpc") != "2.0":
return None, {
"jsonrpc": "2.0",
"id": request.get("id"),
"error": {
"code": -32600,
"message": "Invalid Request: jsonrpc must be '2.0'"
}
}
if "method" not in request:
return None, {
"jsonrpc": "2.0",
"id": request.get("id"),
"error": {
"code": -32600,
"message": "Invalid Request: missing 'method'"
}
}
return request, None
def do_POST(self):
"""Handle POST requests containing JSON-RPC messages."""
# Check the endpoint
if self.path != "/mcp":
self.send_error(404, "Not Found")
return
# Validate Content-Type
content_type = self.headers.get("Content-Type", "")
if "application/json" not in content_type:
self.send_error(415, "Unsupported Media Type")
return
# Read and parse the request
body = self.read_request_body()
if body is None:
self.send_error(400, "Empty request body")
return
request, error = self.parse_json_rpc(body)
if error:
self.send_json_response(error, status=400)
return
# Route the request to appropriate handler
response = self.handle_mcp_request(request)
self.send_json_response(response)
def handle_mcp_request(self, request):
"""Route MCP request to appropriate handler."""
method = request.get("method")
request_id = request.get("id")
params = request.get("params", {})
# TODO: Add method routing here
return {
"jsonrpc": "2.0",
"id": request_id,
"error": {
"code": -32601,
"message": f"Method not found: {method}"
}
}
def send_json_response(self, data, status=200):
"""Send a JSON response."""
body = json.dumps(data).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
JSON-RPC Error Codes
The JSON-RPC 2.0 specification defines standard error codes. Use these when reporting errors:
| Code | Message | Meaning |
|---|---|---|
-32700 |
Parse error | Invalid JSON received |
-32600 |
Invalid Request | JSON is not a valid request |
-32601 |
Method not found | Method does not exist |
-32602 |
Invalid params | Invalid method parameters |
-32603 |
Internal error | Server internal error |
Request Validation
Before processing a request, validate all required fields:
def validate_mcp_request(self, request):
"""Validate an MCP request has all required fields."""
errors = []
# Check jsonrpc version
if request.get("jsonrpc") != "2.0":
errors.append("jsonrpc must be '2.0'")
# Check method
method = request.get("method")
if not method:
errors.append("method is required")
elif not isinstance(method, str):
errors.append("method must be a string")
# Check id (required for requests, absent for notifications)
request_id = request.get("id")
if request_id is not None:
if not isinstance(request_id, (str, int)):
errors.append("id must be a string or integer")
# Check params (optional, but must be object if present)
params = request.get("params")
if params is not None and not isinstance(params, dict):
errors.append("params must be an object")
return errors
Testing the POST Handler
Test your handler with curl:
# Valid JSON-RPC request
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {"name": "test-client", "version": "1.0.0"}
}
}'
# Invalid JSON
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d 'not valid json'
# Missing method
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 1}'
Always return a JSON-RPC error response (not just HTTP errors) when the request body is malformed. This helps clients handle errors consistently using the JSON-RPC error structure.
Handling the Accept Header
MCP clients indicate their preference for SSE responses using the
Accept header:
def client_accepts_sse(self):
"""Check if client accepts SSE responses."""
accept = self.headers.get("Accept", "")
return "text/event-stream" in accept
def do_POST(self):
"""Handle POST with SSE awareness."""
# ... validation code ...
request, error = self.parse_json_rpc(body)
if error:
self.send_json_response(error, status=400)
return
# Decide response format based on Accept header
if self.client_accepts_sse():
self.handle_sse_response(request)
else:
response = self.handle_mcp_request(request)
self.send_json_response(response)
Now let's implement SSE response streaming to send real-time updates back to the client.