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);