Remove 2024-11-05 protocol from Streamable HTTP transport
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
parent
39c225e46c
commit
735a85190b
4 changed files with 115 additions and 75 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue