ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring AI 实战:用 MCP 服务端打通 SSE 流式工具调用

Spring AI 实战:用 MCP 服务端打通 SSE 流式工具调用 1. 为什么要把本地工具用 SSE 暴露给 AI 客户端如果你正在用 Spring Boot 写后端手里已经有一堆现成的 Service查天气的、查订单的、连内部数据库的。现在想让 AI 客户端比如 Claude Code、Cline、Cursor 这类支持 MCP 的工具直接调用这些能力最省事的路径不是重写一遍而是把 Spring 里的方法包装成 MCP 工具通过 SSE 暴露出去。这就是 spring AI 实战里 mcp 服务端最典型的落地场景。MCPModel Context Protocol是 AI 客户端和外部工具之间的通信约定。它有两种主流交互方式stdio 和 SSE。stdio 是客户端和服务端都跑在本地进程间通过标准输入输出通信适合个人电脑上的单机工具SSEServer-Send Events则是服务端独立部署客户端通过 HTTP 长连接订阅事件流适合把能力放到服务器上给多个客户端共用。你要做的是后者一个 Spring Boot 应用作为 MCP 服务端把Tool注解的方法注册成工具客户端通过/sse端点连上来AI 就能在对话里自动调用这些工具。这套方案适合谁后端开发者、需要把内部系统能力开放给 AI Agent 的团队、以及想用统一 Key 通道管理模型调用的工程同学。跑通之后你的 Spring 服务就是一个标准的 MCP Server任何支持 SSE 的 MCP 客户端都能接入。而模型侧的调用我会用 TaoToken 的统一 API 通道来承接这样 Key 管理、模型切换、用量查看都在一个地方不用在多个平台之间来回倒腾。下面从依赖、配置、工具类、验证请求到排错一步步走完。整个过程我按真实项目结构写命令和配置都能直接复制。2. TaoToken 前置准备统一 Key 与 API 通道在写 MCP 服务端之前先把模型调用这一层准备好。MCP 服务端本身只负责暴露工具真正发起对话、决定调用哪个工具的是 AI 客户端和背后的模型。所以你需要一个能稳定调用模型的 API 通道。TaoToken 提供的就是这个一个统一的 Key兼容主流模型接口格式Base URL 固定模型 ID 按需选择。先拿到 Key。打开 API Keys 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite登录后创建一个新 Key复制保存。这个 Key 后面会用在客户端的模型配置里。注意不要把它硬编码进提交到 Git 的配置文件用环境变量或者本地application-local.yml隔离。接着确认 API 入口地址。TaoToken 的 API Base URL 是https://taotoken.net/api这个地址不加任何 UTM 参数直接作为base-url使用。模型 ID 方面你可以先用claude-sonnet-4-20250514或者gpt-4o这类通用模型做验证等工具调用链路跑通后再换成更适合编码的模型。模型对话页面可以快速测试 Key 是否可用https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite在对话页里选一个模型发一句「你好」能正常返回就说明 Key 和通道没问题。这一步别跳过因为后面 MCP 客户端调模型失败时你要能区分是 Key 问题还是 MCP 配置问题。如果你后续要做长期编码或 Agent 场景可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite它适合把模型调用额度集中管理配合 MCP 工具链做持续开发。现在先把基础 Key 准备好我们进入服务端搭建。3. 可复制配置Spring Boot MCP 服务端骨架这一节是核心所有配置都能直接复制。我按 Maven 项目结构来JDK 17Spring Boot 3.2。先看pom.xml的关键依赖。MCP 服务端用 WebFlux 版本因为 SSE 本质是响应式流dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webflux/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency dependency groupIdcom.alibaba.fastjson2/groupId artifactIdfastjson2/artifactId version2.0.52/version /dependency如果你用的是 Spring AI 1.0.0-M6 或相近版本记得在dependencyManagement里引入 Spring AI BOM避免版本冲突。实测下来M6 的 MCP starter 已经比较稳定SSE 端点开箱即用。接着写工具类。工具方法用Tool注解description 会暴露给模型模型根据描述决定是否调用Component public class WeatherTool { Tool(description 获取指定城市的当前天气预报输入城市中文名如北京、上海) String getCurrentWeather(String city) { System.out.println(开始获取天气预报: city); String cityCode getCityCode(city); if (cityCode null) { return 未找到城市: city; } Map result RestClient.create( URI.create(http://t.weather.itboy.net/api/weather/city/ cityCode)) .get() .retrieve() .body(Map.class); return JSON.toJSONString(result.get(data)); } private String getCityCode(String city) { String json {\北京\:\101010100\,\上海\:\101020100\,\广州\:\101280101\}; return JSON.parseObject(json).getString(city); } }再写配置类把工具对象注册成ToolCallbackProviderConfiguration public class McpConfig { Bean ToolCallbackProvider toolCallbackProvider(WeatherTool weatherTool) { return MethodToolCallbackProvider.builder() .toolObjects(weatherTool) .build(); } }然后是application.yml这是 MCP 服务端最关键的配置server: port: 8082 spring: ai: mcp: server: name: demo-mcp-server version: 1.0.0 type: ASYNC sse-endpoint: /sse sse-message-endpoint: /mcp/message这里几个参数要解释清楚。type: ASYNC表示用异步模式配合 WebFlux 的非阻塞特性sse-endpoint: /sse是客户端建立 SSE 连接的路径sse-message-endpoint是客户端回传消息的路径默认是/mcp/message一般不用改。name和version会出现在 MCP 握手信息里方便客户端识别。启动类就是普通的 Spring Boot 启动类不需要额外注解。启动后访问http://127.0.0.1:8082/sse如果看到连接保持、不断有事件输出说明 SSE 端点已经工作。注意用浏览器直接打开会一直转圈这是正常的因为 SSE 是长连接。用 curl 验证更直观curl -N http://127.0.0.1:8082/sse你会看到类似event: endpoint和data: /mcp/message?sessionIdxxx的输出这就是服务端在告诉客户端后续消息往哪里发。4. 验证请求建立 SSE 连接并完成一次工具调用服务端跑起来后需要一个 MCP 客户端来验证。你可以用 Spring AI 写一个客户端也可以用现成的 MCP 客户端工具。这里我用 Spring Boot 写一个最小客户端方便你理解整个调用链路。客户端pom.xml关键依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client-webflux/artifactId /dependency客户端配置类把 MCP 客户端和 ChatClient 组装起来Configuration public class McpClientConfig { Bean ChatClient chatClient(ChatModel chatModel, ListMcpAsyncClient mcpClients) { return ChatClient.builder(chatModel) .defaultToolCallbacks(Objects.requireNonNull( AsyncMcpToolCallbackProvider.asyncToolCallbacks(mcpClients) .collectList().block())) .build(); } Bean RestClient.Builder restClientBuilder() { return RestClient.builder(); } }客户端application.yml重点是模型走 TaoToken 通道MCP 指向本地服务端server: port: 8081 spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-sonnet-4-20250514 mcp: client: name: demo-mcp-client enabled: true type: ASYNC request-timeout: 60s sse: connections: server: url: http://127.0.0.1:8082注意base-url用 TaoToken 的 API 地址api-key从环境变量读别写死在文件里。模型 ID 按你实际可用的填。MCP 的sse.connections.server.url指向服务端的/sse端点注意这里只写到端口路径由客户端自动拼接。写一个测试接口触发对话RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient chatClient; } GetMapping(/ask) public String ask(RequestParam String q) { return chatClient.prompt(q).call().content(); } }启动客户端访问curl http://127.0.0.1:8081/ask?q北京今天天气怎么样如果一切正常你会看到模型返回类似「北京今天晴气温 25 度」的内容同时服务端控制台打印「开始获取天气预报: 北京」。这说明模型识别到了工具描述通过 SSE 调用了服务端的getCurrentWeather方法拿到结果后再生成自然语言回复。整个链路客户端 → TaoToken 模型 → 决定调用工具 → SSE 通知服务端 → 服务端执行 → 结果回传 → 模型总结。这一步跑通MCP 服务端就算真正接入了。你可以把WeatherTool换成任何内部 Service比如订单查询、库存检查只要加Tool注解并注册到ToolCallbackProvider即可。5. 本篇常见错排查401、local proxy failed 与 reading choices实际搭建时报错集中在几个地方。我按真实遇到的顺序列出来。401 Unauthorized。这个基本是 Key 问题。检查TAOTOKEN_API_KEY环境变量是否真的注入到进程里用echo $TAOTOKEN_API_KEY确认。如果是 IDE 启动注意 Run Configuration 里的环境变量配置别只在终端 export。另外确认base-url写的是https://taotoken.net/api末尾不要多加/v1路径由客户端库自己拼。local proxy failed / connection refused。这个报错通常出现在 MCP 客户端连服务端时。检查服务端是否真的在 8082 端口监听netstat -an | grep 8082看一下。如果服务端启动日志里有Netty started on port 8082但客户端还是连不上检查sse.connections.server.url是不是写成了http://127.0.0.1:8082/sse。正确写法是只写到端口路径由 MCP 客户端自动加。我踩过的坑就是多写了/sse导致客户端请求变成/sse/sse直接 404。reading choices 相关报错。这个一般出现在模型返回格式解析阶段。如果你用的是 OpenAI 兼容接口但模型返回的 JSON 结构不符合预期就会报reading choices之类的解析错误。先确认模型 ID 是否正确有些模型不支持工具调用function calling换一个支持工具调用的模型再试。另外检查spring.ai.openai.chat.options.model有没有拼写错误。OAuth 相关报错。如果你在客户端配置里启用了 OAuth 认证但服务端没配对应的鉴权会报 OAuth 握手失败。本地验证阶段先把 OAuth 关掉专注跑通 SSE 和工具调用。等链路稳定后再加鉴权。工具没被调用。模型返回了文字但没触发工具通常是Tool的 description 写得太模糊。模型靠 description 判断是否调用描述要具体比如「获取指定城市的当前天气预报输入城市中文名」就比「天气工具」好得多。另外确认ToolCallbackProvider里注册了工具对象漏注册的话客户端根本看不到这个工具。SSE 连接建立后立即断开。检查request-timeout配置默认可能太短。设成60s或更长。另外 WebFlux 环境下注意不要引入spring-boot-starter-web两者冲突会导致响应式流异常。排错时建议开两个终端一个看服务端日志一个看客户端日志对照时间戳定位是哪一段断了。接入文档里有更详细的参数说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite6. 把 MCP 服务端接入 TaoToken 统一通道服务端跑通后最后一步是把模型调用稳定地接到 TaoToken 上。前面客户端配置里已经用了base-url: https://taotoken.net/api这就是统一通道的入口。你可以在控制台里查看用量、管理多个 Key、按项目分配额度https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite实际项目里我建议把 MCP 服务端和模型调用分开部署。MCP 服务端只负责工具执行不关心模型是谁客户端负责模型调用和工具编排。这样换模型、换 Key 都不影响工具层。TaoToken 的通道兼容 OpenAI 接口格式Spring AI 的spring-ai-openaistarter 直接就能用不需要额外适配层。如果你要做更复杂的 Agent 场景比如多轮工具调用、条件分支可以在客户端用ChatClient的流式接口配合 MCP 的异步回调。Spring AI 的AsyncMcpToolCallbackProvider已经处理了工具结果的回传你只需要关注业务逻辑。最后给一个实用技巧把常用的工具方法按领域拆成多个Component每个类专注一类能力然后在McpConfig里一次性注册。这样工具列表清晰模型选择时也不容易混淆。工具多了之后description 的准确性比数量更重要宁可少而精。整套流程走下来你的 Spring Boot 应用就是一个标准的 MCP 服务端任何支持 SSE 的 AI 客户端都能接入模型调用统一走 TaoToken 通道。后面要加新工具只需要写方法、加注解、注册三步搞定。
返回列表