Skip to content

Commit dc25610

Browse files
committed
Support 2026-07-28 as the Latest Protocol Version
## Motivation and Context Clients that require MCP 2026-07-28, such as ChatGPT, reject connections because the SDK does not advertise that version: `Server#init` answers a 2026-07-28 request with 2025-11-25, the Streamable HTTP transport 400s an `MCP-Protocol-Version: 2026-07-28` header, and `server/discover` omits the version from `supportedVersions`. Making 2026-07-28 the latest entry in `SUPPORTED_STABLE_PROTOCOL_VERSIONS` fixes all three at once, since they all read that list. The version now serves both lifecycles of the SEP-2575 dual-era model: negotiable through the legacy `initialize` handshake, and simultaneously the version of the per-request modern lifecycle. Parts of the 2026-07-28 feature set remain unimplemented, such as the stateless modern lifecycle over the transports, and are being built separately. This change deliberately limits itself to the version bump, focusing first on eliminating the connection errors that 2026-07-28 clients hit. Known follow-up: the in-progress modern HTTP path routes requests to the sessionless modern lifecycle when the header version is absent from `SUPPORTED_STABLE_PROTOCOL_VERSIONS`, so that routing predicate needs rework on top of this change. Closes #474 ## How Has This Been Tested? New regression tests: `initialize` with `protocolVersion: "2026-07-28"` echoes it back (`server_test.rb`), a follow-up POST with `MCP-Protocol-Version: 2026-07-28` passes header validation (`streamable_http_transport_test.rb`), and `Configuration.new(protocol_version: "2026-07-28")` is accepted (`configuration_test.rb`). `bundle exec rake test` passes (1454 runs, 0 failures). The conformance server suite passes 30/30. The client `initialize` scenario is added to the expected-failures baseline: the conformance framework pins the client's requested version to 2025-11-25 and does not accept 2026-07-28 yet; the entry carries a TODO to remove it when the framework catches up. ## Breaking Changes Servers that do not pin `configuration.protocol_version` now answer version negotiation fallbacks and default client requests with 2026-07-28 instead of 2025-11-25. 2025-11-25 remains fully supported and negotiable; clients that request it are unaffected.
1 parent 75fffab commit dc25610

6 files changed

Lines changed: 60 additions & 16 deletions

File tree

‎conformance/expected_failures.yml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
server: []
2-
client: []
2+
client:
3+
# TODO: The conformance framework's client-initialization check pins the expected requested version to 2025-11-25,
4+
# while this SDK's latest protocol version is 2026-07-28. Remove once the @modelcontextprotocol/conformance package
5+
# accepts 2026-07-28.
6+
- initialize

‎docs/building-clients.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Call `MCP::Client#connect` to perform the MCP [initialization handshake](https:/
2222

2323
```ruby
2424
client.connect
25-
# => { "protocolVersion" => "2025-11-25", "capabilities" => {...}, "serverInfo" => {...} }
25+
# => { "protocolVersion" => "2026-07-28", "capabilities" => {...}, "serverInfo" => {...} }
2626

2727
client.connected? # => true
2828
client.server_info # => cached InitializeResult
@@ -99,7 +99,7 @@ After `connect` succeeds, the HTTP transport captures the `Mcp-Session-Id` heade
9999

100100
```ruby
101101
http_transport.session_id # => "abc123..."
102-
http_transport.protocol_version # => "2025-11-25"
102+
http_transport.protocol_version # => "2026-07-28"
103103
```
104104

105105
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.

‎lib/mcp/configuration.rb‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22

33
module MCP
44
class Configuration
5-
LATEST_STABLE_PROTOCOL_VERSION = "2025-11-25"
5+
LATEST_STABLE_PROTOCOL_VERSION = "2026-07-28"
66
SUPPORTED_STABLE_PROTOCOL_VERSIONS = [
7-
LATEST_STABLE_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05",
7+
LATEST_STABLE_PROTOCOL_VERSION, "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05",
88
].freeze
99
DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26"
1010

1111
# Protocol versions of the stateless "modern" lifecycle introduced by the MCP 2026-07-28 spec release (SEP-2575).
12-
# Modern versions are deliberately kept out of `SUPPORTED_STABLE_PROTOCOL_VERSIONS`: the modern lifecycle has
13-
# no `initialize` handshake, so these versions are never negotiated (each request carries its own version in `_meta`
14-
# and is validated against this list independently), and `protocol_version=` keeps rejecting them for the same reason.
12+
# 2026-07-28 serves both lifecycles of the dual-era model: it is negotiable through the legacy `initialize`
13+
# handshake (so it also appears in `SUPPORTED_STABLE_PROTOCOL_VERSIONS`), and it is the version of the modern
14+
# lifecycle, where each request carries its own version in `_meta` and is validated against this list
15+
# independently, with no handshake.
1516
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
1617
LATEST_MODERN_PROTOCOL_VERSION = "2026-07-28"
1718
SUPPORTED_MODERN_PROTOCOL_VERSIONS = [LATEST_MODERN_PROTOCOL_VERSION].freeze

‎test/mcp/configuration_test.rb‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class ConfigurationTest < ActiveSupport::TestCase
5454
Configuration.new(protocol_version: "DRAFT-2025-v3")
5555
end
5656

57-
assert_equal("protocol_version must be 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message)
57+
assert_equal("protocol_version must be 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message)
5858
end
5959

6060
test "raises ArgumentError when protocol_version is not a supported protocol version" do
@@ -63,7 +63,7 @@ class ConfigurationTest < ActiveSupport::TestCase
6363
custom_version = "2025-03-27"
6464
config.protocol_version = custom_version
6565
end
66-
assert_equal("protocol_version must be 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message)
66+
assert_equal("protocol_version must be 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26, or 2024-11-05", exception.message)
6767
end
6868

6969
test "exposes the SEP-2575 modern protocol versions" do
@@ -73,12 +73,11 @@ class ConfigurationTest < ActiveSupport::TestCase
7373
refute Configuration.modern_protocol_version?("2025-11-25")
7474
end
7575

76-
test "raises ArgumentError when setting a modern protocol version" do
77-
# Modern versions (SEP-2575) are never negotiated via `initialize`, so they are deliberately not settable as
78-
# the legacy fallback version.
79-
assert_raises(ArgumentError) do
80-
Configuration.new(protocol_version: Configuration::LATEST_MODERN_PROTOCOL_VERSION)
81-
end
76+
test "accepts 2026-07-28 as the protocol version" do
77+
# 2026-07-28 serves both lifecycles of the dual-era model (SEP-2575), so it is negotiable through
78+
# the legacy `initialize` handshake and settable as the fallback version.
79+
config = Configuration.new(protocol_version: Configuration::LATEST_MODERN_PROTOCOL_VERSION)
80+
assert_equal "2026-07-28", config.protocol_version
8281
end
8382

8483
test "raises ArgumentError when protocol_version is not a boolean value" do

‎test/mcp/server/transports/streamable_http_transport_test.rb‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,6 +1993,32 @@ def string
19931993
assert_equal 200, response[0]
19941994
end
19951995

1996+
test "POST request with the 2026-07-28 MCP-Protocol-Version header succeeds" do
1997+
init_request = create_rack_request(
1998+
"POST",
1999+
"/",
2000+
{ "CONTENT_TYPE" => "application/json" },
2001+
{ jsonrpc: "2.0", method: "initialize", id: "init", params: initialize_params(protocolVersion: "2026-07-28") }.to_json,
2002+
)
2003+
init_response = @transport.handle_request(init_request)
2004+
assert_equal "2026-07-28", JSON.parse(init_response[2][0])["result"]["protocolVersion"]
2005+
session_id = init_response[1]["mcp-session-id"]
2006+
2007+
request = create_rack_request(
2008+
"POST",
2009+
"/",
2010+
{
2011+
"CONTENT_TYPE" => "application/json",
2012+
"HTTP_MCP_SESSION_ID" => session_id,
2013+
"HTTP_MCP_PROTOCOL_VERSION" => "2026-07-28",
2014+
},
2015+
{ jsonrpc: "2.0", method: "tools/list", id: "list" }.to_json,
2016+
)
2017+
2018+
response = @transport.handle_request(request)
2019+
assert_equal 200, response[0]
2020+
end
2021+
19962022
test "POST request without MCP-Protocol-Version header succeeds" do
19972023
init_request = create_rack_request(
19982024
"POST",

‎test/mcp/server_test.rb‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,6 +2036,20 @@ def read_resource_request(uri)
20362036
assert_equal Configuration::LATEST_STABLE_PROTOCOL_VERSION, response[:result][:protocolVersion]
20372037
end
20382038

2039+
test "server negotiates 2026-07-28 when the client requests it via initialize" do
2040+
server = Server.new(name: "test_server")
2041+
2042+
request = {
2043+
jsonrpc: "2.0",
2044+
method: "initialize",
2045+
id: 1,
2046+
params: initialize_params(protocolVersion: "2026-07-28"),
2047+
}
2048+
2049+
response = server.handle(request)
2050+
assert_equal "2026-07-28", response[:result][:protocolVersion]
2051+
end
2052+
20392053
test "server removes description and icons from server_info when negotiating to 2025-06-18" do
20402054
server = Server.new(
20412055
name: "test_server",

0 commit comments

Comments
 (0)