SpringAI(GA):MCP源码解读

328 阅读1小时+

原文链接:SpringAI(GA):MCP源码解读 说明;因掘金字符数限制,本篇删减了StdioClientTransport(StdioServerTransportProvider)、HttpClientSseClientTransport(HttpServletSseServerTransportProvider)、McpServerFeatures 部分内容,完整内容可见原文链接

教程说明

说明:本教程将采用2025年5月20日正式的GA版,给出如下内容

  1. 核心功能模块的快速上手教程
  2. 核心功能模块的源码级解读
  3. 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):ChatClient调用链路解读

第二章内容

SpringAI的Advisor:快速上手+源码解读

SpringAI(GA):Sqlite、Mysql、Redis消息存储快速上手

第三章内容

SpringAI(GA):Tool工具整合—快速上手

SpringAI(GA):Tool源码+工具触发链路解读

第五章内容

SpringAI(GA):内存、Redis、ES的向量数据库存储—快速上手

SpringAI(GA):向量数据库理论源码解读+Redis、Es接入源码

第六章内容

SpringAI(GA):RAG快速上手+模块化解读

SpringAI(GA):RAG下的ETL快速上手

SpringAI(GA):RAG下的ETL源码解读

第七章内容

SpringAI(GA):Nacos2下的分布式MCP

整理不易,获取更好的观赏体验,可付费获取飞书云文档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

<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:目标对象的类型
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 方法:建立客户端与服务端的连接,并定义消息处理逻辑

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 事件类型,用于接收服务器提供的消息发送端点 URI
  • String 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():储服务器提供的消息发送端点 URI
  • String sseEndpoint:SSE 连接的端点 URI

对外暴露的方法

方法名称
描述
connect
建立SSE连接,处理服务器发送的消息,并设置消息处理逻辑
sendMessage
通过HTTP POST请求向服务器发送JSON-RPC消息
eventStream
初始化并启动入站的SSE事件处理,它通过建立SSE连接来接收服务器发送的事件
closeGracefully
优雅地关闭传输连接,清理资源
unmarshalFrom
将数据反序列化为指定类型的对象
builder
创建WebFluxSseClientTransport的构建器,用于定制化实例化
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 是服务端传输层的标记接口,定义了服务端通信的基础功能

package io.modelcontextprotocol.spec;

public interface McpServerTransport extends McpTransport {
}

McpServerTransportProvider 是服务端传输层的核心接口,负责会话管理、消息广播和资源清理

方法名称
描述
setSessionFactory
设置会话工厂,用于创建新的服务端会话
notifyClients
向所有活跃客户端广播JSON-RPC消息
clsoe
立即关闭所有传输层连接并释放资源
closeGracefully
优雅地关闭所有活跃会话,清理资源
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 协议实现双向通信。它负责管理客户端会话,处理消息的接收与发送,并提供可靠的消息广播功能,主要功能如下:

  1. SSE 连接管理:通过 SSE 建立服务端到客户端的实时消息通道
  2. 消息接收与处理:通过 HTTP POST 接收客户端发送的 JSON-RPC 消息
  3. 消息广播:支持将消息推送到所有活跃的客户端会话
  4. 会话管理:维护客户端会话的生命周期,支持资源清理和优雅关闭
  5. 线程安全:使用 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:存储活跃客户端会话的线程安全映射,键为会话 ID
  • boolean isClosing:标志传输是否正在关闭,防止关闭期间接受新连接

对外暴露的方法

方法名称
描述
setSessionFactory
设置会话工厂,用于创建新的服务端会话
notifyClients
向所有活跃客户端广播JSON-RPC消息
closeGracefully
优雅地关闭所有活跃会话,清理资源
getRouterFunction
返回定义SSE和消息端点的路由函数
builder
Builder方式创建WebFluxSseServerTransportProvider实例对象
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
立即关闭会话并释放资源
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 前缀,用于生成唯一请求 ID
  • AtomicLong requestCounter:用于生成唯一请求 ID 的计数器
  • Disposable connection:管理与传输层的连接,负责监听和处理消息
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:会话状态,未初始化、初始化中或已初始化
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
客户端传输层实现
Duration requestTimeout
请求超时时间,默认20秒
Duration initializationTimeout
初始化超时时间,默认20秒
ClientCapabilities capabilities
客户端能力配置
Implementation clientInfo
客户端实现信息
Map roots
客户端可访问的资源根URI映射
List>> toolsChangeConsumers
工具变更通知的消费者列表
List>> resourcesChangeConsumers
资源变更通知的消费者列表
List>> promptsChangeConsumers
提示变更通知的消费者列表
List> loggingConsumers
日志消息通知的消费者列表
Function samplingHandler
自定义消息采样处理器
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
发送完成请求以生成建议值
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模板管理器工厂
McpServerTransportProvider transportProvider
服务端传输层实现
ObjectMapper objectMapper
用于序列化和反序列化JSON消息的对象映射器
McpSchema.Implementation serverInfo
服务器实现信息
McpSchema.ServerCapabilities serverCapabilities
服务器支持的功能
String instructions
服务器的初始化说明
List tools
注册的工具列表
Map resources
注册的资源映射
List resourceTemplates
资源模板列表
Map prompts
注册的提示模板映射
Map completions
注册的完成处理映射
List>> rootsChangeHandlers
根变更通知的处理器列表
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
获取底层的异步服务器实例
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消息的基类
JSONRPCRequest
JSON-RPC请求消息
JSONRPCResponse
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。目前新建了一个交流群,一个人走得快,一群人走得远,另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取