图解源码:万字解析 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...
相关推荐
骄马之死4 小时前
SpringMVC + SpringBoot 核心知识点总结
java·spring boot·后端
GoGeekBaird5 小时前
Anthropic技能"(Skills)的经验分享
后端
王码码20356 小时前
多台服务器怎么统一看状态?Beszel 轻量监控,搭起来不费事
运维·服务器·后端·安全·阿里云·接口·web
刀法如飞6 小时前
一文搞懂DDD 领域驱动设计思想原理
设计模式·架构·代码规范
郑洁文6 小时前
基于Spring Boot的流浪动物救助网站
java·spring boot·后端·毕设·流浪动物救助
螺丝钉code7 小时前
JAVA项目 Claude code CLAUDE.md 到底应该怎么写
java·人工智能·claude code
Cosolar7 小时前
LlamaIndex 文档解析与分块策略深度解析
人工智能·面试·架构
指令集梦境7 小时前
Cursor + Spring Boot实战:从零写一个RESTful API
spring boot·后端·restful
摇滚侠8 小时前
Maven 入门+高深 单一架构案例 54-59
java·架构·maven·intellij-idea
caimouse8 小时前
Reactos 第 4 章 对象管理 — 4.5 几个常用的内核函数
c语言·windows·架构