Skip to content

fix(server): bound resumability version gates to supported versions, pin the unsupported-version rejection format#2280

Merged
felixweinberger merged 3 commits into
mainfrom
fweinberger/bound-version-gates
Jun 12, 2026
Merged

fix(server): bound resumability version gates to supported versions, pin the unsupported-version rejection format#2280
felixweinberger merged 3 commits into
mainfrom
fweinberger/bound-version-gates

Conversation

@felixweinberger

@felixweinberger felixweinberger commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Bounds the two open-ended protocol-version comparisons gating SSE resumability behavior in the Streamable HTTP server transport, and pins the -32000 "Unsupported protocol version" rejection wire format.

Motivation and Context

The priming-event gate and the closeSSEStream callback gate both used open-ended lexicographic checks (protocolVersion >= '2025-11-25'). The version they compare can come from an initialize request body, which — unlike the MCP-Protocol-Version header — is not validated against the supported-versions list, so an unknown future version string (e.g. a draft revision a client speculatively claims) silently enabled behavior reserved for versions the transport actually supports. The checks now also require membership in the transport's supported protocol versions, mirroring the already-set-closed header path.

The rejection shape is load-bearing interop surface: the go-sdk client substring-matches Unsupported protocol version on non-2xx response bodies to drive its protocol-version fallback (streamableClientConn.checkResponse in mcp/streamable.go; its structured JSON-RPC parse path rejects this server's id: null error body, so the substring is the operative signal). This PR pins HTTP 400 + that substring from the response bytes, and asserts the full message shape against a string derived from SUPPORTED_PROTOCOL_VERSIONS so the tests survive future additions to the supported-versions list.

How Has This Been Tested?

  • New future-version tests: '2099-01-01' and '2099-12-31' initialize bodies through both gates (no priming event, no early-close callbacks). Far-future sentinels deliberately avoid the next planned revision (2026-07-28) so the cases stay "unknown" when it gains support.
  • A 5-version × 2-gate matrix covering every currently supported protocol version. Red-green verified: with the source change reverted, exactly the 4 future-version tests fail and all 10 matrix cases plus both wire tests pass against the unmodified baseline — the expectations encode current behavior exactly, and the only behavioral delta is for unknown future versions.
  • Full server + core package suites green; conformance suite green against the pinned baseline.

Breaking Changes

None for any currently supported protocol version (byte-identical behavior, see matrix above). Clients claiming an unknown future protocol version in the initialize body are now treated like clients without empty-SSE-data support: no priming event, no early-close callbacks. Documented in docs/migration.md, with a patch changeset.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also documents the existing 404/-32001 "Session not found" response in the migration guide: that code is an SDK convention (not spec-assigned), flagged for re-derivation as 2026-07-28 error handling is adopted — clients should key off the HTTP 404 status rather than the -32001 code.

Note: this server emits -32000 for the unsupported-version rejection while the go-sdk and the conformance mocks use -32004 — a cross-SDK inconsistency worth aligning separately.

@felixweinberger felixweinberger requested a review from a team as a code owner June 12, 2026 13:50
@changeset-bot

changeset-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 425f79f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@modelcontextprotocol/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@2280

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/codemod@2280

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@2280

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server-legacy@2280

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@2280

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/fastify@2280

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@2280

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@2280

commit: 425f79f

@felixweinberger felixweinberger changed the title fix(server): bound resumability version gates to supported versions, pin the unsupported-version rejection format v2 verification rig: version-gate bounding, behavior pins, API-report gate, draft leak net + 2026-07-28 e2e axis, anchor repin, wire-safety nets Jun 12, 2026
@felixweinberger felixweinberger force-pushed the fweinberger/bound-version-gates branch 2 times, most recently from 4f6b348 to ce5bdbb Compare June 12, 2026 14:05
@felixweinberger felixweinberger changed the title v2 verification rig: version-gate bounding, behavior pins, API-report gate, draft leak net + 2026-07-28 e2e axis, anchor repin, wire-safety nets fix(server): bound resumability version gates to supported versions, pin the unsupported-version rejection format Jun 12, 2026
@felixweinberger felixweinberger force-pushed the fweinberger/bound-version-gates branch from ce5bdbb to 6d78425 Compare June 12, 2026 14:21
The Streamable HTTP server transport enabled priming events and
closeSSEStream callbacks via an open-ended >= '2025-11-25' comparison,
so an unknown future protocol version string in an initialize request
body (which is not header-validated) silently enabled that behavior.
The version must now also be one of the transport's supported protocol
versions. Behavior for all currently supported versions is unchanged.
The go-sdk client substring-matches 'Unsupported protocol version' on
non-2xx response bodies to detect protocol-version fallback (its
structured parse path rejects the id:null error body), so the HTTP 400
status and that literal substring are load-bearing interop surface.
Assert them from the response bytes; assert the full message shape
against a string derived from SUPPORTED_PROTOCOL_VERSIONS so the tests
survive additions to the supported-versions list.
Migration guide notes: unknown future protocol versions in an
initialize body no longer enable resumability behavior, and the
404/-32001 'Session not found' response on session-ID mismatch is an
SDK convention that may be re-derived for the 2026 protocol revision -
clients should key off the HTTP status, not the code. Changeset for
the server package included.
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.

2 participants