From ef8399c1cb3241e26a399f646e84eafa3e9eff51 Mon Sep 17 00:00:00 2001 From: Luca Chang <131398524+LucaButBoring@users.noreply.github.com> Date: Fri, 6 Feb 2026 01:55:37 -0800 Subject: [PATCH] feat: broadcast 2025-11-25 as latest supported client version (#758) * feat: broadcast 2025-11-25 as latest supported client version * fix: ignore empty SSE events --- .../HttpClientStreamableHttpTransport.java | 28 ++++++++++++++++--- ...vletStreamableServerTransportProvider.java | 2 +- .../modelcontextprotocol/spec/McpSchema.java | 2 +- .../spec/McpStatelessServerTransport.java | 3 +- ...HttpClientStreamableHttpTransportTest.java | 4 +-- ...ttpVersionNegotiationIntegrationTests.java | 10 +++---- .../WebClientStreamableHttpTransport.java | 2 +- ...FluxStreamableServerTransportProvider.java | 2 +- ...ttpVersionNegotiationIntegrationTests.java | 10 +++---- ...bMvcStreamableServerTransportProvider.java | 2 +- 10 files changed, 43 insertions(+), 22 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java index 0a8dff3..400a8a2 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java @@ -295,12 +295,23 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { if (statusCode >= 200 && statusCode < 300) { if (MESSAGE_EVENT_TYPE.equals(responseEvent.sseEvent().event())) { + String data = responseEvent.sseEvent().data(); + // Per 2025-11-25 spec (SEP-1699), servers may + // send SSE events + // with empty data to prime the client for + // reconnection. + // Skip these events as they contain no JSON-RPC + // message. + if (data == null || data.isBlank()) { + logger.debug("Skipping SSE event with empty data (stream primer)"); + return Flux.empty(); + } try { // We don't support batching ATM and probably // won't since the next version considers // removing it. - McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage( - this.jsonMapper, responseEvent.sseEvent().data()); + McpSchema.JSONRPCMessage message = McpSchema + .deserializeJsonRpcMessage(this.jsonMapper, data); Tuple2, Iterable> idWithMessages = Tuples .of(Optional.ofNullable(responseEvent.sseEvent().id()), @@ -503,13 +514,22 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { else if (contentType.contains(TEXT_EVENT_STREAM)) { return Flux.just(((ResponseSubscribers.SseResponseEvent) responseEvent).sseEvent()) .flatMap(sseEvent -> { + String data = sseEvent.data(); + // Per 2025-11-25 spec (SEP-1699), servers may send SSE + // events + // with empty data to prime the client for reconnection. + // Skip these events as they contain no JSON-RPC message. + if (data == null || data.isBlank()) { + logger.debug("Skipping SSE event with empty data (stream primer)"); + return Flux.empty(); + } try { // We don't support batching ATM and probably // won't // since the // next version considers removing it. McpSchema.JSONRPCMessage message = McpSchema - .deserializeJsonRpcMessage(this.jsonMapper, sseEvent.data()); + .deserializeJsonRpcMessage(this.jsonMapper, data); Tuple2, Iterable> idWithMessages = Tuples .of(Optional.ofNullable(sseEvent.id()), List.of(message)); @@ -641,7 +661,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { private Duration connectTimeout = Duration.ofSeconds(10); private List supportedProtocolVersions = List.of(ProtocolVersions.MCP_2024_11_05, - ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18); + ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25); /** * Creates a new builder with the specified base URI. diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java index b7c8e7b..d9c0916 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java @@ -171,7 +171,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet @Override public List protocolVersions() { return List.of(ProtocolVersions.MCP_2024_11_05, ProtocolVersions.MCP_2025_03_26, - ProtocolVersions.MCP_2025_06_18); + ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25); } @Override diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index b58f1c5..97bde0b 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -42,7 +42,7 @@ public final class McpSchema { } @Deprecated - public static final String LATEST_PROTOCOL_VERSION = ProtocolVersions.MCP_2025_06_18; + public static final String LATEST_PROTOCOL_VERSION = ProtocolVersions.MCP_2025_11_25; public static final String JSONRPC_VERSION = "2.0"; diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java index d1c2e52..ee28f5f 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java @@ -29,7 +29,8 @@ public interface McpStatelessServerTransport { Mono closeGracefully(); default List protocolVersions() { - return List.of(ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18); + return List.of(ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18, + ProtocolVersions.MCP_2025_11_25); } } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java index 2ade30e..8ddd542 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java @@ -90,7 +90,7 @@ class HttpClientStreamableHttpTransportTest { // Verify the customizer was called verify(mockRequestCustomizer, atLeastOnce()).customize(any(), eq("POST"), eq(uri), eq( - "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"MCP Client\",\"version\":\"0.3.1\"}}}"), + "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"MCP Client\",\"version\":\"0.3.1\"}}}"), eq(context)); }); } @@ -120,7 +120,7 @@ class HttpClientStreamableHttpTransportTest { // Verify the customizer was called verify(mockRequestCustomizer, atLeastOnce()).customize(any(), eq("POST"), eq(uri), eq( - "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"MCP Client\",\"version\":\"0.3.1\"}}}"), + "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"MCP Client\",\"version\":\"0.3.1\"}}}"), eq(context)); }); } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java index 8efb6a9..614172b 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java @@ -77,14 +77,14 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests { .hasSize(3) .map(McpTestRequestRecordingServletFilter.Call::headers) .allSatisfy(headers -> assertThat(headers).containsEntry("mcp-protocol-version", - ProtocolVersions.MCP_2025_06_18)); + ProtocolVersions.MCP_2025_11_25)); assertThat(response).isNotNull(); assertThat(response.content()).hasSize(1) .first() .extracting(McpSchema.TextContent.class::cast) .extracting(McpSchema.TextContent::text) - .isEqualTo(ProtocolVersions.MCP_2025_06_18); + .isEqualTo(ProtocolVersions.MCP_2025_11_25); mcpServer.close(); } @@ -93,7 +93,7 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests { startTomcat(); var transport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT) - .supportedProtocolVersions(List.of(ProtocolVersions.MCP_2025_06_18, "2263-03-18")) + .supportedProtocolVersions(List.of(ProtocolVersions.MCP_2025_11_25, "2263-03-18")) .build(); var client = McpClient.sync(transport).build(); @@ -108,14 +108,14 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests { .hasSize(2) .map(McpTestRequestRecordingServletFilter.Call::headers) .allSatisfy(headers -> assertThat(headers).containsEntry("mcp-protocol-version", - ProtocolVersions.MCP_2025_06_18)); + ProtocolVersions.MCP_2025_11_25)); assertThat(response).isNotNull(); assertThat(response.content()).hasSize(1) .first() .extracting(McpSchema.TextContent.class::cast) .extracting(McpSchema.TextContent::text) - .isEqualTo(ProtocolVersions.MCP_2025_06_18); + .isEqualTo(ProtocolVersions.MCP_2025_11_25); mcpServer.close(); } diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java index 5af9898..282745a 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java @@ -518,7 +518,7 @@ public class WebClientStreamableHttpTransport implements McpClientTransport { private boolean openConnectionOnStartup = false; private List supportedProtocolVersions = List.of(ProtocolVersions.MCP_2024_11_05, - ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18); + ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25); private Builder(WebClient.Builder webClientBuilder) { Assert.notNull(webClientBuilder, "WebClient.Builder must not be null"); diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java index 762ee00..0aed244 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java @@ -106,7 +106,7 @@ public class WebFluxStreamableServerTransportProvider implements McpStreamableSe @Override public List protocolVersions() { return List.of(ProtocolVersions.MCP_2024_11_05, ProtocolVersions.MCP_2025_03_26, - ProtocolVersions.MCP_2025_06_18); + ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25); } @Override diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableHttpVersionNegotiationIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableHttpVersionNegotiationIntegrationTests.java index 5d2bfda..5edc56f 100644 --- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableHttpVersionNegotiationIntegrationTests.java +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableHttpVersionNegotiationIntegrationTests.java @@ -105,14 +105,14 @@ class WebFluxStreamableHttpVersionNegotiationIntegrationTests { .hasSize(3) .map(McpTestRequestRecordingExchangeFilterFunction.Call::headers) .allSatisfy(headers -> assertThat(headers).containsEntry("mcp-protocol-version", - ProtocolVersions.MCP_2025_06_18)); + ProtocolVersions.MCP_2025_11_25)); assertThat(response).isNotNull(); assertThat(response.content()).hasSize(1) .first() .extracting(McpSchema.TextContent.class::cast) .extracting(McpSchema.TextContent::text) - .isEqualTo(ProtocolVersions.MCP_2025_06_18); + .isEqualTo(ProtocolVersions.MCP_2025_11_25); mcpServer.close(); } @@ -120,7 +120,7 @@ class WebFluxStreamableHttpVersionNegotiationIntegrationTests { void usesServerSupportedVersion() { var transport = WebClientStreamableHttpTransport .builder(WebClient.builder().baseUrl("http://localhost:" + PORT)) - .supportedProtocolVersions(List.of(ProtocolVersions.MCP_2025_06_18, "2263-03-18")) + .supportedProtocolVersions(List.of(ProtocolVersions.MCP_2025_11_25, "2263-03-18")) .build(); var client = McpClient.sync(transport).requestTimeout(Duration.ofHours(10)).build(); @@ -137,14 +137,14 @@ class WebFluxStreamableHttpVersionNegotiationIntegrationTests { .hasSize(2) .map(McpTestRequestRecordingExchangeFilterFunction.Call::headers) .allSatisfy(headers -> assertThat(headers).containsEntry("mcp-protocol-version", - ProtocolVersions.MCP_2025_06_18)); + ProtocolVersions.MCP_2025_11_25)); assertThat(response).isNotNull(); assertThat(response.content()).hasSize(1) .first() .extracting(McpSchema.TextContent.class::cast) .extracting(McpSchema.TextContent::text) - .isEqualTo(ProtocolVersions.MCP_2025_06_18); + .isEqualTo(ProtocolVersions.MCP_2025_11_25); mcpServer.close(); } diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java index 7ca76f8..411d9c2 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java @@ -157,7 +157,7 @@ public class WebMvcStreamableServerTransportProvider implements McpStreamableSer @Override public List protocolVersions() { return List.of(ProtocolVersions.MCP_2024_11_05, ProtocolVersions.MCP_2025_03_26, - ProtocolVersions.MCP_2025_06_18); + ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25); } @Override