SpringAI(GA):MCP源码解读

原文链接: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

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 事件类型,用于接收服务器提供的消息发送端点 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的构建器,用于定制化实例化 |

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 协议实现双向通信。它负责管理客户端会话,处理消息的接收与发送,并提供可靠的消息广播功能,主要功能如下:

  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实例对象 |

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 前缀,用于生成唯一请求 ID
  • AtomicLong 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。目前新建了一个交流群,一个人走得快,一群人走得远,另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取

相关推荐
Mr Aokey2 小时前
Spring MVC参数绑定终极手册:单&多参/对象/集合/JSON/文件上传精讲
java·后端·spring
地藏Kelvin3 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
菠萝014 小时前
共识算法Raft系列(1)——什么是Raft?
c++·后端·算法·区块链·共识算法
长勺4 小时前
Spring中@Primary注解的作用与使用
java·后端·spring
小奏技术5 小时前
基于 Spring AI 和 MCP:用自然语言查询 RocketMQ 消息
后端·aigc·mcp
编程轨迹5 小时前
面试官:如何在 Java 中读取和解析 JSON 文件
后端
lanfufu5 小时前
记一次诡异的线上异常赋值排查:代码没错,结果不对
java·jvm·后端
编程轨迹5 小时前
如何在 Java 中实现 PDF 与 TIFF 格式互转
后端
编程轨迹5 小时前
面试官:你知道如何在 Java 中创建对话框吗
后端
粥里有勺糖5 小时前
用Trae做了个公众号小工具
前端·ai编程·trae