Development Choices

Header Routing for MCP Streamable HTTP

Author
Joseph TrasattiMember of technical staff
Published
Section
MCP
Length
11 min read3 sources cited

Header-based routing mirrors MCP protocol version, method, and named target into HTTP headers, letting gateways route, authorize, meter, and rate-limit requests without parsing JSON-RPC. The MCP server still has to compare every required header with the body and reject missing, malformed, or conflicting values before it dispatches the operation.

Header-based routing

Header-based routing exposes the identity of an MCP operation in HTTP metadata, before a gateway reads the JSON-RPC body. It gives infrastructure enough information to select and enforce a policy while leaving the body as the request the MCP server ultimately executes.

The Streamable HTTP transport in the 2026-07-28 MCP specification requires every JSON-RPC request to travel in its own HTTP POST. Each modern request includes MCP-Protocol-Version and Mcp-Method. A request that names a tool, prompt, or resource also includes Mcp-Name:

Header Mirrored body field Required condition
MCP-Protocol-Version params._meta[io.modelcontextprotocol/protocolVersion] Every request POST
Mcp-Method method Every request POST
Mcp-Name params.name or params.uri tools/call, prompts/get, and resources/read

A tool call therefore exposes enough metadata for an intermediary to recognize the protocol revision, the tools/call method, and the selected tool without decoding JSON:

POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
Content-Type: application/json

Mcp-Name is not required merely because a request contains some field called name. Its defined source depends on the method. For tools/call and prompts/get, it mirrors params.name; for resources/read, it mirrors params.uri. Methods such as tools/list still carry Mcp-Method, but they have no named target and therefore no Mcp-Name.

Header names are case-insensitive under HTTP rules. Header values are not: tools/call and Tools/Call are different values. Policy configuration should preserve that distinction rather than normalizing method or target names.

The edge can see the operation

An MCP request exposes standard headers to gateway policy before the handler validates the body
Header visibility helps routing, but end-to-end agreement still needs validation.

A gateway can route, authorize, meter, and rate-limit a modern Streamable HTTP request from these headers without parsing its JSON-RPC body. That changes where common request controls can run, but it does not change which component executes the body.

Routing can select an upstream from Mcp-Method, or use Mcp-Name when particular tools, prompts, or resources have separate handlers. A deployment that assigns operations to regions can use the same metadata when implementing MCP server region routing. The routing rule sees a bounded method or target value instead of needing a JSON parser in the proxy path.

Authorization can compare the authenticated caller with an allowlist keyed by method and, where present, name. A principal permitted to list tools but not call them can be stopped on Mcp-Method. A narrower policy can admit tools/call only for named tools. The headers do not provide an authenticated identity by themselves; they are operation metadata to combine with the deployment’s authentication result.

Metering can count requests by protocol version, method, or named target. Rate limits can use the same dimensions, such as a limit for one expensive tool or a separate allowance for resource reads. These controls do not require a gateway to buffer or decode the JSON-RPC document before choosing a bucket.

The visible metadata is deliberately limited. Standard headers identify the operation, not every argument passed to it. A policy that depends on an arbitrary tool argument cannot infer that value from Mcp-Method or Mcp-Name. It needs a designated custom header, body-aware enforcement at the server, or a policy expressed at a coarser operation boundary.

Header routing is also specific to Streamable HTTP. A request carried over the MCP standard input and output transport has no HTTP gateway or request headers, so these controls must sit elsewhere in that process boundary.

The body remains authoritative for execution

The mirrored values create two representations of the same operation. An intermediary acts on the headers, while the MCP server processes the JSON-RPC body. Both representations must agree or different components could make different decisions about one request.

Consider a request with Mcp-Method: tools/list at the edge and method: tools/call in the body. A gateway that authorizes only from the header could admit what appears to be a list operation. A server that dispatches only from the body could then execute a tool call. The mirror is safe only when the component processing the body verifies the equality before dispatch.

The 2026-07-28 MCP schema reference makes the protocol version part of the request metadata: io.modelcontextprotocol/protocolVersion is required in params._meta, and its value must match MCP-Protocol-Version for HTTP transport. Mcp-Method must likewise match method. Where Mcp-Name applies, its decoded value must match the method-specific params.name or params.uri.

A gateway’s approval is not a substitute for this server check. Infrastructure can be reconfigured, a route can bypass the usual gateway, or different components can interpret malformed values differently. The server must enforce the header-body contract at the point where it still has both representations and is about to act on the body.

Required server validation

A server that processes a modern request body must compare every required mirrored header with its corresponding body field. It must reject the request when a required header is missing, when a value is malformed, or when the header and body disagree.

For these validation failures, the specified response is HTTP 400 Bad Request with a JSON-RPC error using code -32020, named HeaderMismatch. The rule covers all three standard headers and any recognized custom parameter headers. An intermediary may return an appropriate HTTP error without constructing a JSON-RPC response, but the server’s body-aware rejection uses the protocol error.

The validation boundary includes these cases:

Request condition Required result
MCP-Protocol-Version or Mcp-Method is absent from a modern request Reject
A method requiring Mcp-Name omits it Reject
A mirrored header contains an invalid value Reject
A header value differs from its body source Reject
An encoded name or parameter cannot be decoded correctly Reject
Every required value is present, well formed, and equal to the body Continue normal request processing

A supported header value can still fail for another reason. If MCP-Protocol-Version and the body agree but the server does not implement that revision, the server returns 400 Bad Request with UnsupportedProtocolVersionError and its supported versions. If the version is supported but the requested RPC method is not implemented, the response is 404 Not Found with JSON-RPC code -32601. Keeping these outcomes distinct matters when diagnosing protocol-version negotiation: a header mismatch is a malformed request contract, not a request to negotiate an unsupported version.

Validation must happen before dispatch. Logging a disagreement and then executing the body still leaves the edge and origin enforcing different operations. Rewriting the body to match the header also changes the caller’s JSON-RPC request. Rejection gives the client an explicit failure and prevents either representation from silently winning.

Names that do not fit directly in a header

Mcp-Name may carry a tool name, prompt name, or resource URI that cannot safely appear as a plain ASCII HTTP field value. The 2026-07-28 transport defines a Base64 sentinel form for those values:

Mcp-Name: =?base64?{Base64EncodedValue}?=

The prefix =?base64? and suffix ?= are part of the format and are case-sensitive. Encoding applies when the value contains non-ASCII or control characters, has leading or trailing whitespace, or itself matches the sentinel pattern. The bytes encoded are the UTF-8 representation of the original value.

A gateway that routes on Mcp-Name must decode this form if its policy needs the logical name rather than the serialized header value. The MCP server must decode it before comparing it with params.name or params.uri. Comparing the encoded text directly with the body would reject a valid request; treating a malformed sentinel as an opaque name would accept metadata that the server cannot verify correctly.

This encoding is not a license to accept arbitrary field content. Invalid header characters remain a validation failure. The encoding exists so the client can transport a legitimate name or URI through HTTP without creating an injection path or losing the original value.

Custom headers for selected tool arguments

A tool definition can mark individual input properties with x-mcp-header. For Streamable HTTP, a conforming client then mirrors a present argument into Mcp-Param-{Name}. This extends header-visible policy beyond the tool name while keeping the selection explicit in the tool’s schema.

The extension applies only to statically reachable primitive properties. The supported types are string, integer, and boolean; number is not permitted. The annotation name must be a valid, non-empty HTTP field-name token, must not contain control characters, and must be unique without regard to case within that input schema. A property reached through arrays, composition keywords, conditionals, or $ref cannot be annotated. These limits make the extraction path unambiguous before a tool call arrives.

If the annotated argument is present, the client includes its header. If the argument is absent or null, the client omits it. A server that recognizes the tool definition must compare each expected parameter header with the argument value in the body. A present body value with no corresponding header, an invalid header, or a disagreement produces the same 400 and -32020 rejection used for standard metadata.

The TypeScript SDK migration guide for revision 2026-07-28 documents this behavior in Client.callTool() and createMcpHandler. It also records an operational boundary: browser clients skip custom argument mirroring because dynamically named headers cannot be statically allow-listed for credentialed CORS. A design that requires Mcp-Param-* at a browser-facing gateway is therefore the wrong fit unless the browser and cross-origin policy can support the required header set.

Custom parameter headers have a maintenance cost. The gateway policy, client’s current tool definition, and server validation must use the same annotation. When a server returns HeaderMismatch because an expected Mcp-Param-* value is missing or stale, the protocol advises the client to refresh tools/list and retry with the current schema. This is an explicit contract, not a general mechanism for exposing every request argument to infrastructure.

Gateway handling and policy boundaries

An intermediary that does not recognize an Mcp-Param-* header must forward it and otherwise ignore it. A gateway configuration that strips unknown request headers can therefore break a conforming call between a client and a server that recognize the annotation. Standard and custom mirrored headers need to reach the component that validates the body.

A gateway enforcing policy from these headers should also check that MCP-Protocol-Version identifies a revision that requires header-body validation. If the version is older or the version header is absent, the gateway should reject the request instead of trusting metadata that the origin may not be required to verify. This prevents a request from entering a header-enforced route while claiming an earlier protocol contract.

Header-based authorization does not replace the transport’s other HTTP security rules. In particular, a browser-accessible endpoint still needs the prescribed Origin validation for MCP HTTP servers, and the server still needs authentication appropriate to the deployment. Operation headers say what the request claims to do; they do not prove who sent it or whether the browser origin is allowed.

Observability should retain enough information to distinguish edge rejection from server rejection without recording sensitive arguments indiscriminately. The HTTP status, JSON-RPC error code, protocol version, method, and applicable name identify the failed contract. If the failure reaches HeaderMismatch, debugging the failing MCP tool call should include checking what the client emitted, what each intermediary forwarded, and what the server decoded from both headers and body.

Version and message boundaries

The required metadata described here belongs to the modern 2026-07-28 Streamable HTTP request path. Earlier protocol revisions used different transport behavior and do not all promise these mirrored fields. A server deliberately supporting clients from before 2025-06-18 may treat an omitted MCP-Protocol-Version header as version 2025-03-26; a server that does not support those clients must reject the omission.

That compatibility allowance is not suitable for a gateway that makes security or routing decisions from modern headers. The gateway cannot safely infer Mcp-Method and Mcp-Name guarantees from a request that has not established a revision requiring server-side comparison. Such traffic needs its legacy handling path or rejection before modern header policy runs.

The 2026-07-28 core protocol defines no client-to-server notifications over Streamable HTTP, and it does not define required metadata headers for notification POSTs. The standard-header presence rule therefore applies to JSON-RPC requests, not as a blanket assertion about every possible JSON-RPC message carried by an HTTP POST. Implementations should preserve that distinction rather than applying a request validator to notification traffic and calling the result protocol compliance.

What to verify next

For an implementation review, check that the client emits the correct standard headers, any annotated tool arguments are encoded correctly, intermediaries preserve them, and the server validates them before dispatch. Then verify the neighboring controls: supported-version negotiation, authentication, Origin validation, and any operation-specific routing or rate-limit policy. The decisive property is consistent interpretation from the first gateway decision through the server’s final JSON-RPC dispatch.

Sources

  1. Streamable HTTP transport in the 2026-07-28 MCP specificationmodelcontextprotocol.io
  2. 2026-07-28 MCP schema referencemodelcontextprotocol.io
  3. TypeScript SDK migration guide for revision 2026-07-28ts.sdk.modelcontextprotocol.io

See also