Repository navigation
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/discoverprobe. A legacy server rejects it and the client falls back toinitialize(one extra round trip on first connect). Skip the probe withprotocol: :legacy. On HTTP only an answered rejection falls back: a timeout, a 5xx or a dropped connection is raised, anddiscover_timeout:bounds that failure rather than the fallback. Stdio also falls back on a probe timeout.transport: :ssewithprotocol: :modernraisesArgumentError. - On a modern session, a request whose response stream breaks is re-issued once,
tools/callincluded, 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-headerannotation are excluded fromlist_toolswith a warning instead of failing at call time. structuredContentis validated against the tool'soutputSchemawhen one is declared; an unsupported JSON Schema dialect is an error rather than silently skipped.cacheScope: "private"results are bound to theAuthorizationheader 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=raisesArgumentError; 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 withConnectionErroruntil 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 = falsesilences 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
-32020retries 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.mdships in the gem.
Full details, with the reasoning behind each change, are in CHANGELOG.md.