Skip to content

mcp: emit UnsupportedProtocolVersionError for stateful servers rejecting SEP-2575 requests - #1111

Open
jhrozek wants to merge 1 commit into
modelcontextprotocol:mainfrom
jhrozek:unsup-proto-version-error
Open

mcp: emit UnsupportedProtocolVersionError for stateful servers rejecting SEP-2575 requests#1111
jhrozek wants to merge 1 commit into
modelcontextprotocol:mainfrom
jhrozek:unsup-proto-version-error

Conversation

@jhrozek

@jhrozek jhrozek commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

A stateful (Stateless == false) Streamable HTTP server rejected requests carrying _meta."io.modelcontextprotocol/protocolVersion" for MCP 2026-07-28 with a plain-text http.Error body, instead of the JSON-RPC error the draft spec requires. Per the draft's Protocol Version Header section, a server that won't serve a known-but-unsupported version MUST respond 400 Bad Request with an UnsupportedProtocolVersionError listing its supported versions — this is exactly that case (a stateful server that hasn't opted into Stateless).

Two things made this clearly a bug rather than a design choice:

  • The very next branch in the same function already calls writeJSONRPCError with CodeHeaderMismatch for an adjacent condition — this branch used http.Error instead, inconsistently.

  • The body text ("set StreamableHTTPOptions.Stateless = true") is a Go-API hint for whoever wrote the server, not something a remote client can act on.

  • Reuse the existing CodeUnsupportedProtocolVersion (-32022) and UnsupportedProtocolVersionData{Supported, Requested} shape already used by the discover/initialize paths, so Client.Connect's existing renegotiation logic can recognize this response and fall back cleanly, instead of hitting an unrecognized text/plain body.

  • Supported lists only the server's legacy-era versions (excludes 2026-07-28), since that's the whole point of the rejection.

  • The developer-facing hint moves to a server-side log line instead of the wire body.

  • server/discover stays exempt, as before — it's how a client is meant to learn a stateful server's restricted version set in the first place.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./mcp/... (full package)
  • Updated TestStreamableStateful_RejectsNewProtocol to assert the JSON-RPC error shape (code -32022, Requested, Supported excluding 2026-07-28) instead of the old text-contains-"stateless" check
  • TestStreamableStateless_AcceptsNewProtocol, TestStreamableClientUnsupportedVersionFallback, TestStreamableStateful_AcceptsDiscover all still pass unmodified

🤖 Generated with Claude Code

…ing SEP-2575 requests

A stateful (Stateless == false) Streamable HTTP server rejected requests
carrying `_meta."io.modelcontextprotocol/protocolVersion"` >= 2026-07-28
with a plain-text http.Error body, instead of the JSON-RPC
UnsupportedProtocolVersionError the draft spec requires for a known
version the server has chosen not to support. The body also leaked a
Go-API hint ("set StreamableHTTPOptions.Stateless = true") meant for the
server author, not the client.

Reuse the existing CodeUnsupportedProtocolVersion (-32022) and
UnsupportedProtocolVersionData shape already used by the discover and
initialize paths, listing only the server's legacy-era supported
versions. This lets Client.Connect's existing renegotiation logic
recognize and handle the rejection instead of falling back on an
unrecognized text/plain body. The developer hint moves to a server-side
log line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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