From 1d59d66eb99465cbfe5f8a5f5d51840f5e30fd5c Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Thu, 27 Aug 2026 15:33:07 +0200 Subject: [PATCH 1/2] test(mcp-test): deflake streamable HTTP version-negotiation integration test - tolerate an absent MCP-Protocol-Version header in the context extractor: Map.of rejects null values while spec-correct clients legitimately omit the header on initialize requests - await the asynchronously opened GET /mcp stream before asserting recorded calls; observed intermittently in CI as AssertionError Expected size: 3 but was: 2 (evidence: actions/run/33069467622 Jackson 2 Integration Tests) --- ...bleHttpVersionNegotiationIntegrationTests.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java index 563e52061..6506576d4 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java @@ -4,8 +4,10 @@ package io.modelcontextprotocol.common; +import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.BiFunction; import io.modelcontextprotocol.client.McpClient; @@ -22,6 +24,7 @@ import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.startup.Tomcat; +import static org.awaitility.Awaitility.await; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -37,8 +40,10 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests { private final HttpServletStreamableServerTransportProvider transport = HttpServletStreamableServerTransportProvider .builder() - .contextExtractor( - req -> McpTransportContext.create(Map.of("protocol-version", req.getHeader("MCP-protocol-version")))) + // The MCP-Protocol-Version header may legitimately be absent on initialize + // requests, so a missing header must not break context extraction. + .contextExtractor(req -> McpTransportContext + .create(Map.of("protocol-version", Objects.requireNonNullElse(req.getHeader("MCP-protocol-version"), "")))) .build(); private final McpSchema.Tool toolSpec = McpSchema.Tool.builder("test-tool") @@ -72,6 +77,12 @@ void usesLatestVersion() { McpSchema.CallToolResult response = client .callTool(McpSchema.CallToolRequest.builder("test-tool").arguments(Map.of()).build()); + // The GET /mcp stream is opened asynchronously once the initialize response + // creates the session, so wait for it to be recorded before asserting. + await().atMost(Duration.ofSeconds(5)) + .untilAsserted(() -> assertThat(requestRecordingFilter.getCalls()).filteredOn(c -> "GET".equals(c.method())) + .hasSize(1)); + var calls = requestRecordingFilter.getCalls(); assertThat(calls).filteredOn(c -> !c.body().contains("\"method\":\"initialize\"")) From d77e9f7d7f111964df7f55d8acf1f2c9eef2fc4b Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Fri, 28 Aug 2026 18:56:02 +0200 Subject: [PATCH 2/2] test(mcp-test): await the full request assertion, drop null-safe extractor Review follow-up: the header is never absent with the clients used here, so the contextExtractor stays as on main; the whole size-3 assertion now runs inside await() so a late POST is covered as well as the async GET. Claude-Session: https://claude.ai/code/session_01WtjUifqJuzJdfxc1zjcZrL --- ...ttpVersionNegotiationIntegrationTests.java | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java index 6506576d4..40e2c4846 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java @@ -7,7 +7,6 @@ import java.time.Duration; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.BiFunction; import io.modelcontextprotocol.client.McpClient; @@ -40,10 +39,8 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests { private final HttpServletStreamableServerTransportProvider transport = HttpServletStreamableServerTransportProvider .builder() - // The MCP-Protocol-Version header may legitimately be absent on initialize - // requests, so a missing header must not break context extraction. - .contextExtractor(req -> McpTransportContext - .create(Map.of("protocol-version", Objects.requireNonNullElse(req.getHeader("MCP-protocol-version"), "")))) + .contextExtractor( + req -> McpTransportContext.create(Map.of("protocol-version", req.getHeader("MCP-protocol-version")))) .build(); private final McpSchema.Tool toolSpec = McpSchema.Tool.builder("test-tool") @@ -79,18 +76,15 @@ void usesLatestVersion() { // The GET /mcp stream is opened asynchronously once the initialize response // creates the session, so wait for it to be recorded before asserting. - await().atMost(Duration.ofSeconds(5)) - .untilAsserted(() -> assertThat(requestRecordingFilter.getCalls()).filteredOn(c -> "GET".equals(c.method())) - .hasSize(1)); - - var calls = requestRecordingFilter.getCalls(); - - assertThat(calls).filteredOn(c -> !c.body().contains("\"method\":\"initialize\"")) - // GET /mcp ; POST notification/initialized ; POST tools/call - .hasSize(3) - .map(McpTestRequestRecordingServletFilter.Call::headers) - .allSatisfy(headers -> assertThat(headers).containsEntry("mcp-protocol-version", - ProtocolVersions.MCP_2025_11_25)); + await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> { + var calls = requestRecordingFilter.getCalls(); + assertThat(calls).filteredOn(c -> !c.body().contains("\"method\":\"initialize\"")) + // GET /mcp ; POST notification/initialized ; POST tools/call + .hasSize(3) + .map(McpTestRequestRecordingServletFilter.Call::headers) + .allSatisfy(headers -> assertThat(headers).containsEntry("mcp-protocol-version", + ProtocolVersions.MCP_2025_11_25)); + }); assertThat(response).isNotNull(); assertThat(response.content()).hasSize(1)