DraftStandards Track
| Field | Value |
|---|---|
| SEP | 2243 |
| Title | HTTP Header Standardization for Streamable HTTP Transport |
| Status | Draft |
| Type | Standards Track |
| Created | 2026-02-04 |
| Author(s) | MCP Transports Working Group |
| Sponsor | None |
| PR | #2243 |
Abstract
This SEP proposes exposing critical routing and context information in standard HTTP header locations for the Streamable HTTP transport. By mirroring key fields from the JSON-RPC payload into HTTP headers, network intermediaries such as load balancers, proxies, and observability tools can route and process MCP traffic without deep packet inspection, reducing latency and computational overhead.Motivation
Current MCP implementations over HTTP bury all routing information within the JSON-RPC payload. This creates friction for network infrastructure:- Load balancers must terminate TLS and parse the entire JSON body to extract routing information (e.g., region, tool name)
- Proxies and gateways cannot make routing decisions without deep packet inspection
- Observability tools have limited visibility into MCP traffic patterns
- Rate limiters and WAFs cannot apply policies based on MCP-specific fields
Specification
Standard Headers
The Streamable HTTP transport will require POST requests to include the following headers mirrored from the request body:| Header Name | Source Field | Required For |
|---|---|---|
Mcp-Method | method | All requests and notifications |
Mcp-Name | params.name or params.uri | tools/call, resources/read, prompts/get requests |
Rationale: This requirement prevents potential security vulnerabilities and error conditions that could arise when different components in the network rely on different sources of truth. For example, a load balancer or gateway might use the header values to make routing decisions, while the MCP server uses the body values for execution. This requirement applies to any network intermediary that processes the message body, as well as the MCP server itself.Case Sensitivity: Header names (called “field names” in RFC 9110) are case-insensitive. Clients and servers MUST use case-insensitive comparisons for header names.
Example: tools/call Request
Example: resources/read Request
Example: prompts/get Request
Example: Other Request Methods
For requests that don’t involve tools, resources, or prompts, only theMcp-Method header is required:
Example: Notification
Notifications also require theMcp-Method header:
Custom Headers from Tool Parameters
MCP servers MAY designate specific tool parameters to be mirrored into HTTP headers using anx-mcp-header extension property in the parameter’s schema within the tool’s inputSchema.
Client Requirement: While the use of x-mcp-header is optional for servers, clients MUST support this feature. When a server’s tool definition includes x-mcp-header annotations, conforming clients MUST mirror the designated parameter values into HTTP headers as specified in this document.
Schema Extension
Thex-mcp-header property specifies the name portion used to construct the header name Mcp-Param-{name}.
Constraints on x-mcp-header values:
- MUST NOT be empty
- MUST contain only ASCII characters (excluding space and
:) - MUST be case-insensitively unique among all
x-mcp-headervalues in theinputSchema - MUST only be applied to parameters with primitive types (number, string, boolean)
x-mcp-header value violates these constraints. Rejection means the client MUST exclude the invalid tool from the result of tools/list. Clients SHOULD log a warning when rejecting a tool definition, including the tool name and the reason for rejection. This behavior ensures that a single malformed tool definition does not prevent other valid tools from being used.
Example Tool Definition:
Example: Geo-Distributed Database
Consider a server exposing anexecute_sql tool for Google Cloud Spanner, which requires a region parameter.
Tool Definition:
us-west1.
Current Friction: The global load balancer receives the request but must terminate TLS and parse the entire JSON body to find "region": "us-west1" before it knows whether to route the packet to the Oregon or Belgium cluster.
With This Proposal: The client detects the x-mcp-header annotation and automatically adds the header Mcp-Param-Region: us-west1 to the HTTP request. The load balancer can now route based on the header without parsing the body.
Request:
Example: Multi-Tenant SaaS Application
A SaaS platform exposes tools that operate on different customer tenants. By exposing the tenant ID in a header, the platform can route requests to tenant-specific infrastructure. Tool Definition:Example: Priority-Based Request Handling
A server can expose a priority parameter to allow infrastructure to prioritize certain requests. Tool Definition:Header Processing
Value Encoding
Clients MUST encode parameter values before including them in HTTP headers to ensure safe transmission and prevent injection attacks. Character Restrictions Per RFC 9110, HTTP header field values must consist of visible ASCII characters (0x21-0x7E), space (0x20), and horizontal tab (0x09). The following characters are explicitly prohibited:- Carriage return (
\r, 0x0D) - Line feed (
\n, 0x0A) - Null character (
\0, 0x00) - Any character outside the ASCII range (> 0x7F)
- Starts with a space (0x20) or horizontal tab (0x09)
- Ends with a space (0x20) or horizontal tab (0x09)
-
Type conversion: Convert the parameter value to its string representation:
string: Use the value as-isnumber: Convert to decimal string representation (e.g.,42,3.14)boolean: Convert to lowercase"true"or"false"
-
Whitespace check: If the string starts or ends with whitespace (space or tab):
- Apply Base64 encoding (see below)
-
ASCII validation: Check if the string contains only valid ASCII characters (0x20-0x7E):
- If valid, proceed to step 4
- If invalid (contains non-ASCII characters), apply Base64 encoding (see below)
-
Control character check: If the string contains any control characters (0x00-0x1F or 0x7F):
- Apply Base64 encoding (see below)
=?base64? and suffix ?= indicate that the value is Base64-encoded. Servers and intermediaries that need to inspect these values MUST decode them accordingly.
Examples:
| Original Value | Reason | Encoded Header Value |
|---|---|---|
"us-west1" | Plain ASCII | Mcp-Param-Region: us-west1 |
"Hello, 世界" | Contains non-ASCII | Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?= |
" padded " | Leading/trailing spaces | Mcp-Param-Text: =?base64?IHBhZGRlZCA=?= |
"line1\nline2" | Contains newline | Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?= |
Client Behavior
When constructing atools/call request via HTTP transport, the client MUST:
- Extract the values for any standard headers from the request body (e.g.,
method,params.name,params.uri) - Append the
Mcp-Methodheader and, if applicable,Mcp-Nameheader to the request - Inspect the tool’s
inputSchemafor properties marked withx-mcp-headerand extract the value for each parameter - Encode the values according to the rules in Value Encoding
- Append a
Mcp-Param-{Name}: {Value}header to the request:
Server Behavior
When receiving a request, the server MUST reject requests withMcp-Param-{Name} headers that contain invalid characters (see “Character Restrictions” in the Value Encoding section).
Any server that processes the message body (not simply forwarding it) MUST validate that encoded header values, after decoding if Base64-encoded, match the corresponding values in the request body. Servers MUST reject requests with a 400 Bad Request HTTP status if any validation fails.
Error Code
When rejecting a request due to header validation failure, servers MUST return a JSON-RPC error response with the following error code:
| Code | Name | Description |
|---|---|---|
-32001 | HeaderMismatch | The HTTP headers do not match the corresponding values in the request body, or required headers are missing/malformed. |
-32000 to -32099).
Error Response Format:
- A required standard header (
Mcp-Method,Mcp-Name, etc.) is missing - A header value does not match the request body value
- A Base64-encoded value cannot be decoded
- A header value contains invalid characters
Note: Intermediaries MUST return an appropriate HTTP error status (e.g., 400 Bad Request) for validation failures but are not required to return a JSON-RPC error response.
Custom Header Handling:
Custom headers (those defined via x-mcp-header) follow the same validation rules as standard headers:
| Scenario | Client Behavior | Server Behavior |
|---|---|---|
| Parameter value provided | Client MUST include the header | Server MUST validate header matches body |
Parameter value is null | Client MUST omit the header | Server MUST NOT expect the header |
| Parameter not in arguments | Client MUST omit the header | Server MUST NOT expect the header |
| Client omits header but value is in body | Non-conforming client | Server MUST reject the request |
400 Bad Request with JSON-RPC error code -32001 (HeaderMismatch).
Rationale
Headers vs Path
This proposal mirrors request data into headers rather than encoding it in the URL path. Advantages of Headers:- Simplicity: All widely-used network load balancers support routing based on HTTP headers
- Multi-version support: Easier to support multiple MCP versions in clients and servers
- Compatibility: Headers work with the existing Streamable HTTP transport design without changing the endpoint structure
- Unlimited values: Header values can contain characters that would require encoding in URLs (e.g.,
/,?,#) - No URL length limits: Very long values can be transmitted without hitting URL length restrictions
- Framework simplicity: Many web frameworks (Flask, Express, Django, Rails) have built-in support for path-based routing with minimal configuration
- Logging: URL paths are typically logged by default, making debugging easier
| Framework | Header-based Routing | Path-based Routing |
|---|---|---|
| Flask (Python) | Requires middleware or decorators to extract headers before routing | Native support via @app.route('/mcp/<method>') |
| Express (Node.js) | Easy via req.headers but requires custom routing logic | Native support via app.post('/mcp/:method') |
| Django (Python) | Requires custom middleware | Native URL patterns |
| Go (net/http) | Easy via r.Header.Get() | Native via path patterns |
| ASP.NET Core | Easy via [FromHeader] attribute | Native via route templates |
- Backwards Compatibility introducing path based routing would require all existing MCP Servers to take a major update, and potentially support two sets of endpoints to support multiple versions. Even if the SDKs can paper over this additional operational concerns like testing, metrics, etc would need to happen. Header based routing requires minimal client side changes. And clients which don’t opt in will still function correctly.
- Infrastructure benefits outweigh framework complexity: The primary goal is enabling network infrastructure (load balancers, proxies, WAFs) to route and process requests without body parsing. This benefit applies regardless of the server framework.
Infrastructure Support
HTTP header-based routing and processing is supported by:- Load Balancers: All major load balancers (HAProxy, NGINX, Cloudflare, F5, Envoy/Istio)
- Rate Limiting: 9 of 11 popular rate-limiting solutions
- Authorization: Kong, Tyk, AWS API Gateway, Google Cloud Apigee, Azure API Gateway, NGINX, Apache APISIX, Istio/Envoy
- Web Application Firewalls: Cloudflare WAF, AWS WAF, Azure WAF, F5 Advanced WAF, FortiWeb, Imperva WAF, Barracuda WAF, ModSecurity, Akamai, Wallarm
- Observability: Most observability solutions can extract data from HTTP headers
Explicit Header Names in x-mcp-header
The design uses an explicit name value inx-mcp-header rather than deriving the header name from the parameter name because:
- Case sensitivity mismatch: Header names are case-insensitive, but JSON Schema property names are case-sensitive
- Character set constraints: Header names are limited to ASCII characters, but tool parameter names may contain arbitrary Unicode
- Simplicity: No complex scheme needed for constructing header names from nested properties
Placement Within JSON Schema
Thex-mcp-header extension is placed directly within the JSON Schema of the property to be mirrored, rather than in a separate metadata field outside the schema. This design choice offers several advantages:
- Co-location: The header mapping is defined alongside the property it affects, making it immediately clear which parameter will be mirrored. Developers don’t need to cross-reference between the schema and a separate metadata structure.
-
Established pattern: JSON Schema explicitly supports extension keywords (properties starting with
x-), and this pattern is widely used in ecosystems like OpenAPI. Tool authors and SDK developers are already familiar with this approach. -
Schema composability: When schemas are composed, extended, or referenced using
$ref, thex-mcp-headerannotation travels with the property definition. A separate metadata structure would require complex synchronization logic to maintain consistency. -
Tooling compatibility: Existing JSON Schema validators ignore unknown keywords by default, so adding
x-mcp-headerdoesn’t break existing schema validation. Tools that don’t understand this extension simply skip it. - Reduced complexity: A separate metadata structure would require defining a mapping mechanism (e.g., JSON Pointer or property paths) to associate headers with properties, adding implementation complexity and potential for errors.
Scope: Tools Only
Thex-mcp-header mechanism currently applies only to tools/call requests because tools are the only MCP primitive with an inputSchema that supports JSON Schema extension keywords. Resources and prompts lack an equivalent schema structure: resources/read takes only a uri (already exposed via Mcp-Name), and prompts/get defines arguments as a simple {name, description, required} array without JSON Schema extensibility. Generalizing custom header mapping to these primitives would require adding inputSchema-style definitions to resources and prompts, which is a larger specification change. This is noted as a potential future extension.
No Specification-Level Header Size Limit
This specification intentionally does not define limits on individual header value length, total MCP header size, or number of custom headers. Headers are solely an HTTP concept, and HTTP itself (RFC 9110) does not specify header size limits. Common HTTP infrastructure imposes its own limits — ranging from 4–8 KB on some servers (e.g., Apache at ~8190 bytes) to 128 KB on others (e.g., Cloudflare) — but the appropriate limit depends on the deployment environment, which only the service operator can determine. Defining a specification-level limit (such as “omit headers exceeding 8192 bytes”) would introduce problems:- Arbitrary threshold: Any chosen value would be too low for some deployments and irrelevant for others. The “right” limit varies by infrastructure.
- Counterproductive omission: If a client omits a header because it exceeds a spec-defined limit, servers and intermediaries that rely on that header for routing must either parse the body or reject the request — undermining the core purpose of exposing values in headers.
- Unnecessary SDK burden: SDK maintainers would need to implement and test limit-checking logic for a constraint that rarely applies in practice.
- Redundant with HTTP: Servers and intermediaries already reject oversized headers using standard HTTP status codes (
413 Request Entity Too Large,431 Request Header Fields Too Large), which clients must handle regardless.
Note to implementers: Servers, intermediaries, and clients MAY independently impose limits on individual header size, total MCP header size, or number of custom headers as appropriate for their deployment environment. Servers SHOULD document any limits they impose. Clients SHOULD gracefully handle413 Request Entity Too Largeor431 Request Header Fields Too Largeresponses. Tool authors SHOULD limitx-mcp-headerannotations to parameters that provide clear infrastructure benefits.
Encoding Approach for Unsafe Values
Four approaches were considered for encoding parameter values that cannot be safely represented as plain ASCII header values (non-ASCII characters, leading/trailing whitespace, control characters):-
Sentinel wrapping (chosen approach): Use the
=?base64?{value}?=prefix/suffix within the sameMcp-Param-{Name}header to signal Base64-encoded values. -
Separate header name: Use a distinct header name for encoded values, e.g.
Mcp-ParamEncoded-{Name}, so the encoding is indicated by the header name rather than the value format. -
Implicit encoding: Let the parser infer encoding from the tool schema, e.g. via a
"x-mcp-header-encoding": "base64"annotation in the tool definition. -
Always encode: Base64-encode every
Mcp-Param-{Name}value unconditionally.
| Approach | Pros | Cons |
|---|---|---|
| Sentinel wrapping | Single header name per parameter; common case (plain ASCII) is human-readable; intermediaries can route on plain values without decoding | In-band signaling can theoretically collide with literal values; every reader must check for the prefix |
| Separate header name | No in-band ambiguity; encoding is self-documenting from the header name | Doubles the header namespace; every intermediary must check two header names per parameter; needs a conflict rule if both are present |
| Implicit encoding | Simplest wire format; no sentinels or extra headers | Intermediaries need access to the tool schema to know whether to decode — defeats the purpose of exposing values in headers; static per-parameter decision doesn’t handle the mixed case well |
| Always encode | Simplest rules; no conditional logic or ambiguity | Plain ASCII values become unreadable; intermediaries must decode Base64 to inspect any value, significantly undermining the core motivation of this SEP |
=?base64?...?= is an unlikely literal parameter value in practice.
Backward Compatibility
Standard Headers
Existing clients and SDKs will be required to include the standard headers when using the new MCP version. This is a minor addition since clients already include headers likeMcp-Protocol-Version, adding only one or two new headers per message.
Servers implementing the new version MUST reject requests missing required headers. Servers MAY support older clients by accepting requests without headers when negotiating an older protocol version.
Custom Headers from Tool Parameters
Thex-mcp-header extension is optional for servers. Existing tools without this property continue to work unchanged. However, clients implementing the MCP version that includes this specification MUST support the feature. Older clients that do not support x-mcp-header will still function but will not provide the header-based routing benefits that servers may depend on.
Security Implications
Header Injection
Header injection attacks occur when malicious values containing control characters (especially\r\n) are included in headers, potentially allowing attackers to inject additional headers or terminate the header section early.
Clients MUST follow the Value Encoding rules defined in this specification. These rules ensure that:
- Control characters are never included in header values
- Non-ASCII values are safely encoded using Base64
- Values exceeding safe length limits are omitted
Header Spoofing
Servers MUST validate that header values match the corresponding values in the request body. This prevents clients from sending mismatched headers to manipulate routing while executing different operations. For example, a malicious client could attempt to:- Route a request to a less-secured region while executing operations intended for a high-security region
- Bypass rate limits by spoofing tenant identifiers
- Evade security policies by misrepresenting the operation being performed
Information Disclosure
Tool parameter values designated for headers will be visible to network intermediaries (load balancers, proxies, logging systems). Server developers:- SHOULD NOT mark sensitive parameters (passwords, API keys, tokens, PII) with
x-mcp-header - SHOULD document which parameters are exposed as headers
- SHOULD consider that Base64 encoding provides no confidentiality—it is merely an encoding, not encryption
Trusting Header Values
Header values originate from tool call arguments, which may be influenced by an LLM or a malicious client. Intermediaries and servers MUST NOT treat these values as trusted input for security-sensitive decisions. In particular:- Header values that imply access to specific resources (e.g., tenant IDs, region names) MUST be independently verified against the authenticated user’s permissions before granting access to those resources.
- Header values MUST NOT be used as the sole basis for granting elevated privileges without server-side enforcement of rate limits and quotas.
- Deployments SHOULD reject requests with oversized or excessive headers early in the pipeline — before performing Base64 decoding or body parsing — to mitigate denial-of-service risks from crafted payloads.
Conformance Test Cases
This section defines edge cases that conformance tests MUST cover to ensure interoperability between implementations.Standard Header Edge Cases
Case Sensitivity
| Test Case | Input | Expected Behavior |
|---|---|---|
| Header name case variation | mcp-method: tools/call | Server MUST accept (header names are case-insensitive) |
| Header name mixed case | MCP-METHOD: tools/call | Server MUST accept |
| Method value case | Mcp-Method: TOOLS/CALL | Server MUST reject (method values are case-sensitive) |
Header/Body Mismatch
| Test Case | Header Value | Body Value | Expected Behavior |
|---|---|---|---|
| Method mismatch | Mcp-Method: tools/call | "method": "prompts/get" | Server MUST reject with 400 and error code -32001 |
| Tool name mismatch | Mcp-Name: foo | "params": {"name": "bar"} | Server MUST reject with 400 and error code -32001 |
| Missing required header | (no Mcp-Method) | Valid body | Server MUST reject with 400 and error code -32001 |
| Extra whitespace in header | Mcp-Name: foo | "params": {"name": "foo"} | Server MUST accept (trim whitespace per HTTP spec) |
Special Characters in Values
| Test Case | Value | Expected Behavior |
|---|---|---|
| Tool name with hyphen | my-tool-name | Client sends as-is; server accepts |
| Tool name with underscore | my_tool_name | Client sends as-is; server accepts |
| Resource URI with special chars | file:///path/to/file%20name.txt | Client sends as-is; server accepts |
| Resource URI with query string | https://example.com/resource?id=123 | Client sends as-is; server accepts |
Custom Header Edge Cases
x-mcp-header Name Conflicts
| Test Case | Schema | Expected Behavior |
|---|---|---|
| Duplicate header names (same case) | Two properties with "x-mcp-header": "Region" | Client MUST reject tool definition |
| Duplicate header names (different case) | "x-mcp-header": "Region" and "x-mcp-header": "REGION" | Client MUST reject tool definition (case-insensitive uniqueness) |
| Header name matches standard header | "x-mcp-header": "Method" | Allowed (produces Mcp-Param-Method, not Mcp-Method) |
| Empty header name | "x-mcp-header": "" | Client MUST reject tool definition |
Invalid x-mcp-header Values
| Test Case | x-mcp-header Value | Expected Behavior |
|---|---|---|
| Contains space | "x-mcp-header": "My Region" | Client MUST reject tool definition |
| Contains colon | "x-mcp-header": "Region:Primary" | Client MUST reject tool definition |
| Contains non-ASCII | "x-mcp-header": "Région" | Client MUST reject tool definition |
| Contains control character | "x-mcp-header": "Region\t1" | Client MUST reject tool definition |
Value Encoding Edge Cases
| Test Case | Parameter Value | Expected Header Value |
|---|---|---|
| Plain ASCII string | "us-west1" | Mcp-Param-Region: us-west1 |
| String with leading space | " us-west1" | Mcp-Param-Region: =?base64?IHVzLXdlc3Qx?= |
| String with trailing space | "us-west1 " | Mcp-Param-Region: =?base64?dXMtd2VzdDEg?= |
| String with leading/trailing spaces | " us-west1 " | Mcp-Param-Region: =?base64?IHVzLXdlc3QxIA==?= |
| String with internal spaces only | "us west 1" | Mcp-Param-Region: us west 1 |
| Boolean true | true | Mcp-Param-Flag: true |
| Boolean false | false | Mcp-Param-Flag: false |
| Integer | 42 | Mcp-Param-Count: 42 |
| Floating point | 3.14159 | Mcp-Param-Value: 3.14159 |
| Non-ASCII characters | "日本語" | Mcp-Param-Text: =?base64?5pel5pys6Kqe?= |
| String with newline | "line1\nline2" | Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?= |
| String with carriage return | "line1\r\nline2" | Mcp-Param-Text: =?base64?bGluZTENCmxpbmUy?= |
| String with leading tab | "\tindented" | Mcp-Param-Text: =?base64?CWluZGVudGVk?= |
| Empty string | "" | Mcp-Param-Name: (empty value) |
Type Restriction Violations
| Test Case | Property Type | x-mcp-header Present | Expected Behavior |
|---|---|---|---|
| Array type | "type": "array" | Yes | Server MUST reject tool definition |
| Object type | "type": "object" | Yes | Server MUST reject tool definition |
| Null type | "type": "null" | Yes | Server MUST reject tool definition |
| Nested property | Property inside object | Yes | Server MUST reject tool definition |
Server Validation Edge Cases
Base64 Decoding
| Test Case | Header Value | Expected Behavior |
|---|---|---|
| Valid Base64 | =?base64?SGVsbG8=?= | Server decodes to "Hello" and validates |
| Invalid Base64 padding | =?base64?SGVsbG8?= | Server MUST reject with 400 and error code -32001; Intermediary MAY reject with 400 status code |
| Invalid Base64 characters | =?base64?SGVs!!!bG8=?= | Server MUST reject with 400 and error code -32001; Intermediary MAY reject with 400 status code |
| Missing prefix | SGVsbG8= | Server treats as literal value, not Base64 |
| Missing suffix | =?base64?SGVsbG8= | Server treats as literal value, not Base64 |
| Malformed wrapper | =?BASE64?SGVsbG8=?= | Server MUST accept (case-insensitive prefix) |
Null and Missing Values
| Test Case | Scenario | Expected Behavior |
|---|---|---|
| Parameter with x-mcp-header is null | "region": null | Client MUST omit header |
| Parameter with x-mcp-header is missing | Parameter not in arguments | Client MUST omit header |
| Optional parameter present | Optional parameter provided | Client MUST include header |
Missing Custom Header with Value in Body
| Test Case | Header Present | Body Value | Expected Behavior |
|---|---|---|---|
| Standard header omitted, value in body | No Mcp-Name | "params": {"name": "foo"} | Server MUST reject with 400 and error code -32001; Intermediary MAY reject with 400 status code |
| Custom header omitted, value in body | No Mcp-Param-Region | "region": "us-west1" | Server MUST reject with 400 and error code -32001; Intermediary MAY reject with 400 status code |
Reference Implementation
To be provided before this SEP reaches Final status. Implementation requirements:- Server SDKs: Provide a mechanism (attribute/decorator) for marking parameters with
x-mcp-header - Client SDKs: Implement the client behavior for extracting and encoding header values
- Validation: Both sides must validate header/body consistency