rohitg00/ai-engineering-from-scratch · error · ProtocolViolation

successful JSON-RPC result requires HTTP 200

Error message

successful JSON-RPC result requires HTTP 200

What it means

Transport-level consistency rule in validate_response: a response carrying a JSON-RPC result (a success payload) must arrive with HTTP 200. Fires when the server signals application success in the body but a non-200 status (e.g. 202 or 500) on the wire, which is a contradictory envelope for Streamable HTTP MCP.

Solutions

  1. Return HTTP 200 whenever the body contains a "result" member
  2. Map application errors to the JSON-RPC error member with the documented HTTP status instead of a non-200 with a result
  3. Check middleware or error handlers that override the status code after the handler succeeded
  4. Verify the transcript fixture's expected status matches the server's actual behavior
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at phases/13-tools-and-protocols/31-mcp-conformance-versioning-and-operations/code/main.py:400 when the library encounters an invalid state.

Common situations: See trigger scenarios.

Understand the failure class


AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26). Data as JSON: /api/errors/c5f2fdcffc88d972. Report an issue: GitHub.

Appendix: source

Thrown at phases/13-tools-and-protocols/31-mcp-conformance-versioning-and-operations/code/main.py:400

    request: dict[str, Any],
    status: int,
    response: dict[str, Any],
    era: str,
    capabilities: set[str],
) -> None:
    if not isinstance(response, dict) or response.get("jsonrpc") != "2.0":
        raise ProtocolViolation("invalid JSON-RPC response envelope")
    request_id = request.get("id")
    response_id = response.get("id")
    if type(response_id) is not type(request_id) or response_id != request_id:
        raise ProtocolViolation("response id does not exactly match request id")
    has_result = "result" in response
    has_error = "error" in response
    if has_result == has_error:
        raise ProtocolViolation("response must contain exactly one of result or error")
    if has_result:
        if status != 200:
            raise ProtocolViolation("successful JSON-RPC result requires HTTP 200")
        validate_result(response["result"], era, capabilities)
        validate_method_result(request["method"], response["result"])
        return

    error = response["error"]
    if not isinstance(error, dict):
        raise ProtocolViolation("JSON-RPC error must be an object")
    code = error.get("code")
    if isinstance(code, bool) or not isinstance(code, int) or not isinstance(error.get("message"), str):
        raise ProtocolViolation("JSON-RPC error requires integer code and string message")
    expected_status = {-32020: 400, -32021: 400, -32022: 400, -32601: 404}.get(code)
    if expected_status is not None and status != expected_status:
        raise ProtocolViolation(f"JSON-RPC error {code} requires HTTP {expected_status}")


def validate_protocol_error_evidence(case: Transcript, expected_code: int) -> None:
    if case.response_body is None:
        raise ProtocolViolation("expected JSON-RPC error response is missing")

View on GitHub (pinned to 39ea8a1c6d)