1. Spring Boot 项目接入 MCP 时,SSE 长连接为什么会断
如果你正在用 Spring Boot 3.x 加 SpringAI 做 MCP 服务,本地单机跑得好好的,一上多实例就出现客户端反复重连、工具调用偶发失败,那大概率不是模型的问题,而是 SSE 会话没有做统一管理。MCP 的 SSE 传输本质上是客户端先请求/sse拿到一个 sessionId,再通过/sse/mcp/message这条长连接通道双向收发 JSON-RPC 消息。单机时 session 存在本地 Map 里没问题,可一旦部署两个以上实例,负载均衡把心跳和消息请求打到不同机器上,B 机器找不到 A 机器创建的 session,就会返回 404,客户端认为服务端挂了,主动断开。
这篇内容聚焦 Spring Boot 项目通过 SpringAI 接入 MCP 服务的完整链路,结合 SSE 传输与 Redis 做会话缓存。我会给出可复制的application.yml与 MCP 客户端 Bean 配置,并演示一次 SSE 握手与工具调用的验证动作,帮你快速跑通本地联调。适合谁:已经写过 Spring Boot 接口、想把自己的业务方法暴露成 MCP 工具给 AI 客户端调用的后端同学;也适合正在做分布式 MCP 服务、被 session 漂移问题卡住的开发者。核心检索词就是 SpringAI 接入 MCP、SSE 传输、Spring Boot 与 Redis 会话缓存,下面按可跟做的顺序展开。
先说清楚 MCP 在这里扮演什么角色。你可以把它理解成一层标准化的“工具插座”:你的 Spring Boot 服务把方法用@Tool标注后注册成工具,AI 客户端通过 MCP 协议发现并调用这些工具,不用为每个模型单独写适配。SpringAI 提供了spring-ai-starter-mcp-server-webmvc这个 starter,基于 WebMVC 暴露 SSE 端点,接入成本比手写协议低很多。我试过从零搭一个带工具调用的 MCP 服务,半小时能跑通握手,真正花时间的是分布式下的 session 一致性,这也是后面重点。
环境基线先对齐,避免版本差异导致配置不生效:Java 21,Spring Boot 3.4.4,SpringAI BOM 1.0.1。MCP 服务端用 web 方式接入,传输选 SSE。下面每一步都给完整片段,你可以直接复制到自己的工程里改包名。
2. TaoToken 前置准备:统一 Key 与 Base URL 怎么配
在写 MCP 客户端之前,先把模型调用这一层的前置条件准备好。很多同学卡在“工具注册成功了,但模型侧调不通”,其实是 Key 和 Base URL 没统一。TaoToken 在这里的作用是提供一个统一的 API 入口,让你在 Spring Boot 里配置一次就能切换不同模型,不用为每个供应商改代码。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数,配置时直接用这个。
你需要先拿到一个 API Key。进入控制台创建密钥,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,创建后在密钥管理页复制。如果你还没决定用哪个模型,可以先去模型对话页试一下 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite ,确认模型能正常返回再写进配置。密钥的完整列表在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到参数不确定时以文档为准。
这里要强调一个容易踩的点:MCP 服务端和模型客户端是两套配置。MCP 服务端负责暴露工具,模型客户端负责调用模型并决定是否触发工具。TaoToken 的 Key 用在模型客户端这一侧,也就是 SpringAI 的 ChatClient 或 ChatModel 配置里。Base URL 填https://taotoken.net/api,Key 填你复制的那串,Model ID 填你要用的模型标识。这三件套缺一不可,后面在application.yml里会体现。
如果你打算长期做编码类或 Agent 类项目,可以了解下 Coding Plan,路径是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,它更适合持续调用场景。但本篇的重点是 MCP 接入链路,Key 准备好就够了,不用在这一步纠结套餐。把 Key 先放到环境变量里,别硬编码进代码,这是基本习惯。
3. 可复制配置:application.yml 与 MCP 客户端 Bean
这一节是全文的核心,给全可复制的配置片段。先看 MCP 服务端的application.yml,路径是src/main/resources/application.yml,关键配置如下:
server: port: 8088 spring: ai: mcp: server: name: vMcpServer version: "1.0.0" enabled: true sse-endpoint: /sse sse-message-endpoint: /sse/mcp/message type: sync data: redis: host: 127.0.0.1 port: 6379 database: 0 timeout: 3000mstype: sync表示同步处理,本地联调够用。sse-endpoint和sse-message-endpoint这两个路径要和客户端约定一致,改了就两边一起改。Redis 配置是为分布式 session 准备的,单机调试可以先不启,但建议一开始就接上,避免后期改造。
依赖部分,pom.xml里先引入 BOM 再引 starter:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency>工具定义用@Tool和@ToolParam,和写 RPC 接口很像:
@Service public class HMCPService { @Tool(description = "查询用户信息") public UserInfoLabelDressModel getUserInfo( @ToolParam(description = "用户ID") Long userId) { return new UserInfoLabelDressModel(); } @Tool(description = "查询当前在线的所有策略") public List<StrategyInfoModel> getAllOnlineStrategy() { return List.of(new StrategyInfoModel()); } @Tool(description = "通过策略id获取策略") public String getStrategy(@ToolParam(description = "策略id") Long id) { return "策略内容x"; } }模型类加@Schema描述字段,客户端才能自动理解每个字段含义:
@Data public class StrategyInfoModel { @Schema(description = "策略id") private Long id; @Schema(description = "策略key") private String key; @Schema(description = "策略说明") private String description; }注册工具提供者,让 MCP 客户端能发现这些方法:
@Bean public ToolCallbackProvider myTools(HMCPService hMCPService, VMCPService vMCPService) { return MethodToolCallbackProvider.builder() .toolObjects(hMCPService, vMCPService) .build(); }到这里服务端就绪。接下来是模型客户端侧,也就是用 TaoToken 统一 Key 的地方。在application.yml里加:
spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: your-model-idbase-url填 TaoToken 的 API 入口,api-key从环境变量读,model填你在模型对话页确认过的 Model ID。这三件套就是 Base URL、Key、Model ID,缺一个都会在启动或调用时报错。客户端 Bean 可以这样写:
@Bean public ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider tools) { return builder .defaultToolCallbacks(tools) .build(); }把工具回调挂到 ChatClient 上,模型在对话时就能自动决定是否调用你注册的 MCP 工具。配置层面就这些,下面进入验证环节。
4. 验证请求:SSE 握手与工具调用成功结果
配置写完,先验证 MCP 服务端是否正常暴露。启动 Spring Boot 应用,浏览器或 curl 访问http://localhost:8088/sse,正常会看到类似下面的 SSE 数据流:
event: endpoint data: /sse/mcp/message?sessionId=8f3a1c2e-xxxx这里有个关键细节:data里的地址是后续消息通道的入口,不能直接当普通接口访问,它需要以 SSE 协议保持长连接。看到这个输出,说明 MCP 服务端已经部署成功,session 也创建了。如果返回 404 或空白,先检查sse-endpoint配置和端口是否被占用。
接着验证工具调用。用 MCP 客户端或支持 MCP 的 AI 客户端连上http://localhost:8088/sse,发起一次工具列表查询,应该能看到getUserInfo、getAllOnlineStrategy、getStrategy三个工具及其参数描述。再发起一次实际调用,比如让模型执行“查询策略 id 为 1 的内容”,观察服务端日志是否打印工具执行记录,返回结果是否为策略内容x。
模型侧验证用 TaoToken 的 Key 跑一次对话,确认模型能识别工具并触发调用。如果模型返回了工具调用意图但没执行,检查defaultToolCallbacks是否挂上;如果执行了但报错,看服务端异常栈。实测下来,握手成功加一次工具调用成功,基本就说明整条链路通了。本地联调阶段建议把日志级别调到 DEBUG,方便看 JSON-RPC 消息往返。
分布式部署时,SSE 长连接会在实例间漂移,心跳请求可能落到没有该 session 的机器上。解决方案是用 Redis 做 session 广播:每台机器维护自己的 session 对象,发消息时通过 Redis 的 pub/sub 让所有机器从本机查找对应 session 处理。核心改动是替换默认的WebMvcSseServerTransportProvider,自定义一个 transport provider,在handleMessage里把 sessionId 和请求体广播出去,订阅方收到后找到本机 session 再执行。这样客户端请求固定路由到任意实例都能被正确处理,不会因为找不到 session 而断开。
5. 常见报错排查:401、local proxy failed 与 session not found
接入过程中有几类报错特别高频,逐个对照排查。
第一类是 401 Unauthorized。模型侧调用返回 401,基本是 Key 或 Base URL 配错。检查spring.ai.openai.api-key是否读到了环境变量,base-url是否是https://taotoken.net/api,注意结尾不要多加斜杠。如果 Key 是从控制台复制的,确认没有多余空格。MCP 服务端本身不校验这个 Key,401 只会出现在模型调用链路上。
第二类是 local proxy failed 或连接被拒。这类通常出现在客户端连 MCP 服务端时,检查服务是否真的启动在 8088 端口,sse-endpoint路径是否和客户端请求一致。如果用了容器,确认端口映射正确。还有一种情况是 SSE 长连接被中间层缓冲,导致消息延迟,检查是否有网关做了响应缓冲。
第三类是Session not found: xxx。这是分布式部署最典型的报错,客户端拿着 A 机器创建的 sessionId 请求打到了 B 机器。单机不会出现,多实例必现。解决方式就是前面说的 Redis 广播方案,或者用一致性哈希把同一 session 固定到同一实例。前者更灵活,后者实现简单但扩容时会重新分布。如果只是本地联调,先确认是不是误开了多实例。
第四类是Failed to deserialize message或reading choices相关错误。前者是 JSON-RPC 消息格式不对,检查客户端发送的 body 是否符合 MCP schema;后者多出现在模型返回结构解析时,确认 Model ID 填的是支持工具调用的模型,有些模型不支持 function calling,会返回纯文本导致解析失败。
第五类是 OAuth 或鉴权相关报错。如果你在 MCP 客户端侧配了额外的鉴权头,确认格式正确。TaoToken 的调用只需要 Bearer Key,不需要额外 OAuth 流程。遇到不确定的报错,先去接入文档对照参数,路径是 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,大部分配置问题文档里都有说明。
排查顺序建议:先确认服务端/sse能握手,再确认工具列表能拉到,最后确认模型能触发调用。每一步单独验证,比一上来就端到端调更容易定位。
6. 继续深入:把 MCP 接入用到长期编码与 Agent 场景
链路跑通之后,你可以把这套配置用到更实际的场景。比如把内部系统的查询接口包装成 MCP 工具,让 AI 客户端在对话中直接调用,省去手动查库。或者结合 Coding Plan 做长期编码辅助,路径是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,适合需要持续调用模型的 Agent 类项目。密钥管理和模型切换都在控制台完成,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,需要新 Key 时去 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 创建。
一个实用技巧:把 MCP 工具的@Tool描述写清楚,模型选择工具的准确率会明显提升。描述里带上参数含义和返回结构,比只写方法名效果好很多。另一个技巧是分布式环境下给 session 加过期时间,避免 Redis 里堆积无效 session。Redis 的 pub/sub 不持久化,机器重启后 session 会丢,客户端需要重连,这是正常行为,做好重连逻辑即可。
最后提醒一点,MCP 服务端暴露的是你的业务方法,上线前确认工具方法的权限和入参校验,别把敏感操作直接暴露出去。工具描述里也不要写内部地址或密钥信息。把这几步做完,Spring Boot 加 SpringAI 加 MCP 加 SSE 加 Redis 的完整链路就算真正落地了。