图解源码:万字解析 Spring AI 到底是怎么实现 MCP 的

一、前言:Spring AI 与 MCP

在过去的一年里,大模型生态的一个明显趋势是从模型调用走向模型协作。不仅仅是怎么让大模型回答问题,更是怎么让模型具备执行、协同与插件化能力。Spring AI 正是在这一背景下诞生的,它试图以 Spring 的方式,将 AI 能力融入现有的企业级应用体系中。

Spring AI 本质上是一个为 Java 生态设计的 AI 集成框架。它统一了不同模型(如 OpenAI、Anthropic、Ollama 等)的调用方式,并提供了 Prompt 模板、输出解析、工具调用(Tool Invocation)等上层抽象,让开发者可以像使用 RestTemplate 一样优雅地调用大模型。

Spring AI 在 2024 年引入了对 MCPModel 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 的方式传给了会话对象),这个方法会构建给客户端的返回结果,即服务端的基本信息、协议版本等。如下所示。

最后则调用传输层 HttpServletMcpSessionTransportsendMessage() 方法通过 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 系统构建方式的最好起点。

参考资料

  1. Spring AI MCP 浅析_springboot mcp如何告诉大模型参数含义-CSDN博客
  2. SpringAI(GA):MCP源码解读
  3. github.com/modelcontex...
  4. 深度挖掘MCP协议底层架构,单步跟踪细节一览无遗_哔哩哔哩_bilibili
  5. modelcontextprotocol.io/sdk/java/mc...
相关推荐
Real_man2 小时前
Python SQLAlchemy 全生命周期指南:从模型、迁移到优雅会话管理
后端
好学且牛逼的马2 小时前
【SSM 框架 | day27 spring MVC】
java
是烟花哈2 小时前
后端开发CRUD实现
java·开发语言·spring boot·mybatis
JELEE.2 小时前
Django中的clean()方法和full_clean()方法
后端·python·django
aiopencode2 小时前
iOS 上架工具全解析,从 Xcode 到 开心上架(Appuploader)跨平台命令行免 Mac 上传指南
后端
爱分享的鱼鱼2 小时前
Java基础(六:线程、线程同步,线程池)
java·后端
申阳3 小时前
Day 8:06. 基于Nuxt开发博客项目-我的服务模块开发
前端·后端·程序员
quant_19863 小时前
全面解析美股行情API
经验分享·后端·python·websocket·程序人生·区块链
随便叫个啥呢3 小时前
java使用poi-tl模版+vform自定义表单生成word,使用LibreOffice导出为pdf,批量下载为压缩文件
java·pdf·word·zip
CodeCraft Studio3 小时前
国产化Word处理控件Spire.Doc教程:使用Java将RTF文件转换为PDF的全面教程
java·pdf·word·spire.doc·rtf转pdf·文件格式转换·文档开发sdk