Skip to content

MCP 2026-07-28 (2/11): stateless protocol on stdio — server/discover, per-request _meta, legacy fallback - #225

Open
simonx1 wants to merge 4 commits into
mcp-2026/protocol-foundationsfrom
mcp-2026/stateless-stdio
Open

MCP 2026-07-28 (2/11): stateless protocol on stdio — server/discover, per-request _meta, legacy fallback#225
simonx1 wants to merge 4 commits into
mcp-2026/protocol-foundationsfrom
mcp-2026/stateless-stdio

Conversation

@simonx1

@simonx1 simonx1 commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

Second PR of the MCP 2026-07-28 series (stacked on #224). Implements the stateless, handshake-free protocol for the stdio transport and the shared per-request _meta machinery every transport will use.

  • server/discover probe + era detection (basic/transports/stdio "Backward Compatibility", basic/versioning) — before any other request the client sends server/discover with its preferred modern version. DiscoverResult ⇒ modern (version chosen from supportedVersions, capabilities / instructions / _meta.serverInfo recorded, no initialize ever sent). UnsupportedProtocolVersionError ⇒ still modern: retry the probe with an advertised version, never fall back. Any other error or timeout ⇒ legacy: run initialize exactly as before. Era is cached per process. protocol: :auto | :modern | :legacy and discover_timeout: on stdio_config/ServerStdio.
  • Per-request _meta (basic/index "_meta") — protocolVersion, clientInfo (unless send_client_info = false), clientCapabilities (with extensions via declare_extension) on every modern request. Host _meta (progressToken, OTel traceparent/tracestate/baggage) is preserved; reserved keys cannot be overridden. Client.new(request_meta:) merges defaults per request. Legacy traffic is unchanged.
  • Inline version negotiation — a modern UnsupportedProtocolVersionError on any request switches to a mutually supported version and re-sends once with a new id.
  • Removed methods (changelog major Enhanced SSE Connection Management #5) — on modern servers pingserver/discover (answered from the fresh probe), log_level=_meta["io.modelcontextprotocol/logLevel"] (no logging/setLevel), and notifications/roots/list_changed is not sent (modern roots capability is {}).
  • Refactor: every stdio method now goes through rpc_request, so meta injection, typed errors and version retry apply uniformly.

HTTP transports keep their legacy behaviour in this PR; the Streamable HTTP modern mode is PR 3.

Test plan

  • bundle exec rspec — 1747 examples, 0 failures (43 new in stateless_stdio_2026_spec.rb)
  • bundle exec rubocop — clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01MoErzDypnq7hhuFBtueML7

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@simonx1
simonx1 force-pushed the mcp-2026/stateless-stdio branch from 7065c9f to cb0f7d6 Compare September 1, 2026 18:40
simonx1 and others added 3 commits September 1, 2026 20:59
Probe with server/discover before any other request. A DiscoverResult or
a recognized modern error (UnsupportedProtocolVersionError, retried with an
advertised version) marks the server modern; any other error or a timeout
falls back to the initialize handshake (protocol: :auto, the default).
protocol: :modern refuses to fall back, :legacy skips the probe.

Modern requests carry protocolVersion, clientInfo and clientCapabilities
(plus declared extensions) in _meta; host _meta keys such as progressToken
and OpenTelemetry trace context are preserved and the reserved keys cannot
be overridden. Client.new(request_meta:) merges default metadata into every
request. An inline UnsupportedProtocolVersionError switches version and
re-sends once.

Removed-method mapping for modern servers: ping -> server/discover,
log_level= -> per-request _meta logLevel, no roots/list_changed.
Every stdio method now goes through rpc_request.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoErzDypnq7hhuFBtueML7
Codex + grok findings on the stateless stdio PR:
- Any recognized modern error (-32020/-32021, or -32022 with no usable
  version) or a malformed DiscoverResult now surfaces instead of falling
  back to initialize, and clears the tentative protocol version.
- notifications/roots/list_changed (and notifications/initialized) are
  dropped inside rpc_notify once a server is known to be modern, so a
  roots= call before the first request cannot leak the removed method.
- Every server/discover result is applied, not only the probe's.
- The probe waits the full read_timeout by default: a slow-starting modern
  server must not be misclassified as legacy.
- ensure_initialized is serialized on a reentrant lock so concurrent
  first requests spawn and probe once.
- Modern requests declare no roots/sampling/elicitation capability until
  the multi round-trip pattern exists; an unfulfillable input_required
  result raises InputRequiredError rather than posing as a tool result.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoErzDypnq7hhuFBtueML7
…swer; drop server requests

Third review round (codex + grok) on the stateless stdio PR:
- A well-formed UnsupportedProtocolVersionError settles the server as
  modern: a failure of the retried probe surfaces instead of falling back.
- A probe answered with something that is not a DiscoverResult (a scalar,
  null, or an object without supportedVersions) is a legacy answer and
  falls back to initialize (refused under protocol: :modern).
- Every server/discover answer, including the ping heartbeat, is validated
  and applied.
- Inline version renegotiation compares against the version the rejected
  request was actually sent with, so concurrent requests cannot make each
  other give up.
- On a modern session server-initiated requests (ping, roots/list,
  elicitation, sampling) are dropped instead of answered: the client MUST
  NOT write JSON-RPC responses on 2026-07-28 stdio.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoErzDypnq7hhuFBtueML7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant