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

response must contain exactly one of result or error

Error message

response must contain exactly one of result or error

What it means

The result/error XOR rule of JSON-RPC 2.0, enforced in validate_response: has_result == has_error means the response contained both members or neither. A response with both is ambiguous and one with neither is a meaningless envelope, so the conformance check rejects it before looking at HTTP status or payload shape.

Solutions

  1. Return exactly one of "result" or "error" per response, never both, never neither
  2. For notifications the server must return 202 with an empty body, not an empty JSON object
  3. Audit response builders for code paths that attach a default empty result alongside an error
  4. Add a regression transcript covering the both-fields case
Defensive patterns

Strategy: validation

When it happens

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

Common situations: See trigger scenarios.


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

Appendix: source

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


def validate_response(
    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}")

View on GitHub (pinned to 39ea8a1c6d)