☰
Spring AI 实战:用 MCP 服务端打通 SSE 流式工具调用
2026/10/1 20:28:11 网站建设 项目流程

1. 为什么要把本地工具用 SSE 暴露给 AI 客户端

如果你正在用 Spring Boot 写后端,手里已经有一堆现成的 Service:查天气的、查订单的、连内部数据库的。现在想让 AI 客户端(比如 Claude Code、Cline、Cursor 这类支持 MCP 的工具)直接调用这些能力,最省事的路径不是重写一遍,而是把 Spring 里的方法包装成 MCP 工具,通过 SSE 暴露出去。这就是 spring AI 实战里 mcp 服务端最典型的落地场景。

MCP(Model Context Protocol)是 AI 客户端和外部工具之间的通信约定。它有两种主流交互方式:stdio 和 SSE。stdio 是客户端和服务端都跑在本地,进程间通过标准输入输出通信,适合个人电脑上的单机工具;SSE(Server-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_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite

登录后创建一个新 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_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite

在对话页里选一个模型,发一句「你好」,能正常返回就说明 Key 和通道没问题。这一步别跳过,因为后面 MCP 客户端调模型失败时,你要能区分是 Key 问题还是 MCP 配置问题。

如果你后续要做长期编码或 Agent 场景,可以了解 Coding Plan:

https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite

它适合把模型调用额度集中管理,配合 MCP 工具链做持续开发。现在先把基础 Key 准备好,我们进入服务端搭建。

3. 可复制配置:Spring Boot MCP 服务端骨架

这一节是核心,所有配置都能直接复制。我按 Maven 项目结构来,JDK 17+,Spring Boot 3.2+。

先看pom.xml的关键依赖。MCP 服务端用 WebFlux 版本,因为 SSE 本质是响应式流:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.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); } }

再写配置类,把工具对象注册成ToolCallbackProvider:

@Configuration 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?sessionId=xxx的输出,这就是服务端在告诉客户端后续消息往哪里发。

4. 验证请求:建立 SSE 连接并完成一次工具调用

服务端跑起来后,需要一个 MCP 客户端来验证。你可以用 Spring AI 写一个客户端,也可以用现成的 MCP 客户端工具。这里我用 Spring Boot 写一个最小客户端,方便你理解整个调用链路。

客户端pom.xml关键依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> </dependency>

客户端配置类,把 MCP 客户端和 ChatClient 组装起来:

@Configuration public class McpClientConfig { @Bean ChatClient chatClient(ChatModel chatModel, List<McpAsyncClient> 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_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

6. 把 MCP 服务端接入 TaoToken 统一通道

服务端跑通后,最后一步是把模型调用稳定地接到 TaoToken 上。前面客户端配置里已经用了base-url: https://taotoken.net/api,这就是统一通道的入口。你可以在控制台里查看用量、管理多个 Key、按项目分配额度:

https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite

实际项目里,我建议把 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 通道。后面要加新工具,只需要写方法、加注解、注册,三步搞定。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询