Remove 2024-11-05 protocol from Streamable HTTP transport

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Daniel Garnier-Moiroux 2026-09-04 18:24:00 +02:00 • committed by Daniel Garnier-Moiroux
commit 735a85190b
4 changed files with 115 additions and 75 deletions

View file

@ -763,8 +763,8 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
private Duration connectTimeout = Duration.ofSeconds(10);
private List<String> supportedProtocolVersions = List.of(ProtocolVersions.MCP_2024_11_05,
ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25);
private List<String> supportedProtocolVersions = List.of(ProtocolVersions.MCP_2025_03_26,
ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25);
private McpHttpClientTransportAuthorizationErrorHandler authorizationErrorHandler = McpHttpClientTransportAuthorizationErrorHandler.NOOP;

View file

@ -24,6 +24,7 @@ import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpStreamableServerSession;
import io.modelcontextprotocol.spec.McpStreamableServerTransport;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import io.modelcontextprotocol.spec.ProtocolVersions;
import io.modelcontextprotocol.util.Assert;
import io.modelcontextprotocol.util.KeepAliveScheduler;
import jakarta.servlet.AsyncContext;
@ -820,6 +821,12 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
}
@Override
public List<String> protocolVersions() {
return List.of(ProtocolVersions.MCP_2025_03_26, ProtocolVersions.MCP_2025_06_18,
ProtocolVersions.MCP_2025_11_25);
}
public static Builder builder() {
return new Builder();
}

View file

@ -17,25 +17,30 @@ import io.modelcontextprotocol.server.McpSyncServerExchange;
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
import io.modelcontextprotocol.server.transport.McpTestRequestRecordingServletFilter;
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.ProtocolVersions;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.startup.Tomcat;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
class HttpClientStreamableHttpVersionNegotiationIntegrationTests {
private Tomcat tomcat;
private static Tomcat tomcat;
private static final int PORT = TomcatTestUtil.findAvailablePort();
private final McpTestRequestRecordingServletFilter requestRecordingFilter = new McpTestRequestRecordingServletFilter();
private static final McpTestRequestRecordingServletFilter requestRecordingFilter = new McpTestRequestRecordingServletFilter();
private final HttpServletStreamableServerTransportProvider transport = HttpServletStreamableServerTransportProvider
private static final HttpServletStreamableServerTransportProvider transport = HttpServletStreamableServerTransportProvider
.builder()
.contextExtractor(
req -> McpTransportContext.create(Map.of("protocol-version", req.getHeader("MCP-protocol-version"))))
@ -51,78 +56,13 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests {
.isError(false)
.build();
McpSyncServer mcpServer = McpServer.sync(transport)
private final McpSyncServer mcpServer = McpServer.sync(transport)
.capabilities(McpSchema.ServerCapabilities.builder().tools(false).build())
.tools(McpServerFeatures.SyncToolSpecification.builder().tool(toolSpec).callHandler(toolHandler).build())
.build();
@AfterEach
void tearDown() {
stopTomcat();
}
@Test
void usesLatestVersion() {
startTomcat();
var client = McpClient.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT).build())
.build();
client.initialize();
McpSchema.CallToolResult response = client
.callTool(McpSchema.CallToolRequest.builder("test-tool").arguments(Map.of()).build());
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)
.first()
.extracting(McpSchema.TextContent.class::cast)
.extracting(McpSchema.TextContent::text)
.isEqualTo(ProtocolVersions.MCP_2025_11_25);
mcpServer.close();
}
@Test
void usesServerSupportedVersion() {
startTomcat();
var transport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT)
.supportedProtocolVersions(List.of(ProtocolVersions.MCP_2025_11_25, "2263-03-18"))
.build();
var client = McpClient.sync(transport).build();
client.initialize();
McpSchema.CallToolResult response = client
.callTool(McpSchema.CallToolRequest.builder("test-tool").arguments(Map.of()).build());
var calls = requestRecordingFilter.getCalls();
// Initialize tells the server the Client's latest supported version
// FIXME: Set the correct protocol version on GET /mcp
assertThat(calls).filteredOn(c -> c.method().equals("POST") && !c.body().contains("\"method\":\"initialize\""))
// POST notification/initialized ; POST tools/call
.hasSize(2)
.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)
.first()
.extracting(McpSchema.TextContent.class::cast)
.extracting(McpSchema.TextContent::text)
.isEqualTo(ProtocolVersions.MCP_2025_11_25);
mcpServer.close();
}
private void startTomcat() {
@BeforeAll
static void startTomcat() {
tomcat = TomcatTestUtil.createTomcatServer("", PORT, transport, requestRecordingFilter);
try {
tomcat.start();
@ -133,7 +73,8 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests {
}
}
private void stopTomcat() {
@AfterAll
static void stopTomcat() {
if (tomcat != null) {
try {
tomcat.stop();
@ -145,4 +86,92 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests {
}
}
@BeforeEach
void setUp() {
requestRecordingFilter.clear();
}
@Test
void usesLatestVersion() {
try (var client = McpClient.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT).build())
.build()) {
client.initialize();
McpSchema.CallToolResult response = client
.callTool(McpSchema.CallToolRequest.builder("test-tool").arguments(Map.of()).build());
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)
.first()
.extracting(McpSchema.TextContent.class::cast)
.extracting(McpSchema.TextContent::text)
.isEqualTo(ProtocolVersions.MCP_2025_11_25);
}
}
@Test
void usesServerSupportedVersion() {
var transport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT)
.supportedProtocolVersions(List.of(ProtocolVersions.MCP_2025_11_25, "2263-03-18"))
.build();
try (var client = McpClient.sync(transport).build()) {
client.initialize();
McpSchema.CallToolResult response = client
.callTool(McpSchema.CallToolRequest.builder("test-tool").arguments(Map.of()).build());
var calls = requestRecordingFilter.getCalls();
// Initialize tells the server the Client's latest supported version
// FIXME: Set the correct protocol version on GET /mcp
assertThat(calls)
.filteredOn(c -> c.method().equals("POST") && !c.body().contains("\"method\":\"initialize\""))
// POST notification/initialized ; POST tools/call
.hasSize(2)
.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)
.first()
.extracting(McpSchema.TextContent.class::cast)
.extracting(McpSchema.TextContent::text)
.isEqualTo(ProtocolVersions.MCP_2025_11_25);
}
}
@Test
void clientDoesNotSupportProtocolVersion20241105ByDefault() {
var transport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT).build();
// Testing it on the wire would require building a custom transport
// We trust the protocolVersions() accessor instead
assertThat(transport.protocolVersions()).containsExactly(ProtocolVersions.MCP_2025_03_26,
ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25);
}
@Test
void serverDoesNotSupportProtocolVersion20241105() {
var clientTransport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT)
.supportedProtocolVersions(List.of(ProtocolVersions.MCP_2024_11_05))
.build();
try (var client = McpClient.sync(clientTransport).build()) {
assertThatThrownBy(client::initialize).rootCause()
.isInstanceOf(McpError.class)
.asInstanceOf(type(McpError.class))
.extracting(Throwable::getMessage)
.isEqualTo("Unsupported protocol version");
mcpServer.close();
}
}
}

View file

@ -60,6 +60,10 @@ public class McpTestRequestRecordingServletFilter implements Filter {
return List.copyOf(calls);
}
public void clear() {
calls.clear();
}
public record Call(String method, Map<String, String> headers, String body) {
}