Repository navigation
Clarify behavior when initialize.params.protocolVersion conflicts with MCP-Protocol-Version #2721
Description
What's broken?
The spec is ambiguous or self-contradictory
Where in the spec or docs?
Version negotiation, Streamable HTTP protocol version header
What should happen?
The spec should define the expected behavior when an HTTP initialize request contains both:
MCP-Protocol-Version: 2025-03-26
and:
{
"jsonrpc": "2.0",
"id": "init-1",
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}The expected behavior should be explicit. At the moment, it is unclear whether this request is valid, invalid, or valid with one of the two version fields treated as authoritative.
What actually happens?
The current Streamable HTTP specification defines protocol version negotiation in the JSON-RPC initialize request body, and separately defines the MCP-Protocol-Version HTTP header. It does not appear to say what a server should do when both are present on the initial initialize request and they disagree.
This creates an ambiguity across protocol layers:
- MCP request-handling logic may negotiate based on
initialize.params.protocolVersion. - HTTP middleware, gateways, observability tools, or security filters may route or validate based on
MCP-Protocol-Version. - If the two values disagree and the spec does not define precedence or rejection behavior, different components can make decisions based on different protocol versions.
In cross-SDK testing, we sent initial initialize requests where initialize.params.protocolVersion and MCP-Protocol-Version disagreed in both directions:
- Body
2025-11-25, header2025-03-26 - Body
2025-03-26, header2025-11-25
Across the tested official SDK server configurations, none rejected the initial mismatch based on body/header inconsistency. All returned HTTP 200 and completed initialization.
Observed behavior for the initial initialize request:
| SDK | Tested HTTP profile(s) | Stable release | main snapshot (pinned 2026-05-11) |
Observed result |
|---|---|---|---|---|
| TypeScript | stateful, stateless | v1.29.0 (e12cbd70) |
2c0c481c |
Accepted; negotiated version followed the JSON-RPC body |
| C# | stateful, stateless | v1.3.0 (2ce15f4c) |
3118fcd6 |
Accepted; negotiated version followed the JSON-RPC body |
| Go | stateful | v1.6.0 (f5f20154) |
9f5e89fe |
Accepted; negotiated version followed the JSON-RPC body |
| Go | declared stateless profile | v1.6.0 (f5f20154) |
9f5e89fe |
Accepted; issued a session ID in this test setup, so it was not counted as clean stateless evidence |
| Java | stateful | v1.1.2 (e9e1a2f3) |
87e2c7d4 |
Accepted; negotiated version followed the JSON-RPC body |
| Kotlin | stateful | 0.12.0 (c339d8cb) |
8a814292 |
Accepted; negotiated version followed the JSON-RPC body |
| Python | stateful, stateless | v1.27.1 (77431ebe) |
161834d4 |
Accepted; negotiated version followed the JSON-RPC body |
| Ruby | stateful | v0.15.0 (ebc65696) |
9090cb6e |
Accepted; negotiated version followed the JSON-RPC body |
| PHP | stateful | v0.5.0 (fb2c8c2e) |
06917f6a |
Accepted; server returned its preferred version (2025-06-18) regardless of either input value |
| Rust | stateful | rmcp-v1.6.0 (014fb2e6) |
321ab14f |
Accepted; server returned its preferred version (2024-11-05) regardless of either input value |
As of May 11, 2026, the same pattern was observed in both stable release-version SDK pins and recent main-branch snapshots used for comparison.
Follow-up responses generally used the server's negotiated version in MCP-Protocol-Version, not the mismatching initial header value. This suggests that implementations are either ignoring the header during initialization or applying server-specific negotiation rules without checking consistency.
Anything else?
This is not intended as a report against any single SDK. The same ambiguity showed up across multiple official SDK server implementations, suggesting that the spec text is underspecified rather than a single SDK bug.
Related prior issues and work:
- When using HTTP, Version Negotiation is not communicated for subsequent requests without session id #478 — raised the need to communicate the negotiated MCP protocol version on subsequent HTTP requests.
- [v2025-06-18] MCP-Protocol-Version header assumes incorrect version #854 — discussed fallback behavior when
MCP-Protocol-Versionis missing. - Add a special MCP-Version header on http transport can improve the MCP protocol detection #884 — proposed a separate MCP version header for protocol detection by middleware.
- Clarify "invalid" vs "unsupported" protocol versions in transport spec #1943 — discussed clarifying invalid vs unsupported protocol versions in the transport spec.
- SEP-2575: Make MCP Stateless #2575 — SEP-2575 addresses a related future-state problem by requiring the HTTP header to match the per-request
_metaprotocol version.
Related conformance work:
- [Tiering] Add conformance tests for Version Negotiation conformance#102 and feat: add version negotiation conformance tests (closes #102) conformance#224 add current-spec version negotiation tests, but do not appear to cover body/header mismatch on
initialize. - feat: conformance scenario for MCP-Protocol-Version header 400 conformance#241 covers invalid or unsupported
MCP-Protocol-Versionvalues, but not a supported header value that disagrees with the body. - Add SEP-2575 conformance tests conformance#266 and feat: add server conformance tests for SEP-2575 conformance#271 cover SEP-2575 behavior, including header/_meta consistency for the future stateless protocol, but not the current
initialize.params.protocolVersionbody/header interaction.
This issue is narrower than SEP-2575: it asks whether the current 2025-11-25 spec should clarify what happens when the initial initialize request carries two different protocol-version signals.
Activity