一、前言:Spring AI 与 MCP
在过去的一年里,大模型生态的一个明显趋势是从模型调用走向模型协作。不仅仅是怎么让大模型回答问题,更是怎么让模型具备执行、协同与插件化能力。Spring AI 正是在这一背景下诞生的,它试图以 Spring 的方式,将 AI 能力融入现有的企业级应用体系中。
Spring AI 本质上是一个为 Java 生态设计的 AI 集成框架。它统一了不同模型(如 OpenAI、Anthropic、Ollama 等)的调用方式,并提供了 Prompt 模板、输出解析、工具调用(Tool Invocation)等上层抽象,让开发者可以像使用 RestTemplate 一样优雅地调用大模型。
Spring AI 在 2024 年引入了对 MCP (Model Context Protocol)的支持,这一机制旨在标准化模型与外部系统(插件、数据库、API、文件系统等)之间的上下文通信协议,使模型具备理解和调用外部资源的能力。换句话说,MCP 规范了模型与外部世界之间的通信,使模型能够在受控、安全、可扩展的环境下访问外部资源并执行任务。
在理解了 Spring AI 与 MCP 的背景之后,本文将从源码角度出发,系统解析 Spring AI 是如何实现 MCP 协议的。我们将依次梳理其整体架构设计、核心交互过程、关键组件源码以及运行时的执行机制,并通过断点演示还原客户端与服务端的实际协作路径。
二、架构解析
在 Spring AI MCP 中,客户端与服务端的设计呈现出高度的对称性与分层化特征,其架构的核心思想是分层解耦、职责清晰、对称可扩展。无论是客户端还是服务端,每一层都只负责单一维度的功能。下图为 MCP 客户端和服务端的分层架构图:
| MCP 客户端 | MCP 服务端 |
|---|---|
![]() |
![]() |
在 Spring AI MCP 中,客户端和服务端的架构是十分接近的,自上而下包含入口层、运行层、会话层、传输层 ,不同之处在于服务端架构会多出一层,即会话管理层,下面是各层的作用:
- 入口层 :这一层是 MCP 客户端/服务端对外的统一入口,对应的接口为 McpClient/McpServer。只需通过其工厂方法,并提供配置信息(如超时时间、能力配置等),就能以同步或异步模式创建运行层的客户端/服务端实例。入口层对下层的复杂实现进行了封装,对上层提供统一、简洁的构建入口,使得调用者无需关心传输方式与消息机制的差异。
- 运行层 :这一层是 MCP 客户端/服务端的核心控制中心,对应的类为 McpAsyncClient/McpSyncClient、McpAsyncServer/McpSyncServer。其实现了具体的业务行为,包括初始化、工具注册、工具调用等操作,并使用响应式编程模型来处理异步流程。
- 会话管理层 :这一层是 MCP 服务端特有的部分,用于管理多个客户端会话,对应的接口为 McpServerTransportProvider。其用于管理活跃会话的生命周期、向所有客户端广播消息、统一关闭与清理连接资源等,使得服务端可以同时维护多个客户端连接。
- 会话层 :这一层是 MCP 客户端与服务端之间通信的上下文载体,对应的类为 McpClientSession/McpServerSession。每一个会话代表一次完整的逻辑连接,其负责管理客户端与服务端之间的消息上下文,包括请求、响应与通知。该层基于 JSON-RPC 协议,实现了通信数据的统一封装与传递,使业务逻辑与底层传输机制相互解耦。
- 传输层 :这一层负责实际的数据收发与序列化,对应的接口是 McpClientTransport/McpServerTransport。其屏蔽了底层 I/O 的复杂性,为上层提供消息发送、对象反序列化等操作,并提供了不同协议的实现,如 Stdio、HTTP + SSE、Streamable HTTP。
三、核心交互过程
上一章我们从架构层面梳理了 Spring AI MCP 的整体设计思路:客户端由入口层、运行层、会话层与传输层构成,服务端则在此基础上增加了会话管理层,用以协调多会话场景下的资源与上下文。但理解分层结构只是第一步,要真正掌握 MCP 的运行机制,还需要深入到"层与层之间",也就是它们如何建立连接、初始化、交换消息、调用工具的核心交互过程。
本章将以 SSE 为示例,串联起 MCP 在真实运行中的三条主线,分别是客户端与服务端的构建与连接过程、初始化过程、工具发现与调用过程。通过这三个阶段,我们将从源码层面以图的形式还原 MCP 的交互逻辑,理清每一层的职责边界与协作方式。实际上源码中对于其他协议的处理也是非常类似的,只是传输层的实现上会略有不同,但整体结构都是一致的。
在解析三个阶段之前,先要讲述一下 Spring AI MCP 的 SSE 实现中客户端和服务端中是哪些层次完成消息收发的:
| MCP 客户端 | MCP 服务端 |
|---|---|
| 在 MCP 客户端中,消息的收发完全由传输层负责完成。 连接建立阶段 :向服务端发送 GET 请求 ,以建立 SSE 长连接。建立成功后,客户端会持续监听该连接,用于接收来自服务端的实时消息与事件推送。 业务交互阶段 :在执行如初始化、工具发现、工具调用等操作时,传输层会向服务端发送 POST 请求。服务端接收到这些请求后,会通过 SSE 通道返回响应或执行结果,从而完成一次完整的请求-响应循环。 | 与客户端不同,MCP 服务端的消息收发由会话管理层 和传输层 共同完成。 会话管理层 :负责接收客户端的 GET、POST 请求,其中:GET 请求用于接收客户端的 SSE 长连接请求;POST 请求用于接收客户端发送的消息,并进行基础响应(例如返回 HTTP 200 OK 状态码)。 传输层:实际的消息发送者,在已建立的 SSE 通道上向客户端推送响应或事件消息,如工具调用结果、连接建立响应等。 |
3.1 客户端/服务端构建、连接过程
首先讲解客户端/服务端的构建和连接过程,这是他们交互的第一步,其中比较特殊的是,客户端向服务端发起连接的过程是发生在客户端的构建过程的,因此下面会将客户端构建与连接过程一起讲解。
- 服务端构建过程 :服务端的构建过程比较简单,在入口层中配置了服务端的所有信息(如超时时间、能力配置)后,调用其工厂方法就能构建出一个服务端运行层实例。
- 客户端构建过程 :与服务端类似,在入口层中配置了客户端的所有信息(如超时时间、能力配置)后,调用其工厂方法 就能构建出一个客户端运行层实例。但有所区别的是,客户端还需要创建出其会话层对象。在配置完会话层对象 后(需要配置超时时间、传输层对象等),还需要调用传输层方法来建立与服务端的连接 ,此时会向客户端发送 GET 请求来建立并监听 SSE 连接 。服务端的会话管理层接收到后,会开启 SSE 连接,并为客户端创建一个会话层对象,最后再返回客户端消息端点地址(之后客户端需要向这个地址发送业务交互消息)。在客户端收到响应后,会保存这个消息端点地址,至此客户端的构建过程才算结束。
详细的交互流程如下图所示:

3.2 客户端/服务端初始化过程
然后讲解客户端/服务端的初始化过程,这是他们交互的第二步。整个初始化过程可以分为两个阶段:初始化建立、初始化完成通知 。初始化建立 指的是客户端在向服务端发起初始化请求,服务端向客户端响应服务端信息的过程。这个过程完成后,服务端存储的状态仅是初始化中 ,即还未结束初始化过程,之后还需要客户端向服务端发送初始化完成通知 ,服务端存储的状态才会设置为初始化完成 ,这才标志着双方正式完成初始化交互。这样做的意义在于双方能通过两次消息交换完成状态确认,确保初始化过程完整可靠。
- 初始化建立:
上层服务在调用客户端运行层对象的初始化方法后,就能开启这一过程。运行层对象会构建初始化请求方法、参数(包含客户端版本、配置信息 ),然后调用会话层对象发送请求。会话层对象将请求转换为 JSON-RPC 格式的消息,然后就调用传输层对象发送消息。传输层对象则向服务端的消息端点发送 POST 请求,并携带刚才构建的 JSON-RPC 消息。
服务端的会话管理层接收到后,会取出连接过程时创建的会话层对象,然后让其来处理这个消息。会话层对象会保存客户端消息,并设置状态为初始化中,之后构建 JSON-RPC 响应消息(内容包含服务端版本、信息等),再调用传输层对象发送消息给客户端。最后传输层对象则通过 SSE 连接向客户端发送这个 JSON -RPC 消息。
客户端传输层对象接收到这一响应后,会一直向上传递到运行层,之后运行层对象会存储响应中服务端的配置信息。
- 初始化完成通知:
在初始化建立后,客户端运行层对象还需要调用会话层对象来通知服务端初始化完成,会话层对象则会构建 JSON-RPC 通知消息,并调用传输层对象发送这一通知。传输层对象则向服务端的消息端点发送 POST 请求,并携带刚才构建的 JSON-RPC 消息。
服务端的会话管理层接收到后,会取出连接过程时创建的会话层对象,然后让其来处理这个消息。会话层对象则将状态设置为初始化完成,并返回成功信息给会话管理层。最终会话管理层会向客户端的 POST 请求做一个简单的响应。
客户端传输层对象接收到这一响应后,会一直向上传递到运行层,最后运行层会将客户端的状态标记为初始化完成。至此整个初始化过程才算结束。
详细的交互流程如下图所示:

3.3 工具发现、调用过程
最后讲解工具发现、调用的过程,这个过程属于 MCP 的业务交互流程。其他的过程都是很类似的,因为源码对整个业务交互进行了非常好的抽象,所以每个业务流程在层次走向上都是非常类似的。
- 工具发现过程:
上层服务在调用客户端运行层对象的工具发现方法后,就能开启这一过程。运行层对象会调用会话层对象发送请求,来获取工具列表,会话层对象将请求内容转换为 JSON-RPC 格式消息后,调用传输层对象发送消息。最后,传输层对象则向服务端的消息端点发送 POST 请求,并携带这一消息。
服务端的会话管理层对象接收到后,会取出连接过程时创建的会话层对象,然后让其来处理这个消息。会话层对象则会构建 JSON-RPC 响应消息,内容包含服务端的可用工具列表,再调用传输层对象发送消息给客户端。最后,传输层对象会通过 SSE 连接向客户端发送这一消息。
客户端的传输层对象在接收到后,会将返回的内容一直向上传递到运行层对象,然后再返回给上层的调用者,
- 工具调用过程:
整个过程其实和工具发现过程非常类似,这里就不再赘述。唯一的区别在于服务端的会话层对象执行逻辑不同,此时其会调用相应的工具并等待调用结果,最后再把这个结果通过传输层返回给客户端。
详细的交互流程如下图所示:

四、核心组件源码剖析
在上一章中,我们深入解析了 MCP 客户端与服务端在 SSE 模式下的核心交互流程,从构建与连接、初始化,到工具的发现与调用,全流程呈现了分层架构下各模块的协作机制。理解了交互逻辑之后,本章将以 Spring AI MCP 的源码为基础,剖析其核心组件的实现细节,以更好地理解其内部的运作方式。
这里先给出 MCP 客户端、服务端的完整类图,后续会逐层拆分这个类图,自上而下地讲解各个层次涉及到的源码。这里我使用的源码版本为 0.10.0(github.com/modelcontex...),对应 Spring AI 1.0.0 版本内使用的 mcp 版本。
| MCP 客户端 | MCP 服务端 |
|---|---|
![]() |
![]() |
由于掘金篇幅有限,这一章的部分放在了这里:juejin.cn/spost/75709...。
五、运行时断点追踪
在前面的章节中,我们从架构、交互流程、核心源码三个角度剖析了 Spring AI MCP 的整体设计与关键实现,理清了各层之间的职责划分与调用关系。为了让大家能直观地看到 MCP 在实际运行时的交互过程,这一章将通过断点调试 的方式,以服务端源码的视角,深入演示 SSE 模式下客户端与服务端初始化的过程。在理解了初始化这个比较复杂的过程后,其他的过程也是非常类似的,感兴趣的同学可以按照下面的步骤搭建好环境后自行调试。
5.1 环境搭建
pom 依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.10.0</version>
</dependency>
工程代码:
Java
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;
@Configuration
@EnableWebMvc
public class McpServerConfig implements WebMvcConfigurer {
@Bean
public HttpServletSseServerTransportProvider servletSseServerTransportProvider() {
return HttpServletSseServerTransportProvider.builder()
.objectMapper(new ObjectMapper())
.messageEndpoint("/mcp/message")
.build();
}
@Bean
public ServletRegistrationBean customServletBean(HttpServletSseServerTransportProvider transportProvider) {
return new ServletRegistrationBean(transportProvider);
}
}
Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(McpServerApplication.class, args);
// 从 spring 容器中取出会话管理层对象
HttpServletSseServerTransportProvider transportProvider = context.getBean(HttpServletSseServerTransportProvider.class);
// 定义工具,这里为简单的两数加减乘除计算器
var schema = """
{
"type" : "object",
"id" : "urn:jsonschema:Operation",
"properties" : {
"operation" : {
"type" : "string"
},
"a" : {
"type" : "number"
},
"b" : {
"type" : "number"
}
}
}
""";
// 工具的具体实现
var syncToolSpecification = new McpServerFeatures.SyncToolSpecification(
new McpSchema.Tool("calculator", "Basic calculator", schema),
(exchange, arguments) -> {
String operation = (String) arguments.get("operation");
double a = Double.parseDouble(String.valueOf(arguments.get("a")));
double b = Double.parseDouble(String.valueOf(arguments.get("b")));
double resultValue;
switch (operation) {
case "add":
resultValue = a + b;
break;
case "subtract":
resultValue = a - b;
break;
case "multiply":
resultValue = a * b;
break;
case "divide":
if (b == 0) {
return new McpSchema.CallToolResult("Error: Division by zero", true);
}
resultValue = a / b;
break;
default:
return new McpSchema.CallToolResult("Error: Unknown operation '" + operation + "'", true);
}
// 返回执行结果
String result = String.valueOf(resultValue);
return new McpSchema.CallToolResult(result, false);
}
);
// 构建运行层对象
McpSyncServer syncServer = McpServer.sync(transportProvider)
.serverInfo("my-server", "1.0.0")
.capabilities(McpSchema.ServerCapabilities.builder()
.resources(true, true)
.tools(true) // 开启工具调用能力
.prompts(true) // 开启提示词模板能力
.logging() // 开启事件推送能力
.completions() // 开启自动补全能力
.build())
.build();
// 添加定义好的工具
syncServer.addTool(syncToolSpecification);
}
}
之后就可以运行 main 方法启动服务:

这里我使用的 MCP 客户端是 MCP Inspector,它是一个用于调试、测试和可视化交互的工具,相当于一个图形化的 MCP 客户端,可以直观地查看、调用和调试 MCP Server 的各种能力。安装了 Node.js 后,就可以用下面的命令启动 MCP Inspector:
bash
npx @modelcontextprotocol/inspector
启动后的界面如下所示:

5.2 断点演示
在第三章讲解初始化过程时,提到初始化过程包含两大步骤:初始化建立、初始化完成通知 ,而对于这两个步骤,客户端都会发送 POST 请求,因此这里首先需要把断点设置在服务端会话管理层 HttpServletSseServerTransportProvider 中接收 POST 请求的方法 doPost()。在 MCP Inspector 中触发这一步骤的方式就是点击 Connect 按钮,在连接完成后,就会发送 POST 请求触发初始化过程。
5.2.1 初始化建立
初始化建立过程如下所示,可以看到客户端发送了 POST 请求,请求路径为 /mcp/message(对应于服务端设置的消息端点地址),且通过请求参数中的 sessionId(这个 id 是客户端在连接过程得到的)获得到对应的会话对象,然后再通过会话层对象处理 JSON-RPC 消息。

会话层对象在 handle() 方法里判断了消息的类型,这里为请求类型,之后走入 handleIncomingRequest() 方法。如下所示。
handleIncomingRequest() 这个方法会修改服务端的状态为 STATE_INITIALIZING(初始化中),然后再让请求处理器处理这个请求。如下所示。

处理器的实现对应于 asyncInitializeRequestHandler() 方法(这个方法的定义在运行层 McpAsyncServer 中,以 lambda 的方式传给了会话对象),这个方法会构建给客户端的返回结果,即服务端的基本信息、协议版本等。如下所示。

最后则调用传输层 HttpServletMcpSessionTransport 的 sendMessage() 方法通过 SSE 连接发送结果消息给客户端。如下所示。

5.2.2 初始化完成通知
初始化完成过程通知的过程与前面初始化建立过程非常类似,也是会话管理层接收 POST 请求后转发给会话层处理,区别在于会话层的处理逻辑不同,下面的演示仅展示不同之处。
在会话层的 handle() 方法中,此时的消息类型为通知类型,因此会走入 handleIncomingNotification() 方法,如下所示。

handleIncomingNotification() 方法会将服务端的状态设置为 STATE_INITIALIZED (初始化完成),然后再让通知处理器处理这个请求。
而这个处理器实际上并没有做任何事情,在运行层 McpAsyncServer 构造方法中 lambda 设置为 Mono::empty,即不做任何事。如下所示。

六、总结与思考
整体来看,Spring AI 对 MCP 的实现体现出高度的架构一致性与抽象优雅性。无论是客户端还是服务端,其都以分层化、接口化的方式将核心协议能力抽离出来,使得通信、会话、工具调用等功能可以在统一模型下协同运作。这种设计既延续了 Spring 体系一贯的"以接口驱动框架"的哲学,也让 MCP 能够以极低耦合的方式嵌入到更广泛的 AI 应用场景中。
从源码层面看,Spring AI MCP 的关键特征在于"对称与解耦 "。客户端与服务端的类结构几乎是镜像式的存在,这种对称设计不仅提升了理解与调试的便利性,也为未来的扩展(如自定义 Transport、工具注册机制或新能力声明)提供了天然的空间。而在运行时,通过事件流与异步机制,系统实现了协议层与执行层的彻底分离,使开发者能够在保持高抽象层的同时,精确掌握底层交互细节。
更深层次地,Spring AI 对 MCP 的实现并非简单的协议落地,而是一种"开发者可控的 AI 接口体系"的探索。它将"模型上下文协议"转化为可编程的交互骨架,让语言模型不再是黑盒,而成为可管理、可调度的服务端实体。
这种思路的价值,不仅在于当前的 Agent 应用,更可能成为未来 AI 平台化、模块化发展的关键基石 。可以预见,随着 MCP 标准的不断完善以及 Spring AI 的生态扩展,这一体系将从"实验性框架"走向"基础设施级组件"。在那之前,理解其源码设计与运行原理,正是开发者掌握下一代 AI 系统构建方式的最好起点。



