Skip to content

Add option to restrict the MCP protocol versions a server serves - #1385

Merged
adwsingh merged 1 commit into
smithy-lang:mainfrom
psp65:mcp-stdio-handshake-only
Oct 13, 2026
Merged

adwsingh merged 1 commit into
smithy-lang:mainfrom
psp65:mcp-stdio-handshake-only

Conversation

@psp65

@psp65 psp65 commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

What behavior changes?

Adds opt-in McpEngine.Builder#protocolVersions(ProtocolVersion...) and StdioMcpServerBuilder#protocolVersions(ProtocolVersion...). When set, only the given registered versions are negotiated and advertised:

  • server/discover's supportedVersions and the supported list in -32022 errors name only the retained versions.
  • Requests that claim any other version get -32022.
  • initialize falls back to the newest retained handshake version.

Requests that don't identify a version use the engine's default. That applies to stdio sessions and to HTTP requests without a version header. The default now falls back to the initialize fallback when the default version (2025-03-26) is excluded.

Building fails if a version is not registered, or if no retained version supports initialize.

The default behavior is unchanged.

Why is this change needed?

Mitigates #1383. Until subscriptions/listen is implemented, a 2026-07-28 session cannot receive notifications/tools/list_changed. As a result, a server that relays list changes from proxied servers (dynamic tool discovery) stops working for clients that prefer 2026-07-28. Restricting such a server to the handshake versions makes those clients fall back to initialize, where list changes work.

How was this validated?

  • Unit tests:
    • McpArchitectureTest: restricted negotiation and advertisement; retaining an extension protocol; rejecting unregistered versions, an empty list, and a set with no initialize-capable version; excluding the default version.
    • StdioMcpServerTest: a stateless server/discover probe gets -32022, then initialize lands on 2025-11-25 with tools.listChanged: true; a prebuilt engine combined with the option is rejected.
    • McpHttpHandlerTest: an unversioned request still works when the default version is excluded.
  • Integration test: RemoteCatalogNotificationIntegrationTest runs an HTTP upstream behind a stdio server restricted to handshake versions. It checks the -32022 probe, initialize with listChanged, the forwarded list_changed on tools/call, that a re-list shows the new tool, and that the new tool is callable.
  • Build tasks: ./gradlew :mcp:mcp-server:test :mcp:mcp-server:integ :mcp:mcp-server:javadoc :mcp:mcp-server:spotlessCheck all pass (225 unit tests, 128 integration tests).
  • Mutation check: undoing the default-protocol fallback or the HTTP default makes the new tests fail.
  • Real client: checked manually with a 2026-07-28-capable CLI client over stdio, against a proxied server that uses dynamic discovery.
    • Without the option, discovered tools never became callable.
    • With handshake-only versions, the client fell back to initialize and called the newly discovered tools in the same turn and in later turns, 3 of 3 runs.
  • Not run: I did not run the conformance task locally. The default configuration is unchanged.

What should reviewers focus on?

  • McpProtocolRegistry.create: the allow-list is applied after override resolution, and every listed id must be registered.
  • McpProtocolRegistry.defaultProtocol: its fallback.
  • McpHttpHandler.resolveProtocolVersion: now uses the engine default.
  • StdioMcpServerBuilder: the prebuilt-engine check.

Additional Links


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

A server that negotiates 2026-07-28 cannot deliver
notifications/tools/list_changed: that revision only delivers list-change
notifications on a subscriptions/listen stream, which is not implemented,
so server/discover rightly omits listChanged. A stdio server that relays
list changes from proxied servers (dynamic tool discovery) therefore stops
working for clients that prefer 2026-07-28, even though the same clients
work over the handshake versions.

Add McpEngine.Builder#protocolVersions and the matching
StdioMcpServerBuilder option. When set, only the given registered versions
are negotiated and advertised: server/discover and -32022 errors list only
them, and initialize falls back to the newest retained handshake version.
Clients that support both eras then fall back to initialize and keep
receiving list-change notifications.

The default protocol used for requests that do not identify a version now
falls back to the initialize fallback when the default version is excluded,
for stdio sessions and for HTTP requests without a version header.

The default behavior is unchanged.
@adwsingh
adwsingh force-pushed the mcp-stdio-handshake-only branch from 474bc29 to c42f4b0 Compare October 12, 2026 19:17
@adwsingh
adwsingh enabled auto-merge (rebase) October 12, 2026 19:17
@adwsingh
adwsingh merged commit d3dd178 into smithy-lang:main Oct 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mcp Stuff related to our MCP support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants