diff --git a/conformance/expected_failures.yml b/conformance/expected_failures.yml index 0bbbbfb4..371853de 100644 --- a/conformance/expected_failures.yml +++ b/conformance/expected_failures.yml @@ -1,2 +1,6 @@ server: [] -client: [] +client: + # TODO: The conformance framework's client-initialization check pins the expected requested version to 2025-11-25, + # while this SDK's latest protocol version is 2026-07-28. Remove once the @modelcontextprotocol/conformance package + # accepts 2026-07-28. + - initialize diff --git a/docs/building-clients.md b/docs/building-clients.md index 6b4c7d01..9dfaf0d8 100644 --- a/docs/building-clients.md +++ b/docs/building-clients.md @@ -22,7 +22,7 @@ Call `MCP::Client#connect` to perform the MCP [initialization handshake](https:/ ```ruby client.connect -# => { "protocolVersion" => "2025-11-25", "capabilities" => {...}, "serverInfo" => {...} } +# => { "protocolVersion" => "2026-07-28", "capabilities" => {...}, "serverInfo" => {...} } client.connected? # => true client.server_info # => cached InitializeResult @@ -99,7 +99,7 @@ After `connect` succeeds, the HTTP transport captures the `Mcp-Session-Id` heade ```ruby http_transport.session_id # => "abc123..." -http_transport.protocol_version # => "2025-11-25" +http_transport.protocol_version # => "2026-07-28" ``` If the server terminates the session, subsequent requests return HTTP 404 and the transport raises `MCP::Client::SessionExpiredError` (a subclass of `RequestHandlerError`). Session state is cleared automatically; callers should start a new session by calling `connect` again. diff --git a/lib/mcp/configuration.rb b/lib/mcp/configuration.rb index 98f25e92..ed6d430b 100644 --- a/lib/mcp/configuration.rb +++ b/lib/mcp/configuration.rb @@ -2,16 +2,17 @@ module MCP class Configuration - LATEST_STABLE_PROTOCOL_VERSION = "2025-11-25" + LATEST_STABLE_PROTOCOL_VERSION = "2026-07-28" SUPPORTED_STABLE_PROTOCOL_VERSIONS = [ - LATEST_STABLE_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", + LATEST_STABLE_PROTOCOL_VERSION, "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05", ].freeze DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26" # Protocol versions of the stateless "modern" lifecycle introduced by the MCP 2026-07-28 spec release (SEP-2575). - # Modern versions are deliberately kept out of `SUPPORTED_STABLE_PROTOCOL_VERSIONS`: the modern lifecycle has - # no `initialize` handshake, so these versions are never negotiated (each request carries its own version in `_meta` - # and is validated against this list independently), and `protocol_version=` keeps rejecting them for the same reason. + # 2026-07-28 serves both lifecycles of the dual-era model: it is negotiable through the legacy `initialize` + # handshake (so it also appears in `SUPPORTED_STABLE_PROTOCOL_VERSIONS`), and it is the version of the modern + # lifecycle, where each request carries its own version in `_meta` and is validated against this list + # independently, with no handshake. # https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575 LATEST_MODERN_PROTOCOL_VERSION = "2026-07-28" SUPPORTED_MODERN_PROTOCOL_VERSIONS = [LATEST_MODERN_PROTOCOL_VERSION].freeze diff --git a/test/mcp/configuration_test.rb b/test/mcp/configuration_test.rb index 732e5c25..86231f71 100644 --- a/test/mcp/configuration_test.rb +++ b/test/mcp/configuration_test.rb @@ -54,7 +54,7 @@ class ConfigurationTest < ActiveSupport::TestCase Configuration.new(protocol_version: "DRAFT-2025-v3") end - assert_equal("protocol_version must be 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message) + assert_equal("protocol_version must be 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message) end test "raises ArgumentError when protocol_version is not a supported protocol version" do @@ -63,7 +63,7 @@ class ConfigurationTest < ActiveSupport::TestCase custom_version = "2025-03-27" config.protocol_version = custom_version end - assert_equal("protocol_version must be 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message) + assert_equal("protocol_version must be 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message) end test "exposes the SEP-2575 modern protocol versions" do @@ -73,12 +73,11 @@ class ConfigurationTest < ActiveSupport::TestCase refute Configuration.modern_protocol_version?("2025-11-25") end - test "raises ArgumentError when setting a modern protocol version" do - # Modern versions (SEP-2575) are never negotiated via `initialize`, so they are deliberately not settable as - # the legacy fallback version. - assert_raises(ArgumentError) do - Configuration.new(protocol_version: Configuration::LATEST_MODERN_PROTOCOL_VERSION) - end + test "accepts 2026-07-28 as the protocol version" do + # 2026-07-28 serves both lifecycles of the dual-era model (SEP-2575), so it is negotiable through + # the legacy `initialize` handshake and settable as the fallback version. + config = Configuration.new(protocol_version: Configuration::LATEST_MODERN_PROTOCOL_VERSION) + assert_equal "2026-07-28", config.protocol_version end test "raises ArgumentError when protocol_version is not a boolean value" do diff --git a/test/mcp/server/transports/streamable_http_transport_test.rb b/test/mcp/server/transports/streamable_http_transport_test.rb index 099686e1..4912cebf 100644 --- a/test/mcp/server/transports/streamable_http_transport_test.rb +++ b/test/mcp/server/transports/streamable_http_transport_test.rb @@ -1993,6 +1993,32 @@ def string assert_equal 200, response[0] end + test "POST request with the 2026-07-28 MCP-Protocol-Version header succeeds" do + init_request = create_rack_request( + "POST", + "/", + { "CONTENT_TYPE" => "application/json" }, + { jsonrpc: "2.0", method: "initialize", id: "init", params: initialize_params(protocolVersion: "2026-07-28") }.to_json, + ) + init_response = @transport.handle_request(init_request) + assert_equal "2026-07-28", JSON.parse(init_response[2][0])["result"]["protocolVersion"] + session_id = init_response[1]["mcp-session-id"] + + request = create_rack_request( + "POST", + "/", + { + "CONTENT_TYPE" => "application/json", + "HTTP_MCP_SESSION_ID" => session_id, + "HTTP_MCP_PROTOCOL_VERSION" => "2026-07-28", + }, + { jsonrpc: "2.0", method: "tools/list", id: "list" }.to_json, + ) + + response = @transport.handle_request(request) + assert_equal 200, response[0] + end + test "POST request without MCP-Protocol-Version header succeeds" do init_request = create_rack_request( "POST", diff --git a/test/mcp/server_test.rb b/test/mcp/server_test.rb index c51c2109..4d9c5b05 100644 --- a/test/mcp/server_test.rb +++ b/test/mcp/server_test.rb @@ -2036,6 +2036,20 @@ def read_resource_request(uri) assert_equal Configuration::LATEST_STABLE_PROTOCOL_VERSION, response[:result][:protocolVersion] end + test "server negotiates 2026-07-28 when the client requests it via initialize" do + server = Server.new(name: "test_server") + + request = { + jsonrpc: "2.0", + method: "initialize", + id: 1, + params: initialize_params(protocolVersion: "2026-07-28"), + } + + response = server.handle(request) + assert_equal "2026-07-28", response[:result][:protocolVersion] + end + test "server removes description and icons from server_info when negotiating to 2025-06-18" do server = Server.new( name: "test_server",