Skip to content

inbound

Inbound request classification for the modern per-request-envelope path.

Pure module: no I/O, no transport, no mcp.server imports. Runs the validation ladder against a decoded JSON-RPC body and returns either an :class:InboundModernRoute (every rung passed) or an :class:InboundLadderRejection (the first rung that failed). Callers map a rejection's code through :data:ERROR_CODE_HTTP_STATUS to pick the HTTP status.

MCP_PROTOCOL_VERSION_HEADER module-attribute

MCP_PROTOCOL_VERSION_HEADER: Final = 'mcp-protocol-version'

Canonical lowercase name of the HTTP header carrying the MCP protocol version.

ERROR_CODE_HTTP_STATUS module-attribute

HTTP status to send for a JSON-RPC error.code.

Consulted for classifier-origin and handler-origin errors, so one table decides the wire status regardless of where the error was produced. Unmapped codes fall back to the caller's default (typically 200).

InboundModernRoute dataclass

A modern-protocol request whose envelope passed every ladder rung.

client_info and client_capabilities are the raw envelope values; the classifier checks presence only, not shape. Method existence is not a ladder rung — kernel dispatch is the single source of truth for that.

Source code in src/mcp/shared/inbound.py
65
66
67
68
69
70
71
72
73
74
75
76
@dataclass(frozen=True)
class InboundModernRoute:
    """A modern-protocol request whose envelope passed every ladder rung.

    ``client_info`` and ``client_capabilities`` are the raw envelope values;
    the classifier checks presence only, not shape. Method existence is not a
    ladder rung — kernel dispatch is the single source of truth for that.
    """

    protocol_version: str
    client_info: Any
    client_capabilities: Any

InboundLadderRejection dataclass

The first ladder rung that failed, as JSON-RPC error fields.

Source code in src/mcp/shared/inbound.py
79
80
81
82
83
84
85
@dataclass(frozen=True)
class InboundLadderRejection:
    """The first ladder rung that failed, as JSON-RPC error fields."""

    code: int
    message: str
    data: Any = None

classify_inbound_request

classify_inbound_request(
    body: Mapping[str, Any],
    *,
    headers: Mapping[str, str] | None = None,
    supported_modern_versions: Sequence[
        str
    ] = MODERN_PROTOCOL_VERSIONS
) -> InboundModernRoute | InboundLadderRejection

Run the modern-protocol validation ladder over a decoded JSON-RPC body.

Rungs, in order — first failure wins:

  1. params._meta is a mapping carrying every reserved envelope key (protocol version, client info, client capabilities) → else :data:~mcp.types.jsonrpc.INVALID_PARAMS.
  2. When headers is given, its MCP-Protocol-Version entry equals the envelope's protocol version → else :data:~mcp.types.jsonrpc.HEADER_MISMATCH. Runs before the supported-version rung so a client that disagrees with itself is told so, rather than told the body's version is unsupported.
  3. The envelope's protocol version is in supported_modern_versions → else :data:~mcp.types.jsonrpc.UNSUPPORTED_PROTOCOL_VERSION with data = {"supported": [...], "requested": <value>}.

Method existence is not a rung: kernel dispatch owns that decision so custom-registered methods route and the answer lives in one place.

Parameters:

Name Type Description Default
body Mapping[str, Any]

The decoded JSON-RPC request mapping. Envelope shape (jsonrpc / id) is not checked here.

required
headers Mapping[str, str] | None

Transport headers keyed by lowercase name, or None to skip the header rung (non-HTTP callers).

None
supported_modern_versions Sequence[str]

Modern protocol revisions this server accepts on the per-request-envelope path.

MODERN_PROTOCOL_VERSIONS
Source code in src/mcp/shared/inbound.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def classify_inbound_request(
    body: Mapping[str, Any],
    *,
    headers: Mapping[str, str] | None = None,
    supported_modern_versions: Sequence[str] = MODERN_PROTOCOL_VERSIONS,
) -> InboundModernRoute | InboundLadderRejection:
    """Run the modern-protocol validation ladder over a decoded JSON-RPC body.

    Rungs, in order — first failure wins:

    1. ``params._meta`` is a mapping carrying every reserved envelope key
       (protocol version, client info, client capabilities) → else
       :data:`~mcp.types.jsonrpc.INVALID_PARAMS`.
    2. When ``headers`` is given, its ``MCP-Protocol-Version`` entry equals
       the envelope's protocol version → else
       :data:`~mcp.types.jsonrpc.HEADER_MISMATCH`. Runs before the
       supported-version rung so a client that disagrees with itself is told
       so, rather than told the body's version is unsupported.
    3. The envelope's protocol version is in ``supported_modern_versions`` →
       else :data:`~mcp.types.jsonrpc.UNSUPPORTED_PROTOCOL_VERSION` with
       ``data = {"supported": [...], "requested": <value>}``.

    Method existence is *not* a rung: kernel dispatch owns that decision so
    custom-registered methods route and the answer lives in one place.

    Args:
        body: The decoded JSON-RPC request mapping. Envelope shape
            (``jsonrpc`` / ``id``) is not checked here.
        headers: Transport headers keyed by lowercase name, or ``None`` to
            skip the header rung (non-HTTP callers).
        supported_modern_versions: Modern protocol revisions this server
            accepts on the per-request-envelope path.
    """
    try:
        meta = body["params"]["_meta"]
        protocol_version = meta[PROTOCOL_VERSION_META_KEY]
        client_info = meta[CLIENT_INFO_META_KEY]
        client_capabilities = meta[CLIENT_CAPABILITIES_META_KEY]
    except (KeyError, TypeError):
        return InboundLadderRejection(
            code=INVALID_PARAMS,
            message="params._meta must carry the reserved protocol-version, client-info and "
            "client-capabilities envelope keys",
        )

    # TODO(L59): also validate Mcp-Method / Mcp-Name per SEP-2243 §Server Validation
    if headers is not None and headers.get(MCP_PROTOCOL_VERSION_HEADER) != protocol_version:
        return InboundLadderRejection(
            code=HEADER_MISMATCH,
            message=f"{MCP_PROTOCOL_VERSION_HEADER} header does not match the request envelope's protocol version",
        )

    if protocol_version not in supported_modern_versions:
        return InboundLadderRejection(
            code=UNSUPPORTED_PROTOCOL_VERSION,
            message="Unsupported protocol version",
            data=UnsupportedProtocolVersionErrorData(
                supported=list(supported_modern_versions), requested=protocol_version
            ).model_dump(mode="json"),
        )

    return InboundModernRoute(
        protocol_version=protocol_version,
        client_info=client_info,
        client_capabilities=client_capabilities,
    )