SEP-2243: HTTP Header Standardization for Streamable HTTP Transport changedseps/2243-http-standardization
Upstream edited this page at 29 Jul 2026 22:46 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 28 Sep 2026 22:07 UTC.
Upstream edited
Recorded here
Lines+107added
Lines−107removed
From line
20
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits2to this page, all time
The whole hunk
from line 20, old and new numbered
/
from line 20
2020 requirements.
2121</Note>
2222
23| Field | Value |
24| ------------- | ------------------------------------------------------------------------------- |
25| **SEP** | 2243 |
26| **Title** | HTTP Header Standardization for Streamable HTTP Transport |
27| **Status** | Final |
28| **Type** | Standards Track |
29| **Created** | 2026-02-04 |
30| **Author(s)** | MCP Transports Working Group |
31| **Sponsor** | None |
32| **PR** | [#2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243) |
23| Field | Value |
24| - | - |
25| **SEP** | 2243 |
26| **Title** | HTTP Header Standardization for Streamable HTTP Transport |
27| **Status** | Final |
28| **Type** | Standards Track |
29| **Created** | 2026-02-04 |
30| **Author(s)** | MCP Transports Working Group |
31| **Sponsor** | None |
32| **PR** | [#2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243) |
3333
3434***
3535
from line 54
5454
5555The Streamable HTTP transport will require POST requests to include the following headers mirrored from the request body:
5656
57| Header Name | Source Field | Required For |
58| ------------ | ----------------------------- | ------------------------------------------------------ |
59| `Mcp-Method` | `method` | All requests and notifications |
60| `Mcp-Name` | `params.name` or `params.uri` | `tools/call`, `resources/read`, `prompts/get` requests |
57| Header Name | Source Field | Required For |
58| - | - | - |
59| `Mcp-Method` | `method` | All requests and notifications |
60| `Mcp-Name` | `params.name` or `params.uri` | `tools/call`, `resources/read`, `prompts/get` requests |
6161
6262These headers are **required** for compliance with the MCP version in which they are introduced.
6363
from line 444
444444
445445**Examples**:
446446
447| Original Value | Reason | Encoded Header Value |
448| ---------------------- | ------------------------ | ----------------------------------------------------- |
449| `"us-west1"` | Plain ASCII | `Mcp-Param-Region: us-west1` |
450| `"Hello, 世界"` | Contains non-ASCII | `Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?=` |
451| `" padded "` | Leading/trailing spaces | `Mcp-Param-Text: =?base64?IHBhZGRlZCA=?=` |
452| `"line1\nline2"` | Contains newline | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` |
453| `"=?base64?literal?="` | Matches sentinel pattern | `Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=` |
447| Original Value | Reason | Encoded Header Value |
448| - | - | - |
449| `"us-west1"` | Plain ASCII | `Mcp-Param-Region: us-west1` |
450| `"Hello, 世界"` | Contains non-ASCII | `Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?=` |
451| `" padded "` | Leading/trailing spaces | `Mcp-Param-Text: =?base64?IHBhZGRlZCA=?=` |
452| `"line1\nline2"` | Contains newline | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` |
453| `"=?base64?literal?="` | Matches sentinel pattern | `Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=` |
454454
455455#### Client Behavior
456456
from line 474
474474
475475When rejecting a request due to header validation failure, servers MUST return a JSON-RPC error response with the following error code:
476476
477| Code | Name | Description |
478| -------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
477| Code | Name | Description |
478| - | - | - |
479479| `-32001` | `HeaderMismatch` | The HTTP headers do not match the corresponding values in the request body, or required headers are missing/malformed. |
480480
481481This error code is in the JSON-RPC implementation-defined server error range (`-32000` to `-32099`).
from line 508
508508
509509Custom headers (those defined via `x-mcp-header`) follow the same validation rules as standard headers:
510510
511| Scenario | Client Behavior | Server Behavior |
512| ---------------------------------------- | ------------------------------ | ---------------------------------------- |
513| Parameter value provided | Client MUST include the header | Server MUST validate header matches body |
514| Parameter value is `null` | Client MUST omit the header | Server MUST NOT expect the header |
515| Parameter not in arguments | Client MUST omit the header | Server MUST NOT expect the header |
516| Client omits header but value is in body | Non-conforming client | Server MUST reject the request |
511| Scenario | Client Behavior | Server Behavior |
512| - | - | - |
513| Parameter value provided | Client MUST include the header | Server MUST validate header matches body |
514| Parameter value is `null` | Client MUST omit the header | Server MUST NOT expect the header |
515| Parameter not in arguments | Client MUST omit the header | Server MUST NOT expect the header |
516| Client omits header but value is in body | Non-conforming client | Server MUST reject the request |
517517
518518When rejecting requests due to missing or invalid custom headers, the server MUST return HTTP status `400 Bad Request` with JSON-RPC error code `-32001` (`HeaderMismatch`).
519519
from line 538
538538
539539**Trade-offs and Framework Considerations**:
540540
541| Framework | Header-based Routing | Path-based Routing |
542| ----------------- | ------------------------------------------------------------------- | ------------------------------------------------ |
543| Flask (Python) | Requires middleware or decorators to extract headers before routing | Native support via `@app.route('/mcp/<method>')` |
544| Express (Node.js) | Easy via `req.headers` but requires custom routing logic | Native support via `app.post('/mcp/:method')` |
545| Django (Python) | Requires custom middleware | Native URL patterns |
546| Go (net/http) | Easy via `r.Header.Get()` | Native via path patterns |
547| ASP.NET Core | Easy via `[FromHeader]` attribute | Native via route templates |
541| Framework | Header-based Routing | Path-based Routing |
542| - | - | - |
543| Flask (Python) | Requires middleware or decorators to extract headers before routing | Native support via `@app.route('/mcp/<method>')` |
544| Express (Node.js) | Easy via `req.headers` but requires custom routing logic | Native support via `app.post('/mcp/:method')` |
545| Django (Python) | Requires custom middleware | Native URL patterns |
546| Go (net/http) | Easy via `r.Header.Get()` | Native via path patterns |
547| ASP.NET Core | Easy via `[FromHeader]` attribute | Native via route templates |
548548
549549For frameworks like Flask that strongly favor path-based routing, implementing header-based routing requires additional code:
550550
from line 627
627627
6286284. **Always encode**: Base64-encode every `Mcp-Param-{Name}` value unconditionally.
629629
630| Approach | Pros | Cons |
631| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
632| 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 |
633| 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 |
634| 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 |
635| 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 |
630| Approach | Pros | Cons |
631| - | - | - |
632| 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 |
633| 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 |
634| 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 |
635| 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 |
636636
637637**Conclusion**: The sentinel wrapping approach provides the best trade-off. The primary use case for custom headers is enabling intermediaries to route and filter on simple, readable values like region names and tenant IDs — these are invariably plain ASCII and never trigger Base64 encoding. Option 4 makes all values opaque to intermediaries. Option 3 leaves intermediaries unable to distinguish encoded from literal values without access to the tool schema. Option 2 eliminates in-band ambiguity but doubles the header namespace, requiring intermediaries to check two possible header names per parameter and adding a conflict rule when both are present. The theoretical collision risk of the sentinel in Option 1 is negligible since `=?base64?...?=` is an unlikely literal parameter value in practice.
638638
from line 694
694694
695695#### Case Sensitivity
696696
697| Test Case | Input | Expected Behavior |
698| -------------------------- | ------------------------ | ------------------------------------------------------ |
697| Test Case | Input | Expected Behavior |
698| - | - | - |
699699| Header name case variation | `mcp-method: tools/call` | Server MUST accept (header names are case-insensitive) |
700| Header name mixed case | `MCP-METHOD: tools/call` | Server MUST accept |
701| Method value case | `Mcp-Method: TOOLS/CALL` | Server MUST reject (method values are case-sensitive) |
700| Header name mixed case | `MCP-METHOD: tools/call` | Server MUST accept |
701| Method value case | `Mcp-Method: TOOLS/CALL` | Server MUST reject (method values are case-sensitive) |
702702
703703#### Header/Body Mismatch
704704
705| Test Case | Header Value | Body Value | Expected Behavior |
706| -------------------------- | ------------------------ | --------------------------- | --------------------------------------------------- |
707| Method mismatch | `Mcp-Method: tools/call` | `"method": "prompts/get"` | Server MUST reject with 400 and error code `-32001` |
708| Tool name mismatch | `Mcp-Name: foo` | `"params": {"name": "bar"}` | Server MUST reject with 400 and error code `-32001` |
709| Missing required header | (no `Mcp-Method`) | Valid body | Server MUST reject with 400 and error code `-32001` |
710| Extra whitespace in header | `Mcp-Name: foo ` | `"params": {"name": "foo"}` | Server MUST accept (trim whitespace per HTTP spec) |
705| Test Case | Header Value | Body Value | Expected Behavior |
706| - | - | - | - |
707| Method mismatch | `Mcp-Method: tools/call` | `"method": "prompts/get"` | Server MUST reject with 400 and error code `-32001` |
708| Tool name mismatch | `Mcp-Name: foo` | `"params": {"name": "bar"}` | Server MUST reject with 400 and error code `-32001` |
709| Missing required header | (no `Mcp-Method`) | Valid body | Server MUST reject with 400 and error code `-32001` |
710| Extra whitespace in header | `Mcp-Name: foo ` | `"params": {"name": "foo"}` | Server MUST accept (trim whitespace per HTTP spec) |
711711
712712#### Special Characters in Values
713713
714| Test Case | Value | Expected Behavior |
715| ------------------------------- | ------------------------------------- | ---------------------------------- |
716| Tool name with hyphen | `my-tool-name` | Client sends as-is; server accepts |
717| Tool name with underscore | `my_tool_name` | Client sends as-is; server accepts |
718| Resource URI with special chars | `file:///path/to/file%20name.txt` | Client sends as-is; server accepts |
719| Resource URI with query string | `https://example.com/resource?id=123` | Client sends as-is; server accepts |
714| Test Case | Value | Expected Behavior |
715| - | - | - |
716| Tool name with hyphen | `my-tool-name` | Client sends as-is; server accepts |
717| Tool name with underscore | `my_tool_name` | Client sends as-is; server accepts |
718| Resource URI with special chars | `file:///path/to/file%20name.txt` | Client sends as-is; server accepts |
719| Resource URI with query string | `https://example.com/resource?id=123` | Client sends as-is; server accepts |
720720
721721### Custom Header Edge Cases
722722
723723#### x-mcp-header Name Conflicts
724724
725| Test Case | Schema | Expected Behavior |
726| --------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------- |
727| Duplicate header names (same case) | Two properties with `"x-mcp-header": "Region"` | Client MUST reject tool definition |
725| Test Case | Schema | Expected Behavior |
726| - | - | - |
727| Duplicate header names (same case) | Two properties with `"x-mcp-header": "Region"` | Client MUST reject tool definition |
728728| Duplicate header names (different case) | `"x-mcp-header": "Region"` and `"x-mcp-header": "REGION"` | Client MUST reject tool definition (case-insensitive uniqueness) |
729| Header name matches standard header | `"x-mcp-header": "Method"` | Allowed (produces `Mcp-Param-Method`, not `Mcp-Method`) |
730| Empty header name | `"x-mcp-header": ""` | Client MUST reject tool definition |
729| Header name matches standard header | `"x-mcp-header": "Method"` | Allowed (produces `Mcp-Param-Method`, not `Mcp-Method`) |
730| Empty header name | `"x-mcp-header": ""` | Client MUST reject tool definition |
731731
732732#### Invalid x-mcp-header Values
733733
734| Test Case | x-mcp-header Value | Expected Behavior |
735| -------------------------- | ---------------------------------- | ---------------------------------- |
736| Contains space | `"x-mcp-header": "My Region"` | Client MUST reject tool definition |
737| Contains colon | `"x-mcp-header": "Region:Primary"` | Client MUST reject tool definition |
738| Contains non-ASCII | `"x-mcp-header": "Région"` | Client MUST reject tool definition |
739| Contains control character | `"x-mcp-header": "Region\t1"` | Client MUST reject tool definition |
734| Test Case | x-mcp-header Value | Expected Behavior |
735| - | - | - |
736| Contains space | `"x-mcp-header": "My Region"` | Client MUST reject tool definition |
737| Contains colon | `"x-mcp-header": "Region:Primary"` | Client MUST reject tool definition |
738| Contains non-ASCII | `"x-mcp-header": "Région"` | Client MUST reject tool definition |
739| Contains control character | `"x-mcp-header": "Region\t1"` | Client MUST reject tool definition |
740740
741741#### Value Encoding Edge Cases
742742
743| Test Case | Parameter Value | Expected Header Value |
744| ----------------------------------- | ------------------ | ----------------------------------------------- |
745| Plain ASCII string | `"us-west1"` | `Mcp-Param-Region: us-west1` |
746| String with leading space | `" us-west1"` | `Mcp-Param-Region: =?base64?IHVzLXdlc3Qx?=` |
747| String with trailing space | `"us-west1 "` | `Mcp-Param-Region: =?base64?dXMtd2VzdDEg?=` |
748| String with leading/trailing spaces | `" us-west1 "` | `Mcp-Param-Region: =?base64?IHVzLXdlc3QxIA==?=` |
749| String with internal spaces only | `"us west 1"` | `Mcp-Param-Region: us west 1` |
750| Boolean true | `true` | `Mcp-Param-Flag: true` |
751| Boolean false | `false` | `Mcp-Param-Flag: false` |
752| Integer | `42` | `Mcp-Param-Count: 42` |
753| Floating point | `3.14159` | `Mcp-Param-Value: 3.14159` |
754| Non-ASCII characters | `"日本語"` | `Mcp-Param-Text: =?base64?5pel5pys6Kqe?=` |
755| String with newline | `"line1\nline2"` | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` |
756| String with carriage return | `"line1\r\nline2"` | `Mcp-Param-Text: =?base64?bGluZTENCmxpbmUy?=` |
757| String with leading tab | `"\tindented"` | `Mcp-Param-Text: =?base64?CWluZGVudGVk?=` |
758| Empty string | `""` | `Mcp-Param-Name: ` (empty value) |
743| Test Case | Parameter Value | Expected Header Value |
744| - | - | - |
745| Plain ASCII string | `"us-west1"` | `Mcp-Param-Region: us-west1` |
746| String with leading space | `" us-west1"` | `Mcp-Param-Region: =?base64?IHVzLXdlc3Qx?=` |
747| String with trailing space | `"us-west1 "` | `Mcp-Param-Region: =?base64?dXMtd2VzdDEg?=` |
748| String with leading/trailing spaces | `" us-west1 "` | `Mcp-Param-Region: =?base64?IHVzLXdlc3QxIA==?=` |
749| String with internal spaces only | `"us west 1"` | `Mcp-Param-Region: us west 1` |
750| Boolean true | `true` | `Mcp-Param-Flag: true` |
751| Boolean false | `false` | `Mcp-Param-Flag: false` |
752| Integer | `42` | `Mcp-Param-Count: 42` |
753| Floating point | `3.14159` | `Mcp-Param-Value: 3.14159` |
754| Non-ASCII characters | `"日本語"` | `Mcp-Param-Text: =?base64?5pel5pys6Kqe?=` |
755| String with newline | `"line1\nline2"` | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` |
756| String with carriage return | `"line1\r\nline2"` | `Mcp-Param-Text: =?base64?bGluZTENCmxpbmUy?=` |
757| String with leading tab | `"\tindented"` | `Mcp-Param-Text: =?base64?CWluZGVudGVk?=` |
758| Empty string | `""` | `Mcp-Param-Name: ` (empty value) |
759759
760760#### Type Restriction Violations
761761
762| Test Case | Property Type | x-mcp-header Present | Expected Behavior |
763| --------------- | ---------------------- | -------------------- | ---------------------------------- |
764| Array type | `"type": "array"` | Yes | Server MUST reject tool definition |
765| Object type | `"type": "object"` | Yes | Server MUST reject tool definition |
766| Null type | `"type": "null"` | Yes | Server MUST reject tool definition |
767| Nested property | Property inside object | Yes | Server MUST reject tool definition |
762| Test Case | Property Type | x-mcp-header Present | Expected Behavior |
763| - | - | - | - |
764| Array type | `"type": "array"` | Yes | Server MUST reject tool definition |
765| Object type | `"type": "object"` | Yes | Server MUST reject tool definition |
766| Null type | `"type": "null"` | Yes | Server MUST reject tool definition |
767| Nested property | Property inside object | Yes | Server MUST reject tool definition |
768768
769769### Server Validation Edge Cases
770770
771771#### Base64 Decoding
772772
773| Test Case | Header Value | Expected Behavior |
774| ------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------- |
775| Valid Base64 | `=?base64?SGVsbG8=?=` | Server decodes to `"Hello"` and validates |
776| Invalid Base64 padding | `=?base64?SGVsbG8?=` | Server MUST reject with 400 and error code `-32001`; Intermediary MAY reject with 400 status code |
773| Test Case | Header Value | Expected Behavior |
774| - | - | - |
775| Valid Base64 | `=?base64?SGVsbG8=?=` | Server decodes to `"Hello"` and validates |
776| Invalid Base64 padding | `=?base64?SGVsbG8?=` | Server MUST reject with 400 and error code `-32001`; Intermediary MAY reject with 400 status code |
777777| Invalid Base64 characters | `=?base64?SGVs!!!bG8=?=` | Server MUST reject with 400 and error code `-32001`; Intermediary MAY reject with 400 status code |
778| Missing prefix | `SGVsbG8=` | Server treats as literal value, not Base64 |
779| Missing suffix | `=?base64?SGVsbG8=` | Server treats as literal value, not Base64 |
780| Non-lowercase prefix | `=?BASE64?SGVsbG8=?=` | Server treats as literal value, not Base64 |
778| Missing prefix | `SGVsbG8=` | Server treats as literal value, not Base64 |
779| Missing suffix | `=?base64?SGVsbG8=` | Server treats as literal value, not Base64 |
780| Non-lowercase prefix | `=?BASE64?SGVsbG8=?=` | Server treats as literal value, not Base64 |
781781
782782#### Null and Missing Values
783783
784| Test Case | Scenario | Expected Behavior |
785| -------------------------------------- | --------------------------- | -------------------------- |
786| Parameter with x-mcp-header is null | `"region": null` | Client MUST omit header |
787| Parameter with x-mcp-header is missing | Parameter not in arguments | Client MUST omit header |
788| Optional parameter present | Optional parameter provided | Client MUST include header |
784| Test Case | Scenario | Expected Behavior |
785| - | - | - |
786| Parameter with x-mcp-header is null | `"region": null` | Client MUST omit header |
787| Parameter with x-mcp-header is missing | Parameter not in arguments | Client MUST omit header |
788| Optional parameter present | Optional parameter provided | Client MUST include header |
789789
790790#### Missing Custom Header with Value in Body
791791
792| Test Case | Header Present | Body Value | Expected Behavior |
793| -------------------------------------- | --------------------- | --------------------------- | ------------------------------------------------------------------------------------------------- |
794| 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 |
795| 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 |
792| Test Case | Header Present | Body Value | Expected Behavior |
793| - | - | - | - |
794| 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 |
795| 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 |
796796
797797## Reference Implementation
798798
No line in this hunk matches that.