From 711f0640882e846e8a7b111899a47bab13df812e Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sat, 16 May 2026 21:37:34 +0900 Subject: [PATCH] Document `initialize` body `protocolVersion` is authoritative on Streamable HTTP ## Motivation and Context When an `initialize` POST carries an `MCP-Protocol-Version` HTTP header that disagrees with `params.protocolVersion` in the JSON-RPC body, the Streamable HTTP server accepts the request and negotiates using the body value. The behavior was already encoded in `handle_post` (the header check is skipped on `initialize`) but it was not stated in the code and there was no regression test for the specific mismatch case. MCP 2025-06-18 / 2025-11-25 only requires `MCP-Protocol-Version` on requests subsequent to `initialize`, and the spec does not require the header and body to agree on the initial `initialize`. All official MCP SDKs (TypeScript, Python, Rust, Java, Go, PHP) accept the mismatch and treat the JSON-RPC body as authoritative. ## How Has This Been Tested? Streamable HTTP tests: two new regression tests confirm that on an `initialize` POST with a supported but mismatched `MCP-Protocol-Version` header, the server returns HTTP 200 and the response's `result.protocolVersion` matches the JSON-RPC body value, in both mismatch directions (header older / body newer, and header newer / body older). ## Breaking Changes None. The behavior is unchanged: `initialize` requests with a mismatched `MCP-Protocol-Version` header are still accepted, and the JSON-RPC body `params.protocolVersion` continues to drive negotiation. This commit only adds an explanatory comment and regression tests. Closes #351. --- .../transports/streamable_http_transport.rb | 3 ++ .../streamable_http_transport_test.rb | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/lib/mcp/server/transports/streamable_http_transport.rb b/lib/mcp/server/transports/streamable_http_transport.rb index 9258ec19..77d97cc9 100644 --- a/lib/mcp/server/transports/streamable_http_transport.rb +++ b/lib/mcp/server/transports/streamable_http_transport.rb @@ -342,6 +342,9 @@ def handle_post(request) body = parse_request_body(body_string) return body if parse_error_tuple?(body) + # The `MCP-Protocol-Version` header is only meaningful after negotiation, so on `initialize` + # the JSON-RPC body `params.protocolVersion` is authoritative and the header (if any) is ignored. + # This matches the TypeScript and Python SDKs. unless initialize_request?(body) return missing_session_id_response if !@stateless && !session_id diff --git a/test/mcp/server/transports/streamable_http_transport_test.rb b/test/mcp/server/transports/streamable_http_transport_test.rb index 9d743984..a753789b 100644 --- a/test/mcp/server/transports/streamable_http_transport_test.rb +++ b/test/mcp/server/transports/streamable_http_transport_test.rb @@ -1400,6 +1400,58 @@ def string assert_equal 200, response[0] end + test "POST initialize request negotiates body protocolVersion when header is an older supported version" do + older_version = "2025-06-18" + assert_includes Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS, older_version + refute_equal Configuration::LATEST_STABLE_PROTOCOL_VERSION, older_version + + request = create_rack_request( + "POST", + "/", + { + "CONTENT_TYPE" => "application/json", + "HTTP_MCP_PROTOCOL_VERSION" => older_version, + }, + { + jsonrpc: "2.0", + method: "initialize", + id: "init", + params: { protocolVersion: Configuration::LATEST_STABLE_PROTOCOL_VERSION }, + }.to_json, + ) + + response = @transport.handle_request(request) + assert_equal 200, response[0] + body = JSON.parse(response[2][0]) + assert_equal Configuration::LATEST_STABLE_PROTOCOL_VERSION, body["result"]["protocolVersion"] + end + + test "POST initialize request negotiates body protocolVersion when header is a newer supported version" do + older_version = "2025-06-18" + assert_includes Configuration::SUPPORTED_STABLE_PROTOCOL_VERSIONS, older_version + refute_equal Configuration::LATEST_STABLE_PROTOCOL_VERSION, older_version + + request = create_rack_request( + "POST", + "/", + { + "CONTENT_TYPE" => "application/json", + "HTTP_MCP_PROTOCOL_VERSION" => Configuration::LATEST_STABLE_PROTOCOL_VERSION, + }, + { + jsonrpc: "2.0", + method: "initialize", + id: "init", + params: { protocolVersion: older_version }, + }.to_json, + ) + + response = @transport.handle_request(request) + assert_equal 200, response[0] + body = JSON.parse(response[2][0]) + assert_equal older_version, body["result"]["protocolVersion"] + end + test "POST request with unsupported MCP-Protocol-Version returns 400" do init_request = create_rack_request( "POST",