Skip to content

mcp: reject legacy-format requests on 2026-only servers - #1345

Open
Enigmage wants to merge 1 commit into
modelcontextprotocol:mainfrom
Enigmage:fix-2026-only-server-rejects-legacy
Open

Enigmage wants to merge 1 commit into
modelcontextprotocol:mainfrom
Enigmage:fix-2026-only-server-rejects-legacy

Conversation

@Enigmage

@Enigmage Enigmage commented Oct 6, 2026 •

Copy link
Copy Markdown

Fixes #1313.

Note: this PR does not change initialize behavior on a 2026-only server, it still succeeds with protocolVersion20251125 per the existing "server with no handshake version" test. We can discuss if we want to reject initialize with -32022 as well.

A server configured with SupportedProtocolVersions containing only versions >= 2026-07-28 should reject legacy clients that use the initialize/initialized handshake protocol with a clear CodeUnsupportedProtocolVersion error, rather than silently serving them in a degraded state.

What was broken

Two paths allowed legacy requests to slip through on a 2026-only server:

  1. Stateful, uninitialized: handle() reached the "method invalid during initialization" error path for any non-initialize call, but returned a generic error instead of CodeUnsupportedProtocolVersion. The caller had no signal that the server will never support the legacy protocol.

  2. Stateless: serveStateless() calls ephemeralConnectOpts(), which synthesizes fake InitializeParams for every request — making every session appear already initialized. This caused all legacy stateless calls to dispatch successfully and produce results, with no rejection at all.

Fix

server.go — stateful uninitialized path:

In the handle() branch for uninitialized legacy calls, detect when the server has no legacy-supporting version configured and return CodeUnsupportedProtocolVersion with an UnsupportedProtocolVersionData payload before falling through to the generic error.

streamable.go — stateless path:

In serveStateless(), after ephemeralConnectOpts(), check whether the server has any legacy-supporting version configured. If not, write a CodeUnsupportedProtocolVersion error response and return.

Behavior

  • 2026-only server + legacy initialize: allowed — negotiatedVersion returns protocolVersion20251125 as a courtesy per existing semantics in shared.go.
  • 2026-only server + legacy non-initialize call (stateful, uninitialized): rejected with CodeUnsupportedProtocolVersion.
  • 2026-only server + any legacy call (stateless): rejected with CodeUnsupportedProtocolVersion and HTTP 400.
  • Servers with any legacy version in SupportedProtocolVersions are unaffected.

Tests

  • TestServerSupportedProtocolVersions_InitializeLegacy: verifies initialize succeeds on a 2026-only server and returns protocolVersion20251125.
  • TestServerSupportedProtocolVersions_LegacyDispatch: verifies a legacy tools/list call without _meta is rejected with CodeUnsupportedProtocolVersion on a 2026-only stateful server.
  • TestStreamableStateless_RejectsLegacyOn2026OnlyServer: verifies a legacy POST to a stateless 2026-only server returns HTTP 400 with CodeUnsupportedProtocolVersion.

@Enigmage
Enigmage force-pushed the fix-2026-only-server-rejects-legacy branch from 597f2c0 to c47e426 Compare October 6, 2026 14:22
@Enigmage
Enigmage marked this pull request as ready for review October 6, 2026 14:32
@Enigmage
Enigmage marked this pull request as draft October 6, 2026 14:56
@Enigmage
Enigmage force-pushed the fix-2026-only-server-rejects-legacy branch from c47e426 to 79d2351 Compare October 6, 2026 14:59
@Enigmage
Enigmage marked this pull request as ready for review October 6, 2026 15:01
A server configured with SupportedProtocolVersions containing only
versions >= 2026-07-28 should not silently serve legacy clients that
use the initialize/initialized handshake protocol.

Previously, such clients would receive a successful (if confusing)
response: stateful servers would accept any post-initialize method call
because ephemeral session setup bypassed the version check, and
stateless servers would always appear initialized due to ephemeral
connect opts synthesizing fake InitializeParams.

Fix the stateful path by detecting a 2026-only server configuration
in the uninitialized branch of handle() and returning
CodeUnsupportedProtocolVersion before falling through to the generic
"method invalid during initialization" error.

Fix the stateless path by checking the legacy flag in serveStateless()
immediately after ephemeralConnectOpts(), before any method dispatch.

Both paths include an UnsupportedProtocolVersionData payload listing
the server's supported versions so clients can adapt.

Fixes modelcontextprotocol#1313
@Enigmage
Enigmage force-pushed the fix-2026-only-server-rejects-legacy branch from 914ee33 to d5a828d Compare October 6, 2026 15:09

This branch has not been deployed

No deployments
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.

mcp: 2026-only server silently serves legacy clients instead of returning -32022

1 participant