From f3da4cf339d34463db1bd9cf60cd02f6466bf2ad Mon Sep 17 00:00:00 2001 From: Param Parikh Date: Mon, 31 Aug 2026 17:10:40 +0000 Subject: [PATCH] fix(mcp-server): Answer latest supported version for unknown protocol versions Per the MCP spec, a server that does not support the requested protocol version must respond with the latest version it supports. Previously handleInitialize left protocolVersion unset for unknown versions, so the Smithy model default (2024-11-05, the oldest version) leaked into InitializeResult. Add ProtocolVersion.latestVersion(), derived from the supported-version registry so it cannot drift as versions are added. The registry moves into an initialization-on-demand holder: computing the latest during class init of the sealed base class NPEs when a subclass INSTANCE is the first member of the hierarchy touched (base clinit reads a still-null INSTANCE; caught by McpServerIntegrationTest). Known versions are still echoed as-is; a missing protocolVersion param keeps its existing behavior. --- .../smithy/java/mcp/server/McpService.java | 6 ++- .../java/mcp/server/ProtocolVersion.java | 41 +++++++++++++++---- .../smithy/java/mcp/server/McpServerTest.java | 23 +++++++++++ .../java/mcp/server/ProtocolVersionTest.java | 6 +++ 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpService.java b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpService.java index 04e15fb91..6d7fd679f 100644 --- a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpService.java +++ b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpService.java @@ -296,7 +296,11 @@ private JsonRpcResponse handleInitialize(JsonRpcRequest req) { String pv = null; if (maybeVersion != null) { var protocolVersion = ProtocolVersion.version(maybeVersion.asString()); - if (!(protocolVersion instanceof ProtocolVersion.UnknownVersion)) { + if (protocolVersion instanceof ProtocolVersion.UnknownVersion) { + // Per the MCP spec, a server that does not support the requested protocol + // version must respond with the latest version it supports. + pv = ProtocolVersion.latestVersion().identifier(); + } else { pv = protocolVersion.identifier(); } } diff --git a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/ProtocolVersion.java b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/ProtocolVersion.java index 555559a3b..f5b8a6254 100644 --- a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/ProtocolVersion.java +++ b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/ProtocolVersion.java @@ -5,6 +5,8 @@ package software.amazon.smithy.java.mcp.server; +import java.util.Collections; +import java.util.List; import software.amazon.smithy.utils.SmithyUnstableApi; @SmithyUnstableApi @@ -49,6 +51,20 @@ private UnknownVersion(String identifier) { } } + /** + * Holder defers initialization until first use so the version subclasses are fully loaded + * first — a plain static field on this class would read a still-null INSTANCE whenever a + * subclass is the first member of the hierarchy to be initialized. + */ + private static final class SupportedVersions { + private static final List ALL = List.of( + v2024_11_05.INSTANCE, + v2025_03_26.INSTANCE, + v2025_06_18.INSTANCE, + v2025_11_25.INSTANCE); + private static final ProtocolVersion LATEST = Collections.max(ALL); + } + private final String identifier; private ProtocolVersion(String identifier) { @@ -72,17 +88,26 @@ public final int compareTo(ProtocolVersion o) { } public static ProtocolVersion version(String identifier) { - return switch (identifier) { - case null -> v2025_03_26.INSTANCE; - case "2024-11-05" -> v2024_11_05.INSTANCE; - case "2025-03-26" -> v2025_03_26.INSTANCE; - case "2025-06-18" -> v2025_06_18.INSTANCE; - case "2025-11-25" -> v2025_11_25.INSTANCE; - default -> new UnknownVersion(identifier); - }; + if (identifier == null) { + return defaultVersion(); + } + for (var version : SupportedVersions.ALL) { + if (version.identifier.equals(identifier)) { + return version; + } + } + return new UnknownVersion(identifier); } public static ProtocolVersion defaultVersion() { return v2025_03_26.INSTANCE; } + + /** + * The most recent protocol version this server supports, derived from the supported-version + * registry. + */ + public static ProtocolVersion latestVersion() { + return SupportedVersions.LATEST; + } } diff --git a/mcp/mcp-server/src/test/java/software/amazon/smithy/java/mcp/server/McpServerTest.java b/mcp/mcp-server/src/test/java/software/amazon/smithy/java/mcp/server/McpServerTest.java index 88c7abfe6..37ee6a2f8 100644 --- a/mcp/mcp-server/src/test/java/software/amazon/smithy/java/mcp/server/McpServerTest.java +++ b/mcp/mcp-server/src/test/java/software/amazon/smithy/java/mcp/server/McpServerTest.java @@ -154,6 +154,29 @@ public void initializeWithV2025_11_25ProtocolVersion() { assertTrue(outputSchema.get("properties").asStringMap().containsKey("outputStr")); } + @Test + public void initializeWithUnknownProtocolVersionAnswersLatestSupported() { + server = McpServer.builder() + .name("smithy-mcp-server") + .input(input) + .output(output) + .addService("test-mcp", + ProxyService.builder() + .service(ShapeId.from("smithy.test#TestService")) + .proxyEndpoint("http://localhost") + .model(MODEL) + .build()) + .build(); + + server.start(); + + // Per the MCP spec, a server answers a request for a version it does not support with + // the latest version it supports — not the oldest. + write("initialize", Document.of(Map.of("protocolVersion", Document.of("2026-07-28")))); + var pv = read().getResult().getMember("protocolVersion").asString(); + assertEquals("2025-11-25", pv); + } + @Test public void noOutputSchemaWithUnsupportedProtocolVersion() { server = McpServer.builder() diff --git a/mcp/mcp-server/src/test/java/software/amazon/smithy/java/mcp/server/ProtocolVersionTest.java b/mcp/mcp-server/src/test/java/software/amazon/smithy/java/mcp/server/ProtocolVersionTest.java index d95017e47..5d08c0596 100644 --- a/mcp/mcp-server/src/test/java/software/amazon/smithy/java/mcp/server/ProtocolVersionTest.java +++ b/mcp/mcp-server/src/test/java/software/amazon/smithy/java/mcp/server/ProtocolVersionTest.java @@ -39,6 +39,12 @@ void defaultVersionIs2025_03_26() { assertEquals("2025-03-26", ProtocolVersion.defaultVersion().identifier()); } + @Test + void latestVersionIs2025_11_25() { + assertInstanceOf(ProtocolVersion.v2025_11_25.class, ProtocolVersion.latestVersion()); + assertEquals("2025-11-25", ProtocolVersion.latestVersion().identifier()); + } + @Test void compareToOrdersChronologically() { assertTrue(ProtocolVersion.v2024_11_05.INSTANCE.compareTo(ProtocolVersion.v2025_03_26.INSTANCE) < 0);