fix: add backward compatibility for MCP servers returning older protocol versions
- Add ProtocolVersions interface with version constants - Change protocolVersion() to protocolVersions() returning List<String> - Streamable HTTP clients now support both 2024-11-05 and 2025-03-26 - Fixes compatibility with MCP servers that return 2024-11-05 instead of 2025-03-26 Resolves #436 Related to #438 Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
parent
577952d1ee
commit
5e035ea255
20 changed files with 107 additions and 54 deletions
|
|
@ -290,7 +290,8 @@ class LifecycleInitializer {
|
|||
.timeout(this.initializationTimeout)
|
||||
.onErrorResume(ex -> {
|
||||
logger.warn("Failed to initialize", ex);
|
||||
return Mono.error(new McpError("Client failed to initialize " + actionName));
|
||||
return Mono.error(
|
||||
new McpError("Client failed to initialize " + actionName + " due to: " + ex.getMessage()));
|
||||
})
|
||||
.flatMap(operation);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -272,9 +272,9 @@ public class McpAsyncClient {
|
|||
notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_PROGRESS,
|
||||
asyncProgressNotificationHandler(progressConsumersFinal));
|
||||
|
||||
this.initializer = new LifecycleInitializer(clientCapabilities, clientInfo,
|
||||
List.of(transport.protocolVersion()), initializationTimeout, ctx -> new McpClientSession(requestTimeout,
|
||||
transport, requestHandlers, notificationHandlers, con -> con.contextWrite(ctx)));
|
||||
this.initializer = new LifecycleInitializer(clientCapabilities, clientInfo, transport.protocolVersions(),
|
||||
initializationTimeout, ctx -> new McpClientSession(requestTimeout, transport, requestHandlers,
|
||||
notificationHandlers, con -> con.contextWrite(ctx)));
|
||||
this.transport.setExceptionHandler(this.initializer::handleException);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import java.net.http.HttpClient;
|
|||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
|
@ -25,6 +26,7 @@ import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEven
|
|||
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||
import io.modelcontextprotocol.spec.McpError;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.modelcontextprotocol.spec.ProtocolVersions;
|
||||
import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
import io.modelcontextprotocol.util.Utils;
|
||||
|
|
@ -62,7 +64,7 @@ import reactor.core.publisher.Sinks;
|
|||
*/
|
||||
public class HttpClientSseClientTransport implements McpClientTransport {
|
||||
|
||||
private static final String MCP_PROTOCOL_VERSION = "2024-11-05";
|
||||
private static final String MCP_PROTOCOL_VERSION = ProtocolVersions.MCP_2024_11_05;
|
||||
|
||||
private static final String MCP_PROTOCOL_VERSION_HEADER_NAME = "MCP-Protocol-Version";
|
||||
|
||||
|
|
@ -217,8 +219,8 @@ public class HttpClientSseClientTransport implements McpClientTransport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return MCP_PROTOCOL_VERSION;
|
||||
public List<String> protocolVersions() {
|
||||
return List.of(ProtocolVersions.MCP_2024_11_05);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import io.modelcontextprotocol.spec.McpSchema;
|
|||
import io.modelcontextprotocol.spec.McpTransportSession;
|
||||
import io.modelcontextprotocol.spec.McpTransportSessionNotFoundException;
|
||||
import io.modelcontextprotocol.spec.McpTransportStream;
|
||||
import io.modelcontextprotocol.spec.ProtocolVersions;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
import io.modelcontextprotocol.util.Utils;
|
||||
import reactor.core.Disposable;
|
||||
|
|
@ -73,7 +74,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
|||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HttpClientStreamableHttpTransport.class);
|
||||
|
||||
private static final String MCP_PROTOCOL_VERSION = "2025-03-26";
|
||||
private static final String MCP_PROTOCOL_VERSION = ProtocolVersions.MCP_2025_03_26;
|
||||
|
||||
private static final String DEFAULT_ENDPOINT = "/mcp";
|
||||
|
||||
|
|
@ -135,8 +136,8 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return MCP_PROTOCOL_VERSION;
|
||||
public List<String> protocolVersions() {
|
||||
return List.of(ProtocolVersions.MCP_2024_11_05, ProtocolVersions.MCP_2025_03_26);
|
||||
}
|
||||
|
||||
public static Builder builder(String baseUri) {
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ public class McpAsyncServer {
|
|||
Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
|
||||
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
|
||||
|
||||
this.protocolVersions = List.of(mcpTransportProvider.protocolVersion());
|
||||
this.protocolVersions = mcpTransportProvider.protocolVersions();
|
||||
|
||||
mcpTransportProvider.setSessionFactory(transport -> new McpServerSession(UUID.randomUUID().toString(),
|
||||
requestTimeout, transport, this::asyncInitializeRequestHandler, requestHandlers, notificationHandlers));
|
||||
|
|
@ -170,7 +170,7 @@ public class McpAsyncServer {
|
|||
Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
|
||||
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
|
||||
|
||||
this.protocolVersions = List.of(mcpTransportProvider.protocolVersion());
|
||||
this.protocolVersions = mcpTransportProvider.protocolVersions();
|
||||
|
||||
mcpTransportProvider.setSessionFactory(new DefaultMcpStreamableServerSessionFactory(requestTimeout,
|
||||
this::asyncInitializeRequestHandler, requestHandlers, notificationHandlers));
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ public class McpStatelessAsyncServer {
|
|||
requestHandlers.put(McpSchema.METHOD_COMPLETION_COMPLETE, completionCompleteRequestHandler());
|
||||
}
|
||||
|
||||
this.protocolVersions = List.of(mcpTransport.protocolVersion());
|
||||
this.protocolVersions = new ArrayList<>(mcpTransport.protocolVersions());
|
||||
|
||||
McpStatelessServerHandler handler = new DefaultMcpStatelessServerHandler(requestHandlers, Map.of());
|
||||
mcpTransport.setMcpHandler(handler);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import java.io.BufferedReader;
|
|||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
|
@ -20,6 +21,7 @@ import io.modelcontextprotocol.spec.McpSchema;
|
|||
import io.modelcontextprotocol.spec.McpServerSession;
|
||||
import io.modelcontextprotocol.spec.McpServerTransport;
|
||||
import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
||||
import io.modelcontextprotocol.spec.ProtocolVersions;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
import io.modelcontextprotocol.util.KeepAliveScheduler;
|
||||
import jakarta.servlet.AsyncContext;
|
||||
|
|
@ -180,8 +182,8 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
|||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return "2024-11-05";
|
||||
public List<String> protocolVersions() {
|
||||
return List.of(ProtocolVersions.MCP_2024_11_05);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -28,6 +28,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;
|
||||
|
|
@ -155,8 +156,8 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
|||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return "2025-03-26";
|
||||
public List<String> protocolVersions() {
|
||||
return List.of(ProtocolVersions.MCP_2024_11_05, ProtocolVersions.MCP_2025_03_26);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import java.io.InputStream;
|
|||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
|
@ -22,6 +23,7 @@ import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
|
|||
import io.modelcontextprotocol.spec.McpServerSession;
|
||||
import io.modelcontextprotocol.spec.McpServerTransport;
|
||||
import io.modelcontextprotocol.spec.McpServerTransportProvider;
|
||||
import io.modelcontextprotocol.spec.ProtocolVersions;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -89,8 +91,8 @@ public class StdioServerTransportProvider implements McpServerTransportProvider
|
|||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return "2024-11-05";
|
||||
public List<String> protocolVersions() {
|
||||
return List.of(ProtocolVersions.MCP_2024_11_05);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public final class McpSchema {
|
|||
}
|
||||
|
||||
@Deprecated
|
||||
public static final String LATEST_PROTOCOL_VERSION = "2025-03-26";
|
||||
public static final String LATEST_PROTOCOL_VERSION = ProtocolVersions.MCP_2025_03_26;
|
||||
|
||||
public static final String JSONRPC_VERSION = "2.0";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package io.modelcontextprotocol.spec;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
|
@ -63,8 +64,8 @@ public interface McpServerTransportProviderBase {
|
|||
* Returns the protocol version supported by this transport provider.
|
||||
* @return the protocol version as a string
|
||||
*/
|
||||
default String protocolVersion() {
|
||||
return "2024-11-05";
|
||||
default List<String> protocolVersions() {
|
||||
return List.of(ProtocolVersions.MCP_2024_11_05);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package io.modelcontextprotocol.spec;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.modelcontextprotocol.server.McpStatelessServerHandler;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -26,8 +28,8 @@ public interface McpStatelessServerTransport {
|
|||
*/
|
||||
Mono<Void> closeGracefully();
|
||||
|
||||
default String protocolVersion() {
|
||||
return "2025-03-26";
|
||||
default List<String> protocolVersions() {
|
||||
return List.of(ProtocolVersions.MCP_2025_03_26);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package io.modelcontextprotocol.spec;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
|
@ -77,8 +79,8 @@ public interface McpTransport {
|
|||
*/
|
||||
<T> T unmarshalFrom(Object data, TypeReference<T> typeRef);
|
||||
|
||||
default String protocolVersion() {
|
||||
return "2024-11-05";
|
||||
default List<String> protocolVersions() {
|
||||
return List.of(ProtocolVersions.MCP_2024_11_05);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package io.modelcontextprotocol.spec;
|
||||
|
||||
public interface ProtocolVersions {
|
||||
|
||||
/**
|
||||
* MCP protocol version for 2024-11-05.
|
||||
* https://modelcontextprotocol.io/specification/2024-11-05
|
||||
*/
|
||||
String MCP_2024_11_05 = "2024-11-05";
|
||||
|
||||
/**
|
||||
* MCP protocol version for 2025-03-26.
|
||||
* https://modelcontextprotocol.io/specification/2025-03-26
|
||||
*/
|
||||
String MCP_2025_03_26 = "2025-03-26";
|
||||
|
||||
/**
|
||||
* MCP protocol version for 2025-06-18.
|
||||
* https://modelcontextprotocol.io/specification/2025-06-18
|
||||
*/
|
||||
String MCP_2025_06_18 = "2025-06-18";
|
||||
|
||||
}
|
||||
|
|
@ -45,8 +45,8 @@ public class MockMcpClientTransport implements McpClientTransport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String protocolVersion() {
|
||||
return protocolVersion;
|
||||
public List<String> protocolVersions() {
|
||||
return List.of(protocolVersion);
|
||||
}
|
||||
|
||||
public void simulateIncomingMessage(McpSchema.JSONRPCMessage message) {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class McpAsyncClientResponseHandlerTests {
|
|||
|
||||
// Verify initialization result
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.protocolVersion()).isEqualTo(transport.protocolVersion());
|
||||
assertThat(result.protocolVersion()).isEqualTo(transport.protocolVersions().get(0));
|
||||
assertThat(result.capabilities()).isEqualTo(serverCapabilities);
|
||||
assertThat(result.serverInfo()).isEqualTo(serverInfo);
|
||||
assertThat(result.instructions()).isEqualTo("Test instructions");
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.modelcontextprotocol.spec.McpClientTransport;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.modelcontextprotocol.spec.ProtocolVersions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -24,8 +26,8 @@ class McpAsyncClientTests {
|
|||
public static final McpSchema.ServerCapabilities MOCK_SERVER_CAPABILITIES = McpSchema.ServerCapabilities.builder()
|
||||
.build();
|
||||
|
||||
public static final McpSchema.InitializeResult MOCK_INIT_RESULT = new McpSchema.InitializeResult("2024-11-05",
|
||||
MOCK_SERVER_CAPABILITIES, MOCK_SERVER_INFO, "Test instructions");
|
||||
public static final McpSchema.InitializeResult MOCK_INIT_RESULT = new McpSchema.InitializeResult(
|
||||
ProtocolVersions.MCP_2024_11_05, MOCK_SERVER_CAPABILITIES, MOCK_SERVER_INFO, "Test instructions");
|
||||
|
||||
private static final String CONTEXT_KEY = "context.key";
|
||||
|
||||
|
|
|
|||
|
|
@ -37,18 +37,20 @@ class McpClientProtocolVersionTests {
|
|||
try {
|
||||
Mono<InitializeResult> initializeResultMono = client.initialize();
|
||||
|
||||
String protocolVersion = transport.protocolVersions().get(transport.protocolVersions().size() - 1);
|
||||
|
||||
StepVerifier.create(initializeResultMono).then(() -> {
|
||||
McpSchema.JSONRPCRequest request = transport.getLastSentMessageAsRequest();
|
||||
assertThat(request.params()).isInstanceOf(McpSchema.InitializeRequest.class);
|
||||
McpSchema.InitializeRequest initRequest = (McpSchema.InitializeRequest) request.params();
|
||||
assertThat(initRequest.protocolVersion()).isEqualTo(transport.protocolVersion());
|
||||
assertThat(initRequest.protocolVersion()).isEqualTo(transport.protocolVersions().get(0));
|
||||
|
||||
transport.simulateIncomingMessage(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(),
|
||||
new McpSchema.InitializeResult(transport.protocolVersion(), null,
|
||||
new McpSchema.InitializeResult(protocolVersion, null,
|
||||
new McpSchema.Implementation("test-server", "1.0.0"), null),
|
||||
null));
|
||||
}).assertNext(result -> {
|
||||
assertThat(result.protocolVersion()).isEqualTo(transport.protocolVersion());
|
||||
assertThat(result.protocolVersion()).isEqualTo(protocolVersion);
|
||||
}).verifyComplete();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,9 @@ class McpServerProtocolVersionTests {
|
|||
assertThat(jsonResponse.id()).isEqualTo(requestId);
|
||||
assertThat(jsonResponse.result()).isInstanceOf(McpSchema.InitializeResult.class);
|
||||
McpSchema.InitializeResult result = (McpSchema.InitializeResult) jsonResponse.result();
|
||||
assertThat(result.protocolVersion()).isEqualTo(transportProvider.protocolVersion());
|
||||
|
||||
var protocolVersion = transportProvider.protocolVersions().get(transportProvider.protocolVersions().size() - 1);
|
||||
assertThat(result.protocolVersion()).isEqualTo(protocolVersion);
|
||||
|
||||
server.closeGracefully().subscribe();
|
||||
}
|
||||
|
|
@ -93,7 +95,8 @@ class McpServerProtocolVersionTests {
|
|||
assertThat(jsonResponse.id()).isEqualTo(requestId);
|
||||
assertThat(jsonResponse.result()).isInstanceOf(McpSchema.InitializeResult.class);
|
||||
McpSchema.InitializeResult result = (McpSchema.InitializeResult) jsonResponse.result();
|
||||
assertThat(result.protocolVersion()).isEqualTo(transportProvider.protocolVersion());
|
||||
var protocolVersion = transportProvider.protocolVersions().get(transportProvider.protocolVersions().size() - 1);
|
||||
assertThat(result.protocolVersion()).isEqualTo(protocolVersion);
|
||||
|
||||
server.closeGracefully().subscribe();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -320,8 +320,8 @@ public class McpSchemaTests {
|
|||
McpSchema.Implementation clientInfo = new McpSchema.Implementation("test-client", "1.0.0");
|
||||
Map<String, Object> meta = Map.of("metaKey", "metaValue");
|
||||
|
||||
McpSchema.InitializeRequest request = new McpSchema.InitializeRequest("2024-11-05", capabilities, clientInfo,
|
||||
meta);
|
||||
McpSchema.InitializeRequest request = new McpSchema.InitializeRequest(ProtocolVersions.MCP_2024_11_05,
|
||||
capabilities, clientInfo, meta);
|
||||
|
||||
String value = mapper.writeValueAsString(request);
|
||||
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
|
||||
|
|
@ -343,8 +343,8 @@ public class McpSchemaTests {
|
|||
|
||||
McpSchema.Implementation serverInfo = new McpSchema.Implementation("test-server", "1.0.0");
|
||||
|
||||
McpSchema.InitializeResult result = new McpSchema.InitializeResult("2024-11-05", capabilities, serverInfo,
|
||||
"Server initialized successfully");
|
||||
McpSchema.InitializeResult result = new McpSchema.InitializeResult(ProtocolVersions.MCP_2024_11_05,
|
||||
capabilities, serverInfo, "Server initialized successfully");
|
||||
|
||||
String value = mapper.writeValueAsString(result);
|
||||
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
|
||||
|
|
|
|||
Loading…
Reference in a new issue