Lesson 8 - Step 3 of 6

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:

python
"""
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:

python
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:

bash
# 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}'
Best Practice

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:

python
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)
Next Step

Now let's implement SSE response streaming to send real-time updates back to the client.