Align modern envelope validation with the finalized spec - #491
Conversation
|
Adopting this SDK for a production Rails MCP server, so I read #489 and this PR side by side. They overlap in Both PRs make the same three changes independently
So whichever lands first, the other conflicts on those hunks. The two differ in the wire
if error.respond_to?(:error_code) && error.error_code
code = error.error_code
message = error.message # detailed message reaches the wire
else
code, message = case error_type
when :invalid_params then [ErrorCode::INVALID_PARAMS, "Invalid params"] # message replacedBoth yield That matters for one of this PR's own assertions — assert_includes response.dig(:error, :message), "io.modelcontextprotocol/clientCapabilities"This passes with #491's
Both are defensible readings of SEP-2575, but the merge order currently decides which one ships. If For context on why the wire shape matters downstream: our server has to stay dual-era for a while (Claude Code and other clients still speak the handshake revisions), and the documented HTTP detection is "attempt a modern request, then inspect the body of a Happy to test whichever lands against a real dual-era deployment and report back. |
## Motivation and Context Two deviations from the finalized 2026-07-28 specification in the SEP-2575 envelope validation, both introduced against the frozen SEP text and overtaken by post-final spec changes: 1. `clientInfo` was treated as required. Spec PR modelcontextprotocol/modelcontextprotocol#3002 made it optional (clients SHOULD include it unless configured not to), and the TypeScript and Python SDKs validate only the `protocolVersion` + `clientCapabilities` pair. A conformant client configured not to identify itself was rejected. 2. A missing or mistyped envelope answered `-32600` Invalid Request. The spec maps missing required envelope fields to `-32602` Invalid params, and both reference SDKs answer with `-32602` naming the offending keys. Fixing these also corrects the era classification to match the reference SDKs: a request claims the modern lifecycle when `_meta` carries `io.modelcontextprotocol/protocolVersion` (the TypeScript envelope claim and the Python `_has_modern_envelope` are both single-key checks), and a claimed-but-incomplete envelope is now validated and rejected with `-32602` naming the missing keys instead of silently flowing through the legacy path. Legacy `_meta` usage without the claim key (`progressToken`, trace context) is classified exactly as before. Unchanged on purpose: the era-lock violations keep their codes (`-32600` for a modern envelope on a legacy-locked session, `-32022` for `initialize` on a modern-locked one), matching the Python SDK. ## How Has This Been Tested? `test/mcp/request_envelope_test.rb` now covers: classification by the claim key alone, a claimed-but-incomplete envelope staying modern, parsing without the optional `clientInfo` (reader returns `nil`), `-32602` with the offending key names for a missing `clientCapabilities` and for mistyped required and optional fields. `test/mcp/server_test.rb` covers the dispatch-level behavior: a claimed but incomplete envelope answers `-32602` naming the missing key, an envelope without `clientInfo` is served with `server_context.client_info` reading `nil`, and a claim-less request on a modern-locked session answers `-32602` naming the required keys. The stdio and Streamable HTTP transport tests assert the new code on their envelope-requirement paths. ## Breaking Changes None for conforming clients. Requests that were already rejected change error code (`-32600` to `-32602`, HTTP status 400 unchanged), and requests carrying the `protocolVersion` claim key with an incomplete envelope are now rejected as the spec mandates instead of being served as legacy requests. Envelopes without `clientInfo`, previously rejected, now succeed.
4723a2e to
f39b1bf
Compare
|
@seunghan91 Thanks for the careful review and for comparing #489 and #491 side by side. The observations about the overlap, the wire behavior, and the dual-era implications were all very helpful. Resolution: #491 will merge first, and its validation behavior will become the canonical one ( For For the dual-era detection described above, that means a modern probe receiving HTTP 404 with Thanks as well for the offer to test against a real dual-era deployment. Feedback on the rebased #489 would be greatly appreciated. |
Motivation and Context
Two deviations from the finalized 2026-07-28 specification in the SEP-2575 envelope validation, both introduced against the frozen SEP text and overtaken by post-final spec changes:
clientInfowas treated as required. Spec PR feat(schema): add optional serverInfo response metadata and make clientInfo optional modelcontextprotocol#3002 made it optional (clients SHOULD include it unless configured not to), and the TypeScript and Python SDKs validate only theprotocolVersion+clientCapabilitiespair. A conformant client configured not to identify itself was rejected.A missing or mistyped envelope answered
-32600Invalid Request. The spec maps missing required envelope fields to-32602Invalid params, and both reference SDKs answer with-32602naming the offending keys.Fixing these also corrects the era classification to match the reference SDKs: a request claims the modern lifecycle when
_metacarriesio.modelcontextprotocol/protocolVersion(the TypeScript envelope claim and the Python_has_modern_envelopeare both single-key checks), and a claimed-but-incomplete envelope is now validated and rejected with-32602naming the missing keys instead of silently flowing through the legacy path. Legacy_metausage without the claim key (progressToken, trace context) is classified exactly as before.Unchanged on purpose: the era-lock violations keep their codes (
-32600for a modern envelope on a legacy-locked session,-32022forinitializeon a modern-locked one), matching the Python SDK.How Has This Been Tested?
test/mcp/request_envelope_test.rbnow covers: classification by the claim key alone, a claimed-but-incomplete envelope staying modern, parsing without the optionalclientInfo(reader returnsnil),-32602with the offending key names for a missingclientCapabilitiesand for mistyped required and optional fields.test/mcp/server_test.rbcovers the dispatch-level behavior: a claimed but incomplete envelope answers-32602naming the missing key, an envelope withoutclientInfois served withserver_context.client_inforeadingnil, and a claim-less request on a modern-locked session answers-32602naming the required keys. The stdio and Streamable HTTP transport tests assert the new code on their envelope-requirement paths.Breaking Changes
None for conforming clients. Requests that were already rejected change error code (
-32600to-32602, HTTP status 400 unchanged), and requests carrying theprotocolVersionclaim key with an incomplete envelope are now rejected as the spec mandates instead of being served as legacy requests. Envelopes withoutclientInfo, previously rejected, now succeed.Types of changes
Checklist