Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion conformance/expected_failures.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions docs/building-clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 6 additions & 5 deletions lib/mcp/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 7 additions & 8 deletions test/mcp/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
26 changes: 26 additions & 0 deletions test/mcp/server/transports/streamable_http_transport_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 14 additions & 0 deletions test/mcp/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down