Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.ElicitRequest;
import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
Expand All @@ -38,6 +40,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -78,6 +81,13 @@
* .streamableHttpTransport("https://mcp.example.com/http")
* .queryParams(Map.of("token", "abc123", "env", "prod"))
* .buildSync();
*
* // StdIO transport with custom protocol versions
* McpClientWrapper client = McpClientBuilder.create("mcp")
* .stdioTransport("python", "server.py")
* .protocolVersions("2024-11-05", "2025-03-26")
* .buildAsync()
* .block();
* }</pre>
*/
public class McpClientBuilder {
Expand All @@ -91,6 +101,7 @@ public class McpClientBuilder {
private Duration initializationTimeout = DEFAULT_INIT_TIMEOUT;
private Function<ElicitRequest, Mono<ElicitResult>> asyncElicitationHandler;
private Function<ElicitRequest, ElicitResult> syncElicitationHandler;
private List<String> protocolVersions;

private McpClientBuilder(String name) {
this.name = name;
Expand Down Expand Up @@ -294,6 +305,34 @@ public McpClientBuilder initializationTimeout(Duration timeout) {
return this;
}

/**
* Sets the MCP protocol versions that the client supports.
*
* <p>By default, the client only supports "2024-11-05". If the MCP server responds
* with a different protocol version during initialization (e.g., "2025-03-26"),
* the connection will fail with "Unsupported protocol version".
*
* <p>Use this method to declare support for additional protocol versions:
* <pre>{@code
* McpClientWrapper client = McpClientBuilder.create("server")
* .stdioTransport("python", "server.py")
* .protocolVersions("2024-11-05", "2025-03-26")
* .buildAsync()
* .block();
* }</pre>
*
* @param versions one or more protocol version strings (e.g., "2024-11-05", "2025-03-26")
* @return this builder
* @throws IllegalArgumentException if no versions are provided
*/
public McpClientBuilder protocolVersions(String... versions) {
if (versions == null || versions.length == 0) {
throw new IllegalArgumentException("At least one protocol version must be specified");
}
this.protocolVersions = Collections.unmodifiableList(Arrays.asList(versions));
return this;
}

/**
* Registers an asynchronous elicitation handler for processing elicit requests
* from the server.
Expand Down Expand Up @@ -381,6 +420,11 @@ public Mono<McpClientWrapper> buildAsync() {
() -> {
McpClientTransport transport = transportConfig.createTransport();

if (protocolVersions != null) {
transport =
new ProtocolVersionOverrideTransport(transport, protocolVersions);
}

McpSchema.Implementation clientInfo =
new McpSchema.Implementation(
"agentscope-java", "AgentScope Java Framework", VERSION);
Expand Down Expand Up @@ -417,6 +461,10 @@ public McpClientWrapper buildSync() {

McpClientTransport transport = transportConfig.createTransport();

if (protocolVersions != null) {
transport = new ProtocolVersionOverrideTransport(transport, protocolVersions);
}

McpSchema.Implementation clientInfo =
new McpSchema.Implementation(
"agentscope-java", "AgentScope Java Framework", Version.VERSION);
Expand Down Expand Up @@ -460,6 +508,64 @@ private interface TransportConfig {
McpClientTransport createTransport();
}

/**
* A transport decorator that overrides the protocol versions reported to the MCP client.
*
* <p>The MCP Java SDK's {@code LifecycleInitializer} performs a strict check:
* the server's protocol version must be contained in the client's supported versions list.
* By default, transports only report support for "2024-11-05", causing connections to fail
* with servers that respond with newer versions (e.g., "2025-03-26").
*
* <p>This decorator wraps any {@link McpClientTransport} and overrides
* {@link #protocolVersions()} to return a user-specified list, while delegating
* all other operations to the underlying transport.
*/
private static class ProtocolVersionOverrideTransport implements McpClientTransport {

private final McpClientTransport delegate;
private final List<String> versions;

ProtocolVersionOverrideTransport(McpClientTransport delegate, List<String> versions) {
this.delegate = delegate;
this.versions = versions;
}

@Override
public List<String> protocolVersions() {
return versions;
}

@Override
public Mono<Void> connect(Function<Mono<JSONRPCMessage>, Mono<JSONRPCMessage>> handler) {
return delegate.connect(handler);
}

@Override
public void setExceptionHandler(Consumer<Throwable> handler) {
delegate.setExceptionHandler(handler);
}

@Override
public Mono<Void> closeGracefully() {
return delegate.closeGracefully();
}

@Override
public void close() {
delegate.close();
}

@Override
public Mono<Void> sendMessage(JSONRPCMessage message) {
return delegate.sendMessage(message);
}

@Override
public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return delegate.unmarshalFrom(data, typeRef);
}
}

private static class StdioTransportConfig implements TransportConfig {
private final String command;
private final List<String> args;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1296,4 +1296,124 @@ void testElicitation_BothHandlersSet() {
assertNotNull(syncWrapper);
assertTrue(syncWrapper instanceof McpSyncClientWrapper);
}

// ==================== Protocol Versions Tests ====================

@Test
void testProtocolVersions_SingleVersion() {
McpClientBuilder builder =
McpClientBuilder.create("pv-client")
.stdioTransport("echo", "test")
.protocolVersions("2025-03-26");

assertNotNull(builder);
}

@Test
void testProtocolVersions_MultipleVersions() {
McpClientBuilder builder =
McpClientBuilder.create("pv-client")
.stdioTransport("echo", "test")
.protocolVersions("2024-11-05", "2025-03-26", "2025-06-18");

assertNotNull(builder);
}

@Test
void testProtocolVersions_NullThrows() {
assertThrows(
IllegalArgumentException.class,
() ->
McpClientBuilder.create("pv-client")
.stdioTransport("echo", "test")
.protocolVersions((String[]) null));
}

@Test
void testProtocolVersions_EmptyThrows() {
assertThrows(
IllegalArgumentException.class,
() ->
McpClientBuilder.create("pv-client")
.stdioTransport("echo", "test")
.protocolVersions());
}

@Test
void testProtocolVersions_BuildAsyncWithVersions() {
McpClientBuilder builder =
McpClientBuilder.create("pv-async")
.stdioTransport("echo", "test")
.protocolVersions("2024-11-05", "2025-03-26");

McpClientWrapper wrapper = builder.buildAsync().block();
assertNotNull(wrapper);
assertTrue(wrapper instanceof McpAsyncClientWrapper);
}

@Test
void testProtocolVersions_BuildSyncWithVersions() {
McpClientBuilder builder =
McpClientBuilder.create("pv-sync")
.stdioTransport("echo", "test")
.protocolVersions("2024-11-05", "2025-03-26");

McpClientWrapper wrapper = builder.buildSync();
assertNotNull(wrapper);
assertTrue(wrapper instanceof McpSyncClientWrapper);
}

@Test
void testProtocolVersions_WithSseTransport() {
McpClientBuilder builder =
McpClientBuilder.create("pv-sse")
.sseTransport("https://mcp.example.com/sse")
.protocolVersions("2024-11-05", "2025-03-26");

assertNotNull(builder);
}

@Test
void testProtocolVersions_WithStreamableHttpTransport() {
McpClientBuilder builder =
McpClientBuilder.create("pv-http")
.streamableHttpTransport("https://mcp.example.com/http")
.protocolVersions("2024-11-05", "2025-03-26");

assertNotNull(builder);
}

@Test
@SuppressWarnings("unchecked")
void testProtocolVersions_OverrideTransportIsApplied() throws Exception {
// Verify that when protocolVersions is set, the transport is wrapped
McpClientBuilder builder =
McpClientBuilder.create("pv-verify")
.stdioTransport("echo", "test")
.protocolVersions("2024-11-05", "2025-03-26");

// Use reflection to access the protocolVersions field
Field pvField = McpClientBuilder.class.getDeclaredField("protocolVersions");
pvField.setAccessible(true);
List<String> versions = (List<String>) pvField.get(builder);

assertNotNull(versions);
assertEquals(2, versions.size());
assertEquals("2024-11-05", versions.get(0));
assertEquals("2025-03-26", versions.get(1));
}

@Test
void testProtocolVersions_WithoutSettingUsesDefault() throws Exception {
// Verify that when protocolVersions is NOT set, the field remains null
McpClientBuilder builder =
McpClientBuilder.create("pv-default").stdioTransport("echo", "test");

Field pvField = McpClientBuilder.class.getDeclaredField("protocolVersions");
pvField.setAccessible(true);
Object versions = pvField.get(builder);

// Should be null, meaning the transport's default protocolVersions() is used
assertEquals(null, versions);
}
}
24 changes: 24 additions & 0 deletions docs/en/task/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,30 @@ System.out.println("Received elicit request: " + request.message());

## Managing MCP Clients

### Protocol Version Configuration

By default, the MCP client only supports protocol version `2024-11-05`. If the MCP server responds with a different protocol version during initialization (e.g., `2025-03-26`), the connection will fail with "Unsupported protocol version".

Use `protocolVersions()` to declare support for additional protocol versions:

```java
// Support multiple protocol versions
McpClientWrapper client = McpClientBuilder.create("mcp")
.stdioTransport("python", "server.py")
.protocolVersions("2024-11-05", "2025-03-26")
.buildAsync()
.block();

// Works with any transport type
McpClientWrapper sseClient = McpClientBuilder.create("mcp")
.sseTransport("https://mcp.example.com/sse")
.protocolVersions("2024-11-05", "2025-03-26", "2025-06-18")
.buildAsync()
.block();
```

> **Note**: This is useful when connecting to third-party MCP servers that may use newer protocol versions. The MCP specification defines protocol version negotiation as a client-server handshake where the server may respond with a different version than the client requested.

### List Tools from MCP Server

```java
Expand Down
24 changes: 24 additions & 0 deletions docs/zh/task/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,30 @@ System.out.println("Received elicit request: " + request.message());

## 管理 MCP 客户端

### 协议版本配置

默认情况下,MCP 客户端仅支持协议版本 `2024-11-05`。如果 MCP 服务器在初始化时返回不同的协议版本(例如 `2025-03-26`),连接将失败并报错 "Unsupported protocol version"。

使用 `protocolVersions()` 声明支持的协议版本:

```java
// 支持多个协议版本
McpClientWrapper client = McpClientBuilder.create("mcp")
.stdioTransport("python", "server.py")
.protocolVersions("2024-11-05", "2025-03-26")
.buildAsync()
.block();

// 适用于任何传输类型
McpClientWrapper sseClient = McpClientBuilder.create("mcp")
.sseTransport("https://mcp.example.com/sse")
.protocolVersions("2024-11-05", "2025-03-26", "2025-06-18")
.buildAsync()
.block();
```

> **注意**:当连接使用较新协议版本的第三方 MCP 服务器时,此功能非常有用。MCP 规范将协议版本协商定义为客户端-服务器握手过程,服务器可能返回与客户端请求不同的版本。
### 列出 MCP 服务器的工具

```java
Expand Down
Loading