Add Stateless Lifecycle Foundations per SEP-2575 - #471
Conversation
## Motivation and Context SEP-2575 (modelcontextprotocol/modelcontextprotocol#2575, merged for the 2026-07-28 spec release) replaces the `initialize` handshake with a stateless "modern" lifecycle: every request carries its protocol version, client info, and client capabilities in reserved `_meta` keys, and the server validates each request independently. This change lays the shared vocabulary for that lifecycle without changing any behavior: - `MCP::Configuration` gains `LATEST_MODERN_PROTOCOL_VERSION` ("2026-07-28"), `SUPPORTED_MODERN_PROTOCOL_VERSIONS`, and `Configuration.modern_protocol_version?`. Modern versions are deliberately kept out of `SUPPORTED_STABLE_PROTOCOL_VERSIONS` so `initialize` can never negotiate them and `protocol_version=` keeps rejecting them. - `MCP::ErrorCodes` gains `HEADER_MISMATCH` (-32020), completing the spec's stateless lifecycle error code block alongside the existing -32021/-32022 constants. - New `MCP::RequestEnvelope` parses and validates the per-request `_meta` triple (`io.modelcontextprotocol/protocolVersion`, `clientInfo`, `clientCapabilities`, plus the optional deprecated `logLevel`). A request is classified as modern only when the full triple is present, matching the TypeScript SDK's `RequestMetaEnvelopeSchema` and the Python SDK's `_has_modern_envelope`, so partial or legacy `_meta` entries keep flowing through the legacy path untouched. - New typed errors `Server::UnsupportedProtocolVersionError` (-32022 with `data: { supported:, requested: }`) and `Server::MissingRequiredClientCapabilityError` (-32021 with `data: { requiredCapabilities: }`) reuse the established `RequestHandlerError` `error_code`/`error_data` plumbing (the -32042 URL elicitation precedent). Nothing dispatches through `RequestEnvelope` yet; the server-side modern handling, transport routing, and client support build on this in follow-up changes. Refs modelcontextprotocol#389. ## How Has This Been Tested? New `test/mcp/request_envelope_test.rb` covers the modern-triple classification (full triple, partial triple, string keys, legacy `progressToken`), parsed field access, the -32022 error data shape, and invalid-request errors for incomplete or mistyped triples. `test/mcp/configuration_test.rb` asserts the new constants, the predicate, and that modern versions stay rejected by `protocol_version=`. `test/mcp/server_test.rb` asserts both typed errors surface on the wire with their SEP-2575 codes and data through `Server#handle`. `test/mcp/error_codes_test.rb` pins -32020. ## Breaking Changes None. Constants and classes are additive; no request path changes behavior.
|
The spec doesn't seem to adopt the term "modern" to refer to this statetless mode. Can we use "stateless" instead of "modern"? |
|
Thanks, good point. The spec says "stateless" rather than "modern", so the term isn't spec-derived. I chose "modern" (paired with "legacy") to follow the reference SDKs, which treat this as a two-era ("dual-era") split:
There's also a naming collision to avoid. That said, this is the foundational PR for the stack, so I'd like to settle on the terminology here. Would you still prefer "stateless", given the overlap with the existing |
Motivation and Context
SEP-2575 (modelcontextprotocol/modelcontextprotocol#2575, merged for the 2026-07-28 spec release) replaces the
initializehandshake with a stateless "modern" lifecycle: every request carries its protocol version, client info, and client capabilities in reserved_metakeys, and the server validates each request independently. This change lays the shared vocabulary for that lifecycle without changing any behavior:MCP::ConfigurationgainsLATEST_MODERN_PROTOCOL_VERSION("2026-07-28"),SUPPORTED_MODERN_PROTOCOL_VERSIONS, andConfiguration.modern_protocol_version?. Modern versions are deliberately kept out ofSUPPORTED_STABLE_PROTOCOL_VERSIONSsoinitializecan never negotiate them andprotocol_version=keeps rejecting them.MCP::ErrorCodesgainsHEADER_MISMATCH(-32020), completing the spec's stateless lifecycle error code block alongside the existing -32021/-32022 constants.MCP::RequestEnvelopeparses and validates the per-request_metatriple (io.modelcontextprotocol/protocolVersion,clientInfo,clientCapabilities, plus the optional deprecatedlogLevel). A request is classified as modern only when the full triple is present, matching the TypeScript SDK'sRequestMetaEnvelopeSchemaand the Python SDK's_has_modern_envelope, so partial or legacy_metaentries keep flowing through the legacy path untouched.Server::UnsupportedProtocolVersionError(-32022 withdata: { supported:, requested: }) andServer::MissingRequiredClientCapabilityError(-32021 withdata: { requiredCapabilities: }) reuse the establishedRequestHandlerErrorerror_code/error_dataplumbing (the -32042 URL elicitation precedent).Nothing dispatches through
RequestEnvelopeyet; the server-side modern handling, transport routing, and client support build on this in follow-up changes.Refs #389.
How Has This Been Tested?
New
test/mcp/request_envelope_test.rbcovers the modern-triple classification (full triple, partial triple, string keys, legacyprogressToken), parsed field access, the -32022 error data shape, and invalid-request errors for incomplete or mistyped triples.test/mcp/configuration_test.rbasserts the new constants, the predicate, and that modern versions stay rejected byprotocol_version=.test/mcp/server_test.rbasserts both typed errors surface on the wire with their SEP-2575 codes and data throughServer#handle.test/mcp/error_codes_test.rbpins -32020.Breaking Changes
None. Constants and classes are additive; no request path changes behavior.
Types of changes
Checklist