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 0a8dff363..400a8a2fa 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 @@ private Mono reconnect(McpTransportStream stream) { 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 Mono sendMessage(McpSchema.JSONRPCMessage sentMessage) { 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 static class Builder { 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 34671c105..46612e7a2 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 @@ -157,7 +157,7 @@ private HttpServletStreamableServerTransportProvider(McpJsonMapper jsonMapper, S @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 b58f1c552..97bde0b10 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 @@ private 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 d1c2e5206..ee28f5ff8 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 @@ default void close() { 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 2ade30e17..8ddd54266 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 @@ void testRequestCustomizer() throws URISyntaxException { // 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 @@ void testAsyncRequestCustomizer() throws URISyntaxException { // 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 8efb6a960..614172b84 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 @@ void usesLatestVersion() { .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 @@ void usesServerSupportedVersion() { 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 @@ void usesServerSupportedVersion() { .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 0b5ce55cd..1e3113e79 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 @@ -510,7 +510,7 @@ public static class Builder { 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 deebfc616..5fe8428a6 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 @@ -98,7 +98,7 @@ private WebFluxStreamableServerTransportProvider(McpJsonMapper jsonMapper, Strin @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 5d2bfda68..5edc56fb9 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 @@ void usesLatestVersion() { .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 @@ void usesLatestVersion() { 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 @@ void usesServerSupportedVersion() { .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 f2a58d4d8..d2f1a7e37 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 @@ -144,7 +144,7 @@ private WebMvcStreamableServerTransportProvider(McpJsonMapper jsonMapper, String @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