Skip to content

MCP 2026-07-28 (2026-09-21)

Latest

Choose a tag to compare

@simonx1 simonx1 released this 21 Sep 10:40
· 13 commits to main since this release
ab56f31

Full support for the MCP 2026-07-28 protocol revision. The revision makes the protocol stateless: no initialize handshake, no session, no server-to-client request channel. The client probes a server with server/discover, carries its identity in each request's _meta, and answers what the server asks for through the result.

Most existing code keeps working unchanged. The client detects which revision a server speaks and adapts; a 2025-11-25 or earlier server is the same conversation it was in 2.1.0. The major version marks the size of the new surface and the behaviour changes below — read them before upgrading, particularly the OAuth ones.

What is new

Feature Entry point
Stateless protocol, era detection server.modern?, protocol_era, protocol: :auto/:modern/:legacy, discover_timeout:
Per-request metadata MCPClient::Client.new(request_meta:)
Streamable HTTP modern mode no session id, no GET stream, one broken-stream re-issue
Custom headers from tool parameters x-mcp-header → Mcp-Param-*, MCPClient::HeaderParams
Multi round-trip requests resultType: "input_required", InputRequiredError
Subscriptions client.listen(notifications:)
Cacheable results ttlMs / cacheScope, server.cache_info
Tasks extension extensions: ['io.modelcontextprotocol/tasks'], call_tool_as_task
Authorization RFC 9207 iss validation, issuer-bound tokens and clients
JSON Schema 2020-12 dialects, local $ref, composition bounds
Deprecations MCPClient::Deprecations registry and notices

Three new examples run against a Flask server that speaks the new revision (examples/mcp_2026_07_28_server.py); every example is exercised by examples/run_all_examples.sh.

Behaviour changes when upgrading from 2.1.0

  • Ruby 3.3 or newer is required.
  • Every HTTP and stdio connection begins with a server/discover probe. A legacy server rejects it and the client falls back to initialize (one extra round trip on first connect). Skip the probe with protocol: :legacy. On HTTP only an answered rejection falls back: a timeout, a 5xx or a dropped connection is raised, and discover_timeout: bounds that failure rather than the fallback. Stdio also falls back on a probe timeout. transport: :sse with protocol: :modern raises ArgumentError.
  • On a modern session, a request whose response stream breaks is re-issued once, tools/call included, as the revision requires (the closed stream is the server's cancellation signal). Legacy sessions and every other recovery path keep the 2.1.0 rule: non-idempotent requests are never re-sent.
  • Tool definitions with an invalid x-mcp-header annotation are excluded from list_tools with a warning instead of failing at call time.
  • structuredContent is validated against the tool's outputSchema when one is declared; an unsupported JSON Schema dialect is an error rather than silently skipped.
  • cacheScope: "private" results are bound to the Authorization header they went out with and never served to another authorization context.
  • A redirect URI that is neither loopback HTTP nor HTTPS is refused (OAuthProvider#redirect_uri= raises ArgumentError; private-use schemes are fine).
  • OAuth credentials and tokens are bound to the authorization server that issued them. Records persisted by an earlier version carry no issuer: a token is bound on first read when the server's metadata is already cached and retired otherwise, and a dynamic registration whose server cannot be established is retired and re-registered. Some users will authorize again once. Pre-registered credentials without issuer: are refused with ConnectionError until they name their authorization server.
  • Roots, Sampling, Logging, the HTTP+SSE transport and OAuth Dynamic Client Registration log a deprecation notice once per feature per process. They keep working; MCPClient::Deprecations.enabled = false silences the notices.

Also in this release

  • Test suite consolidated from 296 files and 5550 examples to 103 files and 4398 examples with the same line-and-branch coverage.
  • Fixed during release review: two overlapping -32020 retries could go out under each other's tool definition; the lock serializing a resource's OAuth state could be replaced while held (WeakMap → WeakKeyMap); require 'cgi' warned on Ruby 4.0; YARD builds without warnings; OAUTH.md ships in the gem.

Full details, with the reasoning behind each change, are in CHANGELOG.md.