原文链接:SpringAI(GA):MCP源码解读 说明;因掘金字符数限制,本篇删减了StdioClientTransport(StdioServerTransportProvider)、HttpClientSseClientTransport(HttpServletSseServerTransportProvider)、McpServerFeatures 部分内容,完整内容可见原文链接
教程说明
说明:本教程将采用2025年5月20日正式的GA版,给出如下内容
- 核心功能模块的快速上手教程
- 核心功能模块的源码级解读
- Spring ai alibaba增强的快速上手教程 + 源码级解读
版本:JDK21 + SpringBoot3.4.5 + SpringAI 1.0.0 + SpringAI Alibaba 1.0.0.2
将陆续完成如下章节教程。本章是第七章(MCP使用范式)下的MCP源码解读
代码开源如下:github.com/GTyingzi/sp...

微信推文往届解读可参考:
第一章内容
SpringAI(GA)的chat:快速上手+自动注入源码解读
第二章内容
SpringAI(GA):Sqlite、Mysql、Redis消息存储快速上手
第三章内容
第五章内容
SpringAI(GA):内存、Redis、ES的向量数据库存储---快速上手
SpringAI(GA):向量数据库理论源码解读+Redis、Es接入源码
第六章内容
第七章内容
整理不易,获取更好的观赏体验,可付费获取飞书云文档Spring AI最新教程权限,目前49.9,随着内容不断完善,会逐步涨价。
注:M6版快速上手教程+源码解读飞书云文档已免费提供
为鼓励大家积极参与为Spring Ai Alibaba开源社区 :github.com/alibaba/spr...
MCP 源码解读
!TIP\] 本文档是 Java 实现 MCP 的 0.10.0 版本
Spring AI 结合 MCP 调用链路源码可见《SpringAI 下的 MCP 链路解读》
pom.xml
java
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.10.0</version>
</dependency>
MCP 各类说明

McpTransport
该接口定义了一个异步传输层,用于实现模型的上下文协议的双向通信,设计目标是基于 JSON-RPC 格式的异步消息交换,并且与协议无关,可通过不同的传输机制(入 WebSocket、HTTP 或自定义协议)实现
|-----------------|----------------------------------------------------------------------------|
| 方法名称 | 描述 |
| close | 关闭传输连接并释放相关资源,提供默认实现,调用closeGracefully()方法完成资源清理 |
| closeGracefully | 异步关闭传输连接并释放资源 |
| sendMessage | 以异步方式向对端发送消息 |
| unmarshalFrom | 将给定的数据反序列化为指定类型的对象 - Object data:需要反序列化的数据 - TypeReference typeRef:目标对象的类型 |
java
package io.modelcontextprotocol.spec;
import com.fasterxml.jackson.core.type.TypeReference;
import reactor.core.publisher.Mono;
public interface McpTransport {
default void close() {
this.closeGracefully().subscribe();
}
Mono<Void> closeGracefully();
Mono<Void> sendMessage(McpSchema.JSONRPCMessage message);
<T> T unmarshalFrom(Object data, TypeReference<T> typeRef);
}
McpClientTransport
用于定义客户端侧的 MCP 传输层,继承自 McpTransport 接口类
connect 方法:建立客户端与服务端的连接,并定义消息处理逻辑
java
package io.modelcontextprotocol.spec;
import java.util.function.Function;
import reactor.core.publisher.Mono;
public interface McpClientTransport extends McpTransport {
Mono<Void> connect(Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler);
}
WebFluxSseClientTransport
基于 Spring WebFlux 框架,使用 SSE 协议实现 MCP 客户端传输层,主要功能如下:
- 接收消息:通过 SSE 连接从服务器接收消息
- 发送消息:通过 HTTP POST 请求向服务器发送消息
- 遵循 MCP HTTP 与 SSE 传输规范:支持 JSON 序列化和反序列化,处理 JSON-RPC 格式的消息
各字段含义
String MESSAGEEVENTTYPE = "message"
:SSE 事件类型,用于接收 JSON-RPC 消息String ENDPOINTEVENTTYPE = "endpoint"
:SSE 事件类型,用于接收服务器提供的消息发送端点 URIString DEFAULTSSEENDPOINT = "/sse"
:默认的 SSE 连接端点路径ParameterizedTypeReference<ServerSentEvent<String>> SSETYPE
:用于解析 SSE 事件中包含字符串数据的类型引用WebClient webClient
:用于处理 SSE 连接和 HTTP POST 请求的 WebClient 实例ObjectMapper objectMapper
:用于 JSON 序列化和反序列化的 ObjectMapper 实例Disposable inboundSubscription
:管理 SSE 连接的订阅,用于在关闭时清理资源volatile boolean isClosing = false
:标志传输是否正在关闭,防止关闭期间执行新操作Sinks.One<String> messageEndpointSink = Sinks.one()
:储服务器提供的消息发送端点 URIString sseEndpoint
:SSE 连接的端点 URI
对外暴露的方法
|-----------------|------------------------------------------|
| 方法名称 | 描述 |
| connect | 建立SSE连接,处理服务器发送的消息,并设置消息处理逻辑 |
| sendMessage | 通过HTTP POST请求向服务器发送JSON-RPC消息 |
| eventStream | 初始化并启动入站的SSE事件处理,它通过建立SSE连接来接收服务器发送的事件 |
| closeGracefully | 优雅地关闭传输连接,清理资源 |
| unmarshalFrom | 将数据反序列化为指定类型的对象 |
| builder | 创建WebFluxSseClientTransport的构建器,用于定制化实例化 |
java
package io.modelcontextprotocol.client.transport;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.util.Assert;
import java.io.IOException;
import java.util.function.BiConsumer;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.core.publisher.SynchronousSink;
import reactor.core.scheduler.Schedulers;
import reactor.util.retry.Retry;
public class WebFluxSseClientTransport implements McpClientTransport {
private static final Logger logger = LoggerFactory.getLogger(WebFluxSseClientTransport.class);
private static final String MESSAGEEVENTTYPE = "message";
private static final String ENDPOINTEVENTTYPE = "endpoint";
private static final String DEFAULTSSEENDPOINT = "/sse";
private static final ParameterizedTypeReference<ServerSentEvent<String>> SSETYPE = new ParameterizedTypeReference<ServerSentEvent<String>>() {
};
private final WebClient webClient;
protected ObjectMapper objectMapper;
private Disposable inboundSubscription;
private volatile boolean isClosing;
protected final Sinks.One<String> messageEndpointSink;
private String sseEndpoint;
private BiConsumer<Retry.RetrySignal, SynchronousSink<Object>> inboundRetryHandler;
public WebFluxSseClientTransport(WebClient.Builder webClientBuilder) {
this(webClientBuilder, new ObjectMapper());
}
public WebFluxSseClientTransport(WebClient.Builder webClientBuilder, ObjectMapper objectMapper) {
this(webClientBuilder, objectMapper, "/sse");
}
public WebFluxSseClientTransport(WebClient.Builder webClientBuilder, ObjectMapper objectMapper, String sseEndpoint) {
this.isClosing = false;
this.messageEndpointSink = Sinks.one();
this.inboundRetryHandler = (retrySpec, sink) -> {
if (this.isClosing) {
logger.debug("SSE connection closed during shutdown");
sink.error(retrySpec.failure());
} else if (retrySpec.failure() instanceof IOException) {
logger.debug("Retrying SSE connection after IO error");
sink.next(retrySpec);
} else {
logger.error("Fatal SSE error, not retrying: {}", retrySpec.failure().getMessage());
sink.error(retrySpec.failure());
}
};
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Assert.notNull(webClientBuilder, "WebClient.Builder must not be null");
Assert.hasText(sseEndpoint, "SSE endpoint must not be null or empty");
this.objectMapper = objectMapper;
this.webClient = webClientBuilder.build();
this.sseEndpoint = sseEndpoint;
}
public Mono<Void> connect(Function<Mono<McpSchema.JSONRPCMessage>, Mono<McpSchema.JSONRPCMessage>> handler) {
Flux<ServerSentEvent<String>> events = this.eventStream();
this.inboundSubscription = events.concatMap((event) -> Mono.just(event).handle((e, s) -> {
if ("endpoint".equals(event.event())) {
String messageEndpointUri = (String)event.data();
if (this.messageEndpointSink.tryEmitValue(messageEndpointUri).isSuccess()) {
s.complete();
} else {
s.error(new McpError("Failed to handle SSE endpoint event"));
}
} else if ("message".equals(event.event())) {
try {
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, (String)event.data());
s.next(message);
} catch (IOException ioException) {
s.error(ioException);
}
} else {
s.error(new McpError("Received unrecognized SSE event type: " + event.event()));
}
}).transform(handler)).subscribe();
return this.messageEndpointSink.asMono().then();
}
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return this.messageEndpointSink.asMono().flatMap((messageEndpointUri) -> {
if (this.isClosing) {
return Mono.empty();
} else {
try {
String jsonText = this.objectMapper.writeValueAsString(message);
return ((WebClient.RequestBodySpec)this.webClient.post().uri(messageEndpointUri, new Object[0])).contentType(MediaType.APPLICATIONJSON).bodyValue(jsonText).retrieve().toBodilessEntity().doOnSuccess((response) -> logger.debug("Message sent successfully")).doOnError((error) -> {
if (!this.isClosing) {
logger.error("Error sending message: {}", error.getMessage());
}
});
} catch (IOException e) {
return !this.isClosing ? Mono.error(new RuntimeException("Failed to serialize message", e)) : Mono.empty();
}
}
}).then();
}
protected Flux<ServerSentEvent<String>> eventStream() {
return this.webClient.get().uri(this.sseEndpoint, new Object[0]).accept(new MediaType[]{MediaType.TEXTEVENTSTREAM}).retrieve().bodyToFlux(SSETYPE).retryWhen(Retry.from((retrySignal) -> retrySignal.handle(this.inboundRetryHandler)));
}
public Mono<Void> closeGracefully() {
return Mono.fromRunnable(() -> {
this.isClosing = true;
if (this.inboundSubscription != null) {
this.inboundSubscription.dispose();
}
}).then().subscribeOn(Schedulers.boundedElastic());
}
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return (T)this.objectMapper.convertValue(data, typeRef);
}
public static Builder builder(WebClient.Builder webClientBuilder) {
return new Builder(webClientBuilder);
}
public static class Builder {
private final WebClient.Builder webClientBuilder;
private String sseEndpoint = "/sse";
private ObjectMapper objectMapper = new ObjectMapper();
public Builder(WebClient.Builder webClientBuilder) {
Assert.notNull(webClientBuilder, "WebClient.Builder must not be null");
this.webClientBuilder = webClientBuilder;
}
public Builder sseEndpoint(String sseEndpoint) {
Assert.hasText(sseEndpoint, "sseEndpoint must not be empty");
this.sseEndpoint = sseEndpoint;
return this;
}
public Builder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper must not be null");
this.objectMapper = objectMapper;
return this;
}
public WebFluxSseClientTransport build() {
return new WebFluxSseClientTransport(this.webClientBuilder, this.objectMapper, this.sseEndpoint);
}
}
}
McpServerTransport(McpServerTransportProvider)
McpServerTransport 是服务端传输层的标记接口,定义了服务端通信的基础功能
java
package io.modelcontextprotocol.spec;
public interface McpServerTransport extends McpTransport {
}
McpServerTransportProvider 是服务端传输层的核心接口,负责会话管理、消息广播和资源清理
|-------------------|----------------------|
| 方法名称 | 描述 |
| setSessionFactory | 设置会话工厂,用于创建新的服务端会话 |
| notifyClients | 向所有活跃客户端广播JSON-RPC消息 |
| clsoe | 立即关闭所有传输层连接并释放资源 |
| closeGracefully | 优雅地关闭所有活跃会话,清理资源 |
java
package io.modelcontextprotocol.spec;
import reactor.core.publisher.Mono;
public interface McpServerTransportProvider {
void setSessionFactory(McpServerSession.Factory sessionFactory);
Mono<Void> notifyClients(String method, Object params);
default void close() {
this.closeGracefully().subscribe();
}
Mono<Void> closeGracefully();
}
WebFluxSseServerTransportProvider
该类是服务端实现的 MCP 传输层(内部类 WebFluxMcpSessionTransport 实现 McpServerTransport),基于 Spring WebFlux 框架,使用 SSE 协议实现双向通信。它负责管理客户端会话,处理消息的接收与发送,并提供可靠的消息广播功能,主要功能如下:
- SSE 连接管理:通过 SSE 建立服务端到客户端的实时消息通道
- 消息接收与处理:通过 HTTP POST 接收客户端发送的 JSON-RPC 消息
- 消息广播:支持将消息推送到所有活跃的客户端会话
- 会话管理:维护客户端会话的生命周期,支持资源清理和优雅关闭
- 线程安全:使用 ConcurrentHashMap 管理会话,确保多客户端连接的安全性
各字段含义
ObjectMapper objectMapper
:用于 JSON 序列化和反序列化的 ObjectMapper 实例String baseUrl
:消息端点的基础 URL,用于构建客户端发送消息的完整路径,默认为""String messageEndpoint
:客户端发送 JSON-RPC 消息的端点 URI,默认为"/mcp/message"String sseEndpoint
:服务端接收 SSE 连接的端点 URI,默认为"/sse"RouterFunction<?> routerFunction
:定义 HTTP 路由的 RouterFunction,包括 SSE 和消息端点McpServerSession.Factory sessionFactory
:会话工厂,用于创建新的服务端会话ConcurrentHashMap<String, McpServerSession> sessions
:存储活跃客户端会话的线程安全映射,键为会话 IDboolean isClosing
:标志传输是否正在关闭,防止关闭期间接受新连接
对外暴露的方法
|-------------------|--------------------------------------------------|
| 方法名称 | 描述 |
| setSessionFactory | 设置会话工厂,用于创建新的服务端会话 |
| notifyClients | 向所有活跃客户端广播JSON-RPC消息 |
| closeGracefully | 优雅地关闭所有活跃会话,清理资源 |
| getRouterFunction | 返回定义SSE和消息端点的路由函数 |
| builder | Builder方式创建WebFluxSseServerTransportProvider实例对象 |
java
package io.modelcontextprotocol.server.transport;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpServerSession;
import io.modelcontextprotocol.spec.McpServerTransport;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import io.modelcontextprotocol.util.Assert;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;
import reactor.core.publisher.Mono;
public class WebFluxSseServerTransportProvider implements McpServerTransportProvider {
private static final Logger logger = LoggerFactory.getLogger(WebFluxSseServerTransportProvider.class);
public static final String MESSAGEEVENTTYPE = "message";
public static final String ENDPOINTEVENTTYPE = "endpoint";
public static final String DEFAULTSSEENDPOINT = "/sse";
public static final String DEFAULTBASEURL = "";
private final ObjectMapper objectMapper;
private final String baseUrl;
private final String messageEndpoint;
private final String sseEndpoint;
private final RouterFunction<?> routerFunction;
private McpServerSession.Factory sessionFactory;
private final ConcurrentHashMap<String, McpServerSession> sessions;
private volatile boolean isClosing;
public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String messageEndpoint) {
this(objectMapper, messageEndpoint, "/sse");
}
public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String messageEndpoint, String sseEndpoint) {
this(objectMapper, "", messageEndpoint, sseEndpoint);
}
public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint) {
this.sessions = new ConcurrentHashMap();
this.isClosing = false;
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Assert.notNull(baseUrl, "Message base path must not be null");
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
this.objectMapper = objectMapper;
this.baseUrl = baseUrl;
this.messageEndpoint = messageEndpoint;
this.sseEndpoint = sseEndpoint;
this.routerFunction = RouterFunctions.route().GET(this.sseEndpoint, this::handleSseConnection).POST(this.messageEndpoint, this::handleMessage).build();
}
public void setSessionFactory(McpServerSession.Factory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Mono<Void> notifyClients(String method, Object params) {
if (this.sessions.isEmpty()) {
logger.debug("No active sessions to broadcast message to");
return Mono.empty();
} else {
logger.debug("Attempting to broadcast message to {} active sessions", this.sessions.size());
return Flux.fromIterable(this.sessions.values()).flatMap((session) -> session.sendNotification(method, params).doOnError((e) -> logger.error("Failed to send message to session {}: {}", session.getId(), e.getMessage())).onErrorComplete()).then();
}
}
public Mono<Void> closeGracefully() {
return Flux.fromIterable(this.sessions.values()).doFirst(() -> logger.debug("Initiating graceful shutdown with {} active sessions", this.sessions.size())).flatMap(McpServerSession::closeGracefully).then();
}
public RouterFunction<?> getRouterFunction() {
return this.routerFunction;
}
private Mono<ServerResponse> handleSseConnection(ServerRequest request) {
return this.isClosing ? ServerResponse.status(HttpStatus.SERVICEUNAVAILABLE).bodyValue("Server is shutting down") : ServerResponse.ok().contentType(MediaType.TEXTEVENTSTREAM).body(Flux.create((sink) -> {
WebFluxMcpSessionTransport sessionTransport = new WebFluxMcpSessionTransport(sink);
McpServerSession session = this.sessionFactory.create(sessionTransport);
String sessionId = session.getId();
logger.debug("Created new SSE connection for session: {}", sessionId);
this.sessions.put(sessionId, session);
logger.debug("Sending initial endpoint event to session: {}", sessionId);
sink.next(ServerSentEvent.builder().event("endpoint").data(this.baseUrl + this.messageEndpoint + "?sessionId=" + sessionId).build());
sink.onCancel(() -> {
logger.debug("Session {} cancelled", sessionId);
this.sessions.remove(sessionId);
});
}), ServerSentEvent.class);
}
private Mono<ServerResponse> handleMessage(ServerRequest request) {
if (this.isClosing) {
return ServerResponse.status(HttpStatus.SERVICEUNAVAILABLE).bodyValue("Server is shutting down");
} else if (request.queryParam("sessionId").isEmpty()) {
return ServerResponse.badRequest().bodyValue(new McpError("Session ID missing in message endpoint"));
} else {
McpServerSession session = (McpServerSession)this.sessions.get(request.queryParam("sessionId").get());
return session == null ? ServerResponse.status(HttpStatus.NOTFOUND).bodyValue(new McpError("Session not found: " + (String)request.queryParam("sessionId").get())) : request.bodyToMono(String.class).flatMap((body) -> {
try {
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(this.objectMapper, body);
return session.handle(message).flatMap((response) -> ServerResponse.ok().build()).onErrorResume((error) -> {
logger.error("Error processing message: {}", error.getMessage());
return ServerResponse.status(HttpStatus.INTERNALSERVERERROR).bodyValue(new McpError(error.getMessage()));
});
} catch (IOException | IllegalArgumentException e) {
logger.error("Failed to deserialize message: {}", ((Exception)e).getMessage());
return ServerResponse.badRequest().bodyValue(new McpError("Invalid message format"));
}
});
}
}
public static Builder builder() {
return new Builder();
}
private class WebFluxMcpSessionTransport implements McpServerTransport {
private final FluxSink<ServerSentEvent<?>> sink;
public WebFluxMcpSessionTransport(FluxSink<ServerSentEvent<?>> sink) {
this.sink = sink;
}
public Mono<Void> sendMessage(McpSchema.JSONRPCMessage message) {
return Mono.fromSupplier(() -> {
try {
return WebFluxSseServerTransportProvider.this.objectMapper.writeValueAsString(message);
} catch (IOException e) {
throw Exceptions.propagate(e);
}
}).doOnNext((jsonText) -> {
ServerSentEvent<Object> event = ServerSentEvent.builder().event("message").data(jsonText).build();
this.sink.next(event);
}).doOnError((e) -> {
Throwable exception = Exceptions.unwrap(e);
this.sink.error(exception);
}).then();
}
public <T> T unmarshalFrom(Object data, TypeReference<T> typeRef) {
return (T)WebFluxSseServerTransportProvider.this.objectMapper.convertValue(data, typeRef);
}
public Mono<Void> closeGracefully() {
FluxSink var10000 = this.sink;
Objects.requireNonNull(var10000);
return Mono.fromRunnable(var10000::complete);
}
public void close() {
this.sink.complete();
}
}
public static class Builder {
private ObjectMapper objectMapper;
private String baseUrl = "";
private String messageEndpoint;
private String sseEndpoint = "/sse";
public Builder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
return this;
}
public Builder basePath(String baseUrl) {
Assert.notNull(baseUrl, "basePath must not be null");
this.baseUrl = baseUrl;
return this;
}
public Builder messageEndpoint(String messageEndpoint) {
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
this.messageEndpoint = messageEndpoint;
return this;
}
public Builder sseEndpoint(String sseEndpoint) {
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
this.sseEndpoint = sseEndpoint;
return this;
}
public WebFluxSseServerTransportProvider build() {
Assert.notNull(this.objectMapper, "ObjectMapper must be set");
Assert.notNull(this.messageEndpoint, "Message endpoint must be set");
return new WebFluxSseServerTransportProvider(this.objectMapper, this.baseUrl, this.messageEndpoint, this.sseEndpoint);
}
}
}
McpSession
MCP 会话,用于处理客户端与服务端之间的通信。它定义了会话的生命周期管理以及消息交互的核心功能,支持异步操作,基于 Project Reactor 的 Mono 实现非阻塞通信,主要功能如下
- 请求-响应模式:支持发送请求并接收响应
- 通知模式:支持发送无需响应的通知消息
- 会话管理:提供会话关闭和资源释放的功能
- 异步通信:通过 Mono 实现非阻塞的消息交互
|------------------|----------------|
| 方法名称 | 描述 |
| sendRequest | 发送请求并接收指定类型的响应 |
| sendNotification | 发送无需参数的通知消息 |
| sendNotification | 发送带参数的通知消息 |
| closeGracefully | 异步关闭会话并释放资源 |
| close | 立即关闭会话并释放资源 |
java
package io.modelcontextprotocol.spec;
import com.fasterxml.jackson.core.type.TypeReference;
import reactor.core.publisher.Mono;
public interface McpSession {
<T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef);
default Mono<Void> sendNotification(String method) {
return this.sendNotification(method, (Object)null);
}
Mono<Void> sendNotification(String method, Object params);
Mono<Void> closeGracefully();
void close();
}
McpClientSession
客户端会话实现类,负责管理与服务端之间的双向 JSON-RPC 通信,主要功能如下:
- 请求/响应处理:支持发送请求并接收响应,确保消息的唯一性和正确性。
- 通知处理:支持发送无需响应的通知消息。
- 消息超时管理:通过配置超时时间,确保请求不会无限等待。
- 传输层抽象:通过 McpClientTransport 实现消息的发送和接收。
- 会话管理:提供会话的生命周期管理,包括优雅关闭和立即关闭。
各字段含义
Duration requestTimeout
:请求超时时间,等待响应的最大时长McpClientTransport transport
:传输层实现,用于消息的发送和接收ConcurrentHashMap<Object, MonoSink<McpSchema.JSONRPCResponse>> pendingResponses
:存储待处理响应的映射,键为请求 ID,值为响应的回调ConcurrentHashMap<String, RequestHandler<?>> requestHandlers
:存储请求处理器的映射,键为方法名称,值为对应的处理逻辑ConcurrentHashMap<String, NotificationHandler> notificationHandlers
:存储通知处理器的映射,键为方法名称,值为对应的处理逻辑String sessionPrefix
:会话特定的请求 ID 前缀,用于生成唯一请求 IDAtomicLong requestCounter
:用于生成唯一请求 ID 的计数器Disposable connection
:管理与传输层的连接,负责监听和处理消息
java
package io.modelcontextprotocol.spec;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.util.Assert;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;
public class McpClientSession implements McpSession {
private static final Logger logger = LoggerFactory.getLogger(McpClientSession.class);
private final Duration requestTimeout;
private final McpClientTransport transport;
private final ConcurrentHashMap<Object, MonoSink<McpSchema.JSONRPCResponse>> pendingResponses = new ConcurrentHashMap();
private final ConcurrentHashMap<String, RequestHandler<?>> requestHandlers = new ConcurrentHashMap();
private final ConcurrentHashMap<String, NotificationHandler> notificationHandlers = new ConcurrentHashMap();
private final String sessionPrefix = UUID.randomUUID().toString().substring(0, 8);
private final AtomicLong requestCounter = new AtomicLong(0L);
private final Disposable connection;
public McpClientSession(Duration requestTimeout, McpClientTransport transport, Map<String, RequestHandler<?>> requestHandlers, Map<String, NotificationHandler> notificationHandlers) {
Assert.notNull(requestTimeout, "The requestTimeout can not be null");
Assert.notNull(transport, "The transport can not be null");
Assert.notNull(requestHandlers, "The requestHandlers can not be null");
Assert.notNull(notificationHandlers, "The notificationHandlers can not be null");
this.requestTimeout = requestTimeout;
this.transport = transport;
this.requestHandlers.putAll(requestHandlers);
this.notificationHandlers.putAll(notificationHandlers);
this.connection = this.transport.connect((mono) -> mono.doOnNext(this::handle)).subscribe();
}
private void handle(McpSchema.JSONRPCMessage message) {
if (message instanceof McpSchema.JSONRPCResponse response) {
logger.debug("Received Response: {}", response);
MonoSink<McpSchema.JSONRPCResponse> sink = (MonoSink)this.pendingResponses.remove(response.id());
if (sink == null) {
logger.warn("Unexpected response for unknown id {}", response.id());
} else {
sink.success(response);
}
} else if (message instanceof McpSchema.JSONRPCRequest request) {
logger.debug("Received request: {}", request);
Mono var10000 = this.handleIncomingRequest(request).onErrorResume((error) -> {
McpSchema.JSONRPCResponse errorResponse = new McpSchema.JSONRPCResponse("2.0", request.id(), (Object)null, new McpSchema.JSONRPCResponse.JSONRPCError(-32603, error.getMessage(), (Object)null));
return this.transport.sendMessage(errorResponse).then(Mono.empty());
});
McpClientTransport var10001 = this.transport;
Objects.requireNonNull(var10001);
var10000.flatMap(var10001::sendMessage).subscribe();
} else if (message instanceof McpSchema.JSONRPCNotification notification) {
logger.debug("Received notification: {}", notification);
this.handleIncomingNotification(notification).doOnError((error) -> logger.error("Error handling notification: {}", error.getMessage())).subscribe();
} else {
logger.warn("Received unknown message type: {}", message);
}
}
private Mono<McpSchema.JSONRPCResponse> handleIncomingRequest(McpSchema.JSONRPCRequest request) {
return Mono.defer(() -> {
RequestHandler<?> handler = (RequestHandler)this.requestHandlers.get(request.method());
if (handler == null) {
MethodNotFoundError error = this.getMethodNotFoundError(request.method());
return Mono.just(new McpSchema.JSONRPCResponse("2.0", request.id(), (Object)null, new McpSchema.JSONRPCResponse.JSONRPCError(-32601, error.message(), error.data())));
} else {
return handler.handle(request.params()).map((result) -> new McpSchema.JSONRPCResponse("2.0", request.id(), result, (McpSchema.JSONRPCResponse.JSONRPCError)null)).onErrorResume((errorx) -> Mono.just(new McpSchema.JSONRPCResponse("2.0", request.id(), (Object)null, new McpSchema.JSONRPCResponse.JSONRPCError(-32603, errorx.getMessage(), (Object)null))));
}
});
}
private MethodNotFoundError getMethodNotFoundError(String method) {
switch (method) {
case "roots/list" -> {
return new MethodNotFoundError(method, "Roots not supported", Map.of("reason", "Client does not have roots capability"));
}
default -> {
return new MethodNotFoundError(method, "Method not found: " + method, (Object)null);
}
}
}
private Mono<Void> handleIncomingNotification(McpSchema.JSONRPCNotification notification) {
return Mono.defer(() -> {
NotificationHandler handler = (NotificationHandler)this.notificationHandlers.get(notification.method());
if (handler == null) {
logger.error("No handler registered for notification method: {}", notification.method());
return Mono.empty();
} else {
return handler.handle(notification.params());
}
});
}
private String generateRequestId() {
String var10000 = this.sessionPrefix;
return var10000 + "-" + this.requestCounter.getAndIncrement();
}
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef) {
String requestId = this.generateRequestId();
return Mono.deferContextual((ctx) -> Mono.create((sink) -> {
this.pendingResponses.put(requestId, sink);
McpSchema.JSONRPCRequest jsonrpcRequest = new McpSchema.JSONRPCRequest("2.0", method, requestId, requestParams);
this.transport.sendMessage(jsonrpcRequest).contextWrite(ctx).subscribe((v) -> {
}, (error) -> {
this.pendingResponses.remove(requestId);
sink.error(error);
});
})).timeout(this.requestTimeout).handle((jsonRpcResponse, sink) -> {
if (jsonRpcResponse.error() != null) {
logger.error("Error handling request: {}", jsonRpcResponse.error());
sink.error(new McpError(jsonRpcResponse.error()));
} else if (typeRef.getType().equals(Void.class)) {
sink.complete();
} else {
sink.next(this.transport.unmarshalFrom(jsonRpcResponse.result(), typeRef));
}
});
}
public Mono<Void> sendNotification(String method, Object params) {
McpSchema.JSONRPCNotification jsonrpcNotification = new McpSchema.JSONRPCNotification("2.0", method, params);
return this.transport.sendMessage(jsonrpcNotification);
}
public Mono<Void> closeGracefully() {
return Mono.defer(() -> {
this.connection.dispose();
return this.transport.closeGracefully();
});
}
public void close() {
this.connection.dispose();
this.transport.close();
}
static record MethodNotFoundError(String method, String message, Object data) {
}
@FunctionalInterface
public interface NotificationHandler {
Mono<Void> handle(Object params);
}
@FunctionalInterface
public interface RequestHandler<T> {
Mono<T> handle(Object params);
}
}
McpServerSession
服务端会话管理类,负责管理与客户端的双向 JSON-RPC 通信,主要功能如下:
- 请求/响应处理:支持发送请求并接收响应,确保消息的唯一性和正确性
- 通知处理:支持发送无需响应的通知消息
- 会话初始化:管理客户端与服务端的初始化过程,包括能力协商和信息交换
- 传输层抽象:通过 McpServerTransport 实现消息的发送和接收
- 会话管理:提供会话的生命周期管理,包括优雅关闭和立即关闭
各字段含义
String id
:会话唯一标识符,用于区分不同的会话Duration requestTimeout
:请求超时时间,等待响应的最大时长AtomicLong requestCounter
:用于生成唯一请求 ID 的计数器InitRequestHandler initRequestHandler
:存储待处理响应的映射,键为请求 ID,值为响应的回调InitNotificationHandler initNotificationHandler
:处理初始化请求的处理器Map<String, RequestHandler<?>> requestHandlers
:存储请求处理器的映射,键为方法名称,值为对应的处理逻辑Map<String, NotificationHandler> notificationHandlers
:存储通知处理器的映射,键为方法名称,值为对应的处理逻辑McpServerTransport transport
:传输层实现,用于消息的发送和接收Sinks.One<McpAsyncServerExchange> exchangeSink
:用于管理服务端与客户端的交互状态AtomicReference<McpSchema.ClientCapabilities> clientCapabilities
:存储客户端的能力信息AtomicReference<McpSchema.Implementation> clientInfo
:存储客户端的实现信息AtomicInteger state
:会话状态,未初始化、初始化中或已初始化
java
package io.modelcontextprotocol.spec;
import com.fasterxml.jackson.core.type.TypeReference;
import io.modelcontextprotocol.server.McpAsyncServerExchange;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;
import reactor.core.publisher.Sinks;
public class McpServerSession implements McpSession {
private static final Logger logger = LoggerFactory.getLogger(McpServerSession.class);
private final ConcurrentHashMap<Object, MonoSink<McpSchema.JSONRPCResponse>> pendingResponses = new ConcurrentHashMap();
private final String id;
private final Duration requestTimeout;
private final AtomicLong requestCounter = new AtomicLong(0L);
private final InitRequestHandler initRequestHandler;
private final InitNotificationHandler initNotificationHandler;
private final Map<String, RequestHandler<?>> requestHandlers;
private final Map<String, NotificationHandler> notificationHandlers;
private final McpServerTransport transport;
private final Sinks.One<McpAsyncServerExchange> exchangeSink = Sinks.one();
private final AtomicReference<McpSchema.ClientCapabilities> clientCapabilities = new AtomicReference();
private final AtomicReference<McpSchema.Implementation> clientInfo = new AtomicReference();
private static final int STATEUNINITIALIZED = 0;
private static final int STATEINITIALIZING = 1;
private static final int STATEINITIALIZED = 2;
private final AtomicInteger state = new AtomicInteger(0);
public McpServerSession(String id, Duration requestTimeout, McpServerTransport transport, InitRequestHandler initHandler, InitNotificationHandler initNotificationHandler, Map<String, RequestHandler<?>> requestHandlers, Map<String, NotificationHandler> notificationHandlers) {
this.id = id;
this.requestTimeout = requestTimeout;
this.transport = transport;
this.initRequestHandler = initHandler;
this.initNotificationHandler = initNotificationHandler;
this.requestHandlers = requestHandlers;
this.notificationHandlers = notificationHandlers;
}
public String getId() {
return this.id;
}
public void init(McpSchema.ClientCapabilities clientCapabilities, McpSchema.Implementation clientInfo) {
this.clientCapabilities.lazySet(clientCapabilities);
this.clientInfo.lazySet(clientInfo);
}
private String generateRequestId() {
String var10000 = this.id;
return var10000 + "-" + this.requestCounter.getAndIncrement();
}
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef) {
String requestId = this.generateRequestId();
return Mono.create((sink) -> {
this.pendingResponses.put(requestId, sink);
McpSchema.JSONRPCRequest jsonrpcRequest = new McpSchema.JSONRPCRequest("2.0", method, requestId, requestParams);
this.transport.sendMessage(jsonrpcRequest).subscribe((v) -> {
}, (error) -> {
this.pendingResponses.remove(requestId);
sink.error(error);
});
}).timeout(this.requestTimeout).handle((jsonRpcResponse, sink) -> {
if (jsonRpcResponse.error() != null) {
sink.error(new McpError(jsonRpcResponse.error()));
} else if (typeRef.getType().equals(Void.class)) {
sink.complete();
} else {
sink.next(this.transport.unmarshalFrom(jsonRpcResponse.result(), typeRef));
}
});
}
public Mono<Void> sendNotification(String method, Object params) {
McpSchema.JSONRPCNotification jsonrpcNotification = new McpSchema.JSONRPCNotification("2.0", method, params);
return this.transport.sendMessage(jsonrpcNotification);
}
public Mono<Void> handle(McpSchema.JSONRPCMessage message) {
return Mono.defer(() -> {
if (message instanceof McpSchema.JSONRPCResponse response) {
logger.debug("Received Response: {}", response);
MonoSink<McpSchema.JSONRPCResponse> sink = (MonoSink)this.pendingResponses.remove(response.id());
if (sink == null) {
logger.warn("Unexpected response for unknown id {}", response.id());
} else {
sink.success(response);
}
return Mono.empty();
} else if (message instanceof McpSchema.JSONRPCRequest request) {
logger.debug("Received request: {}", request);
Mono var10000 = this.handleIncomingRequest(request).onErrorResume((error) -> {
McpSchema.JSONRPCResponse errorResponse = new McpSchema.JSONRPCResponse("2.0", request.id(), (Object)null, new McpSchema.JSONRPCResponse.JSONRPCError(-32603, error.getMessage(), (Object)null));
return this.transport.sendMessage(errorResponse).then(Mono.empty());
});
McpServerTransport var10001 = this.transport;
Objects.requireNonNull(var10001);
return var10000.flatMap(var10001::sendMessage);
} else if (message instanceof McpSchema.JSONRPCNotification notification) {
logger.debug("Received notification: {}", notification);
return this.handleIncomingNotification(notification).doOnError((error) -> logger.error("Error handling notification: {}", error.getMessage()));
} else {
logger.warn("Received unknown message type: {}", message);
return Mono.empty();
}
});
}
private Mono<McpSchema.JSONRPCResponse> handleIncomingRequest(McpSchema.JSONRPCRequest request) {
return Mono.defer(() -> {
Mono<?> resultMono;
if ("initialize".equals(request.method())) {
McpSchema.InitializeRequest initializeRequest = (McpSchema.InitializeRequest)this.transport.unmarshalFrom(request.params(), new TypeReference<McpSchema.InitializeRequest>() {
});
this.state.lazySet(1);
this.init(initializeRequest.capabilities(), initializeRequest.clientInfo());
resultMono = this.initRequestHandler.handle(initializeRequest);
} else {
RequestHandler<?> handler = (RequestHandler)this.requestHandlers.get(request.method());
if (handler == null) {
MethodNotFoundError error = this.getMethodNotFoundError(request.method());
return Mono.just(new McpSchema.JSONRPCResponse("2.0", request.id(), (Object)null, new McpSchema.JSONRPCResponse.JSONRPCError(-32601, error.message(), error.data())));
}
resultMono = this.exchangeSink.asMono().flatMap((exchange) -> handler.handle(exchange, request.params()));
}
return resultMono.map((result) -> new McpSchema.JSONRPCResponse("2.0", request.id(), result, (McpSchema.JSONRPCResponse.JSONRPCError)null)).onErrorResume((errorx) -> Mono.just(new McpSchema.JSONRPCResponse("2.0", request.id(), (Object)null, new McpSchema.JSONRPCResponse.JSONRPCError(-32603, errorx.getMessage(), (Object)null))));
});
}
private Mono<Void> handleIncomingNotification(McpSchema.JSONRPCNotification notification) {
return Mono.defer(() -> {
if ("notifications/initialized".equals(notification.method())) {
this.state.lazySet(2);
this.exchangeSink.tryEmitValue(new McpAsyncServerExchange(this, (McpSchema.ClientCapabilities)this.clientCapabilities.get(), (McpSchema.Implementation)this.clientInfo.get()));
return this.initNotificationHandler.handle();
} else {
NotificationHandler handler = (NotificationHandler)this.notificationHandlers.get(notification.method());
if (handler == null) {
logger.error("No handler registered for notification method: {}", notification.method());
return Mono.empty();
} else {
return this.exchangeSink.asMono().flatMap((exchange) -> handler.handle(exchange, notification.params()));
}
}
});
}
private MethodNotFoundError getMethodNotFoundError(String method) {
return new MethodNotFoundError(method, "Method not found: " + method, (Object)null);
}
public Mono<Void> closeGracefully() {
return this.transport.closeGracefully();
}
public void close() {
this.transport.close();
}
static record MethodNotFoundError(String method, String message, Object data) {
}
@FunctionalInterface
public interface Factory {
McpServerSession create(McpServerTransport sessionTransport);
}
public interface InitNotificationHandler {
Mono<Void> handle();
}
public interface InitRequestHandler {
Mono<McpSchema.InitializeResult> handle(McpSchema.InitializeRequest initializeRequest);
}
public interface NotificationHandler {
Mono<Void> handle(McpAsyncServerExchange exchange, Object params);
}
public interface RequestHandler<T> {
Mono<T> handle(McpAsyncServerExchange exchange, Object params);
}
}
McpClient
该接口用于创建 MCP 客户端的工厂类,提供了构建同步和异步客户端的静态方法
静态方法说明:
- sync:创建一个同步 MCP 客户端的构建器
- async:创建一个异步 MCP 客户端的构建器
内部类 SyncSpec、AsyncSpec 类说明
|--------------------|-----------------------------------|-----------------|
| | 字段 | 名称 |
| SyncSpec、AsyncSpec | McpClientTransport transport | 客户端传输层实现 |
| SyncSpec、AsyncSpec | Duration requestTimeout | 请求超时时间,默认20秒 |
| SyncSpec、AsyncSpec | Duration initializationTimeout | 初始化超时时间,默认20秒 |
| SyncSpec、AsyncSpec | ClientCapabilities capabilities | 客户端能力配置 |
| SyncSpec、AsyncSpec | Implementation clientInfo | 客户端实现信息 |
| SyncSpec、AsyncSpec | Map roots | 客户端可访问的资源根URI映射 |
| SyncSpec、AsyncSpec | List>> toolsChangeConsumers | 工具变更通知的消费者列表 |
| SyncSpec、AsyncSpec | List>> resourcesChangeConsumers | 资源变更通知的消费者列表 |
| SyncSpec、AsyncSpec | List>> promptsChangeConsumers | 提示变更通知的消费者列表 |
| SyncSpec、AsyncSpec | List> loggingConsumers | 日志消息通知的消费者列表 |
| SyncSpec、AsyncSpec | Function samplingHandler | 自定义消息采样处理器 |
java
package io.modelcontextprotocol.client;
import io.modelcontextprotocol.client.McpClientFeatures.Async;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.util.Assert;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import reactor.core.publisher.Mono;
public interface McpClient {
static SyncSpec sync(McpClientTransport transport) {
return new SyncSpec(transport);
}
static AsyncSpec async(McpClientTransport transport) {
return new AsyncSpec(transport);
}
public static class SyncSpec {
private final McpClientTransport transport;
private Duration requestTimeout = Duration.ofSeconds(20L);
private Duration initializationTimeout = Duration.ofSeconds(20L);
private McpSchema.ClientCapabilities capabilities;
private McpSchema.Implementation clientInfo = new McpSchema.Implementation("Java SDK MCP Client", "1.0.0");
private final Map<String, McpSchema.Root> roots = new HashMap();
private final List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers = new ArrayList();
private final List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers = new ArrayList();
private final List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers = new ArrayList();
private final List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers = new ArrayList();
private Function<McpSchema.CreateMessageRequest, McpSchema.CreateMessageResult> samplingHandler;
private SyncSpec(McpClientTransport transport) {
Assert.notNull(transport, "Transport must not be null");
this.transport = transport;
}
public SyncSpec requestTimeout(Duration requestTimeout) {
Assert.notNull(requestTimeout, "Request timeout must not be null");
this.requestTimeout = requestTimeout;
return this;
}
public SyncSpec initializationTimeout(Duration initializationTimeout) {
Assert.notNull(initializationTimeout, "Initialization timeout must not be null");
this.initializationTimeout = initializationTimeout;
return this;
}
public SyncSpec capabilities(McpSchema.ClientCapabilities capabilities) {
Assert.notNull(capabilities, "Capabilities must not be null");
this.capabilities = capabilities;
return this;
}
public SyncSpec clientInfo(McpSchema.Implementation clientInfo) {
Assert.notNull(clientInfo, "Client info must not be null");
this.clientInfo = clientInfo;
return this;
}
public SyncSpec roots(List<McpSchema.Root> roots) {
Assert.notNull(roots, "Roots must not be null");
for(McpSchema.Root root : roots) {
this.roots.put(root.uri(), root);
}
return this;
}
public SyncSpec roots(McpSchema.Root... roots) {
Assert.notNull(roots, "Roots must not be null");
for(McpSchema.Root root : roots) {
this.roots.put(root.uri(), root);
}
return this;
}
public SyncSpec sampling(Function<McpSchema.CreateMessageRequest, McpSchema.CreateMessageResult> samplingHandler) {
Assert.notNull(samplingHandler, "Sampling handler must not be null");
this.samplingHandler = samplingHandler;
return this;
}
public SyncSpec toolsChangeConsumer(Consumer<List<McpSchema.Tool>> toolsChangeConsumer) {
Assert.notNull(toolsChangeConsumer, "Tools change consumer must not be null");
this.toolsChangeConsumers.add(toolsChangeConsumer);
return this;
}
public SyncSpec resourcesChangeConsumer(Consumer<List<McpSchema.Resource>> resourcesChangeConsumer) {
Assert.notNull(resourcesChangeConsumer, "Resources change consumer must not be null");
this.resourcesChangeConsumers.add(resourcesChangeConsumer);
return this;
}
public SyncSpec promptsChangeConsumer(Consumer<List<McpSchema.Prompt>> promptsChangeConsumer) {
Assert.notNull(promptsChangeConsumer, "Prompts change consumer must not be null");
this.promptsChangeConsumers.add(promptsChangeConsumer);
return this;
}
public SyncSpec loggingConsumer(Consumer<McpSchema.LoggingMessageNotification> loggingConsumer) {
Assert.notNull(loggingConsumer, "Logging consumer must not be null");
this.loggingConsumers.add(loggingConsumer);
return this;
}
public SyncSpec loggingConsumers(List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers) {
Assert.notNull(loggingConsumers, "Logging consumers must not be null");
this.loggingConsumers.addAll(loggingConsumers);
return this;
}
public McpSyncClient build() {
McpClientFeatures.Sync syncFeatures = new McpClientFeatures.Sync(this.clientInfo, this.capabilities, this.roots, this.toolsChangeConsumers, this.resourcesChangeConsumers, this.promptsChangeConsumers, this.loggingConsumers, this.samplingHandler);
McpClientFeatures.Async asyncFeatures = Async.fromSync(syncFeatures);
return new McpSyncClient(new McpAsyncClient(this.transport, this.requestTimeout, this.initializationTimeout, asyncFeatures));
}
}
public static class AsyncSpec {
private final McpClientTransport transport;
private Duration requestTimeout = Duration.ofSeconds(20L);
private Duration initializationTimeout = Duration.ofSeconds(20L);
private McpSchema.ClientCapabilities capabilities;
private McpSchema.Implementation clientInfo = new McpSchema.Implementation("Spring AI MCP Client", "0.3.1");
private final Map<String, McpSchema.Root> roots = new HashMap();
private final List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers = new ArrayList();
private final List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers = new ArrayList();
private final List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers = new ArrayList();
private final List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers = new ArrayList();
private Function<McpSchema.CreateMessageRequest, Mono<McpSchema.CreateMessageResult>> samplingHandler;
private AsyncSpec(McpClientTransport transport) {
Assert.notNull(transport, "Transport must not be null");
this.transport = transport;
}
public AsyncSpec requestTimeout(Duration requestTimeout) {
Assert.notNull(requestTimeout, "Request timeout must not be null");
this.requestTimeout = requestTimeout;
return this;
}
public AsyncSpec initializationTimeout(Duration initializationTimeout) {
Assert.notNull(initializationTimeout, "Initialization timeout must not be null");
this.initializationTimeout = initializationTimeout;
return this;
}
public AsyncSpec capabilities(McpSchema.ClientCapabilities capabilities) {
Assert.notNull(capabilities, "Capabilities must not be null");
this.capabilities = capabilities;
return this;
}
public AsyncSpec clientInfo(McpSchema.Implementation clientInfo) {
Assert.notNull(clientInfo, "Client info must not be null");
this.clientInfo = clientInfo;
return this;
}
public AsyncSpec roots(List<McpSchema.Root> roots) {
Assert.notNull(roots, "Roots must not be null");
for(McpSchema.Root root : roots) {
this.roots.put(root.uri(), root);
}
return this;
}
public AsyncSpec roots(McpSchema.Root... roots) {
Assert.notNull(roots, "Roots must not be null");
for(McpSchema.Root root : roots) {
this.roots.put(root.uri(), root);
}
return this;
}
public AsyncSpec sampling(Function<McpSchema.CreateMessageRequest, Mono<McpSchema.CreateMessageResult>> samplingHandler) {
Assert.notNull(samplingHandler, "Sampling handler must not be null");
this.samplingHandler = samplingHandler;
return this;
}
public AsyncSpec toolsChangeConsumer(Function<List<McpSchema.Tool>, Mono<Void>> toolsChangeConsumer) {
Assert.notNull(toolsChangeConsumer, "Tools change consumer must not be null");
this.toolsChangeConsumers.add(toolsChangeConsumer);
return this;
}
public AsyncSpec resourcesChangeConsumer(Function<List<McpSchema.Resource>, Mono<Void>> resourcesChangeConsumer) {
Assert.notNull(resourcesChangeConsumer, "Resources change consumer must not be null");
this.resourcesChangeConsumers.add(resourcesChangeConsumer);
return this;
}
public AsyncSpec promptsChangeConsumer(Function<List<McpSchema.Prompt>, Mono<Void>> promptsChangeConsumer) {
Assert.notNull(promptsChangeConsumer, "Prompts change consumer must not be null");
this.promptsChangeConsumers.add(promptsChangeConsumer);
return this;
}
public AsyncSpec loggingConsumer(Function<McpSchema.LoggingMessageNotification, Mono<Void>> loggingConsumer) {
Assert.notNull(loggingConsumer, "Logging consumer must not be null");
this.loggingConsumers.add(loggingConsumer);
return this;
}
public AsyncSpec loggingConsumers(List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers) {
Assert.notNull(loggingConsumers, "Logging consumers must not be null");
this.loggingConsumers.addAll(loggingConsumers);
return this;
}
public McpAsyncClient build() {
return new McpAsyncClient(this.transport, this.requestTimeout, this.initializationTimeout, new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.roots, this.toolsChangeConsumers, this.resourcesChangeConsumers, this.promptsChangeConsumers, this.loggingConsumers, this.samplingHandler));
}
}
}
McpClientFeatures
用于定义和管理 MCP 客户端的功能和能力。它提供了两种规范:
- Sync:阻塞操作,直接返回响应
- Async:非阻塞操作,基于 Project Reactor 的 Mono 响应
McpSyncClient
MCP 的同步客户端实现,封装了 McpAsyncClient 以提供阻塞操作的 API。它适用于非响应式应用程序,提供了工具发现、资源管理、提示模板处理以及实时通知等功能
对外暴露方法说明
|--------|-----------------------|-----------------------------|
| 核心板块 | 方法名称 | 描述 |
| 生命周期管理 | initialize | 执行客户端与服务端的初始化过程,包括能力协商和信息交换 |
| 生命周期管理 | close | 立即关闭客户端并释放资源 |
| 生命周期管理 | closeGracefully | 优雅关闭客户端,确保未完成的操作完成 |
| 工具管理 | callTool | 调用服务端提供的工具并返回执行结果 |
| 工具管理 | listTools | 获取服务端提供的工具列表 |
| 资源管理 | listResources | 获取服务端提供的资源列表 |
| 资源管理 | readResource | 读取指定资源的内容 |
| 资源管理 | listResourceTemplates | 获取服务端提供的资源模板列表 |
| 资源管理 | subscribeResource | 订阅资源变更通知 |
| 资源管理 | unsubscribeResource | 取消资源变更订阅 |
| 资源管理 | addRoot | 动态添加资源根 |
| 资源管理 | removeRoot | 动态移除资源根 |
| 提示模板管理 | listPrompts | 获取服务端提供的提示模板列表 |
| 提示模板管理 | getPrompt | 获取指定提示模板的详细信息 |
| 日志管理 | setLoggingLevel | 设置客户端接收的最小日志级别 |
| 通用功能 | ping | 发送同步Ping请求以检查连接状态 |
| 通用功能 | completeCompletion | 发送完成请求以生成建议值 |
java
package io.modelcontextprotocol.client;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.util.Assert;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class McpSyncClient implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(McpSyncClient.class);
private static final long DEFAULTCLOSETIMEOUTMS = 10000L;
private final McpAsyncClient delegate;
McpSyncClient(McpAsyncClient delegate) {
Assert.notNull(delegate, "The delegate can not be null");
this.delegate = delegate;
}
public McpSchema.ServerCapabilities getServerCapabilities() {
return this.delegate.getServerCapabilities();
}
public String getServerInstructions() {
return this.delegate.getServerInstructions();
}
public McpSchema.Implementation getServerInfo() {
return this.delegate.getServerInfo();
}
public boolean isInitialized() {
return this.delegate.isInitialized();
}
public McpSchema.ClientCapabilities getClientCapabilities() {
return this.delegate.getClientCapabilities();
}
public McpSchema.Implementation getClientInfo() {
return this.delegate.getClientInfo();
}
public void close() {
this.delegate.close();
}
public boolean closeGracefully() {
try {
this.delegate.closeGracefully().block(Duration.ofMillis(10000L));
return true;
} catch (RuntimeException e) {
logger.warn("Client didn't close within timeout of {} ms.", 10000L, e);
return false;
}
}
public McpSchema.InitializeResult initialize() {
return (McpSchema.InitializeResult)this.delegate.initialize().block();
}
public void rootsListChangedNotification() {
this.delegate.rootsListChangedNotification().block();
}
public void addRoot(McpSchema.Root root) {
this.delegate.addRoot(root).block();
}
public void removeRoot(String rootUri) {
this.delegate.removeRoot(rootUri).block();
}
public Object ping() {
return this.delegate.ping().block();
}
public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolRequest) {
return (McpSchema.CallToolResult)this.delegate.callTool(callToolRequest).block();
}
public McpSchema.ListToolsResult listTools() {
return (McpSchema.ListToolsResult)this.delegate.listTools().block();
}
public McpSchema.ListToolsResult listTools(String cursor) {
return (McpSchema.ListToolsResult)this.delegate.listTools(cursor).block();
}
public McpSchema.ListResourcesResult listResources(String cursor) {
return (McpSchema.ListResourcesResult)this.delegate.listResources(cursor).block();
}
public McpSchema.ListResourcesResult listResources() {
return (McpSchema.ListResourcesResult)this.delegate.listResources().block();
}
public McpSchema.ReadResourceResult readResource(McpSchema.Resource resource) {
return (McpSchema.ReadResourceResult)this.delegate.readResource(resource).block();
}
public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest readResourceRequest) {
return (McpSchema.ReadResourceResult)this.delegate.readResource(readResourceRequest).block();
}
public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor) {
return (McpSchema.ListResourceTemplatesResult)this.delegate.listResourceTemplates(cursor).block();
}
public McpSchema.ListResourceTemplatesResult listResourceTemplates() {
return (McpSchema.ListResourceTemplatesResult)this.delegate.listResourceTemplates().block();
}
public void subscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
this.delegate.subscribeResource(subscribeRequest).block();
}
public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
this.delegate.unsubscribeResource(unsubscribeRequest).block();
}
public McpSchema.ListPromptsResult listPrompts(String cursor) {
return (McpSchema.ListPromptsResult)this.delegate.listPrompts(cursor).block();
}
public McpSchema.ListPromptsResult listPrompts() {
return (McpSchema.ListPromptsResult)this.delegate.listPrompts().block();
}
public McpSchema.GetPromptResult getPrompt(McpSchema.GetPromptRequest getPromptRequest) {
return (McpSchema.GetPromptResult)this.delegate.getPrompt(getPromptRequest).block();
}
public void setLoggingLevel(McpSchema.LoggingLevel loggingLevel) {
this.delegate.setLoggingLevel(loggingLevel).block();
}
public McpSchema.CompleteResult completeCompletion(McpSchema.CompleteRequest completeRequest) {
return (McpSchema.CompleteResult)this.delegate.completeCompletion(completeRequest).block();
}
}
McpAsyncClient
MCP 的异步客户端实现,其余同 McpSyncClient 一致
McpServer
用于创建 MCP 服务端的工厂类,提供了同步、异步服务端的静态方法
静态方法说明:
- sync:创建一个同步 MCP 服务器的构建器
- async:创建一个异步 MCP 服务器的构建器
内部类 SyncSpecification、AsyncSpecification 类说明
|--------------------------------------|--------------------------------------------------------|------------------------|
| | 字段 | 名称 |
| SyncSpecification、AsyncSpecification | McpUriTemplateManagerFactory uriTemplateManagerFactory | URI模板管理器工厂 |
| SyncSpecification、AsyncSpecification | McpServerTransportProvider transportProvider | 服务端传输层实现 |
| SyncSpecification、AsyncSpecification | ObjectMapper objectMapper | 用于序列化和反序列化JSON消息的对象映射器 |
| SyncSpecification、AsyncSpecification | McpSchema.Implementation serverInfo | 服务器实现信息 |
| SyncSpecification、AsyncSpecification | McpSchema.ServerCapabilities serverCapabilities | 服务器支持的功能 |
| SyncSpecification、AsyncSpecification | String instructions | 服务器的初始化说明 |
| SyncSpecification、AsyncSpecification | List tools | 注册的工具列表 |
| SyncSpecification、AsyncSpecification | Map resources | 注册的资源映射 |
| SyncSpecification、AsyncSpecification | List resourceTemplates | 资源模板列表 |
| SyncSpecification、AsyncSpecification | Map prompts | 注册的提示模板映射 |
| SyncSpecification、AsyncSpecification | Map completions | 注册的完成处理映射 |
| SyncSpecification、AsyncSpecification | List>> rootsChangeHandlers | 根变更通知的处理器列表 |
| SyncSpecification、AsyncSpecification | Duration requestTimeout | 请求超时时间,默认10秒 |
McpSyncServer
McpSyncServer 类是 MCP 服务器的同步实现,封装了 McpAsyncServer 以提供阻塞操作的 API。它适用于非响应式编程场景,简化了传统同步应用程序的集成。该类主要用于管理工具、资源、提示模板的注册与通知,同时支持客户端交互和服务器生命周期管理
对外暴露方法说明
|--------|----------------------------|----------------------------------------------------|
| 核心板块 | 方法名称 | 描述 |
| 工具管理 | addTool | 添加新的工具处理器 |
| 工具管理 | removeTool | 移除指定名称的工具处理器 |
| 工具管理 | notifyToolsListChanged | 通知客户端工具列表发生变化 |
| 资源管理 | addResource | 添加新的资源处理器 |
| 资源管理 | removeResource | 移除指定URI的资源处理器 |
| 资源管理 | notifyResourcesListChanged | 通知客户端资源列表发生变化 |
| 提示模板管理 | addPrompt | 添加新的提示模板处理器 |
| 提示模板管理 | removePrompt | 移除指定名称的提示模板处理器 |
| 提示模板管理 | notifyPromptsListChanged | 通知客户端提示模板列表发生变化 |
| 日志管理 | loggingNotification | 向所有客户端广播日志消息(已弃用,建议使用McpSyncServerExchange的日志通知方法) |
| 生命周期管理 | closeGracefully | 优雅关闭服务器,确保未完成的操作完成 |
| 生命周期管理 | close | 立即关闭服务器 |
| 其他 | getServerCapabilities | 获取服务器支持的功能和特性 |
| 其他 | getServerInfo | 获取服务器的实现信息 |
| 其他 | getAsyncServer | 获取底层的异步服务器实例 |
java
package io.modelcontextprotocol.server;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncPromptSpecification;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncResourceSpecification;
import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolSpecification;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.util.Assert;
public class McpSyncServer {
private final McpAsyncServer asyncServer;
public McpSyncServer(McpAsyncServer asyncServer) {
Assert.notNull(asyncServer, "Async server must not be null");
this.asyncServer = asyncServer;
}
public void addTool(McpServerFeatures.SyncToolSpecification toolHandler) {
this.asyncServer.addTool(AsyncToolSpecification.fromSync(toolHandler)).block();
}
public void removeTool(String toolName) {
this.asyncServer.removeTool(toolName).block();
}
public void addResource(McpServerFeatures.SyncResourceSpecification resourceHandler) {
this.asyncServer.addResource(AsyncResourceSpecification.fromSync(resourceHandler)).block();
}
public void removeResource(String resourceUri) {
this.asyncServer.removeResource(resourceUri).block();
}
public void addPrompt(McpServerFeatures.SyncPromptSpecification promptSpecification) {
this.asyncServer.addPrompt(AsyncPromptSpecification.fromSync(promptSpecification)).block();
}
public void removePrompt(String promptName) {
this.asyncServer.removePrompt(promptName).block();
}
public void notifyToolsListChanged() {
this.asyncServer.notifyToolsListChanged().block();
}
public McpSchema.ServerCapabilities getServerCapabilities() {
return this.asyncServer.getServerCapabilities();
}
public McpSchema.Implementation getServerInfo() {
return this.asyncServer.getServerInfo();
}
public void notifyResourcesListChanged() {
this.asyncServer.notifyResourcesListChanged().block();
}
public void notifyPromptsListChanged() {
this.asyncServer.notifyPromptsListChanged().block();
}
/** @deprecated */
@Deprecated
public void loggingNotification(McpSchema.LoggingMessageNotification loggingMessageNotification) {
this.asyncServer.loggingNotification(loggingMessageNotification).block();
}
public void closeGracefully() {
this.asyncServer.closeGracefully().block();
}
public void close() {
this.asyncServer.close();
}
public McpAsyncServer getAsyncServer() {
return this.asyncServer;
}
}
McpAsyncServer
McpSyncServer 类是 MCP 服务器的异步实现,其余同 McpSyncServer 一致
McpSchema
McpSchema 类定义了 MCP(Model Context Protocol)协议的核心规范和数据结构。它基于 JSON-RPC 2.0 协议,提供了方法名称、错误代码、消息类型以及与客户端和服务器交互的请求和响应模型。该类的主要作用是:
- 协议版本管理:定义最新的协议版本和 JSON-RPC 版本
- 方法名称定义:提供所有支持的 JSON-RPC 方法名称
- 错误代码定义:定义标准的 JSON-RPC 错误代码
- 消息序列化与反序列化:支持 JSON-RPC 消息的序列化和反序列化
- 数据结构定义:提供与 MCP 交互的请求、响应和通知的具体数据结构
|--------------|-----------------------------|-------------------|
| | 内部类 | 描述 |
| JSON-RPC消息类型 | JSONRPCMessage | 标识所有JSON-RPC消息的基类 |
| JSON-RPC消息类型 | JSONRPCRequest | JSON-RPC请求消息 |
| JSON-RPC消息类型 | JSONRPCResponse | JSON-RPC响应消息 |
| JSON-RPC消息类型 | JSONRPCNotification | JSON-RPC通知消息 |
| 生命周期管理 | InitializeRequest | 客户端发送的初始化请求 |
| 生命周期管理 | InitializeResult | 服务器返回的初始化结果 |
| 工具管理 | Tool | 服务器提供的工具 |
| 工具管理 | CallToolRequest | 调用工具的请求 |
| 工具管理 | CallToolResult | 调用工具的响应结果 |
| 工具管理 | ListToolsResult | 工具列表的响应结果 |
| 资源管理 | Resource | 服务器提供的资源 |
| 资源管理 | ResourceTemplate | 资源模板 |
| 资源管理 | ListResourcesResult | 资源列表的响应结果 |
| 资源管理 | ListResourceTemplatesResult | 资源模板列表的响应结果 |
| 资源管理 | ReadResourceRequest | 读取资源的请求 |
| 资源管理 | ReadResourceResult | 读取资源的响应结果 |
| 资源管理 | SubscribeRequest | 订阅资源变更的请求 |
| 资源管理 | UnsubscribeRequest | 取消订阅资源变更的请求 |
| 资源管理 | ResourceContents | 资源的内容 |
| 提示模板管理 | Prompt | 服务器提供的提示模板 |
| 提示模板管理 | PromptArgument | 提示模板的参数 |
| 提示模板管理 | PromptMessage | 提示模板返回的消息 |
| 提示模板管理 | ListPromptsResult | 提示模板列表的响应结果 |
| 提示模板管理 | GetPromptRequest | 获取提示模板的请求 |
| 提示模板管理 | GetPromptResult | 获取提示模板的响应结果 |
| 完成请求管理 | CompleteReference | 完成请求的引用 |
| 完成请求管理 | CompleteRequest | 完成请求 |
| 完成请求管理 | CompleteResult | 请求的响应结果 |
| 日志管理 | LoggingMessageNotification | 日志消息通知 |
| 日志管理 | SetLevelRequest | 设置日志级别的请求 |
| 日志管理 | LoggingLevel | 日志级别的枚举 |
| 根资源管理 | Root | 服务器可操作的根资源 |
| 根资源管理 | ListRootsResult | 资源列表的响应结果 |
| 采样管理 | SamplingMessage | 采样消息 |
| 采样管理 | CreateMessageRequest | 创建消息的请求 |
| 采样管理 | CreateMessageResult | 创建消息的响应结果 |
| 采样管理 | ModelPreferences | 模型偏好设置 |
| 采样管理 | ModelHint | 模型提示 |
| 分页管理 | PaginatedRequest | 分页请求 |
| 分页管理 | PaginatedResult | 分页响应结果 |
| 进度通知 | ProgressNotification | 进度通知 |
| 通用内容类型 | Content | 消息内容的基类 |
| 错误代码 | ErrorCodes | 标准的JSON-RPC错误代码 |
学习交流圈
你好,我是影子,曾先后在🐻、新能源、老铁就职,现在是一名AI研发工程师,同时作为Spring AI Alibaba开源社区的Committer。目前新建了一个交流群,一个人走得快,一群人走得远,另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取
