1. 为什么要在 Spring AI + MCP 里引入统一 API 通道
如果你正在用 Spring AI 搭智能体,大概率会遇到一个很现实的问题:MCP 负责把外部工具接进来,但模型调用这一层还是散的。服务端一个 Key、客户端一个 Key、本地调试再换一个 Key,项目一多,配置就开始打架。Spring AI 本身对 MCP 的支持已经比较完整,spring-ai-mcp-server和spring-ai-mcp-client两个 starter 能把工具注册、SSE 传输、工具回调这条链路串起来,但模型侧的接入点如果每个环境都写死,后面换模型、换环境、做灰度就会很痛苦。
这篇要解决的就是这件事:用 TaoToken 作为统一的 API 通道,把 Spring AI 的 Chat 调用和 MCP 工具调用收敛到一套 Key 和一套 base-url 上。你最终会得到一个可复制的application.yml、一个 MCP 客户端配置骨架,以及三步验证动作——启动 Spring Boot 服务、确认 MCP 工具注册成功、通过统一 Key 完成一次端到端智能体调用。适合已经写过 Spring Boot、想快速把 MCP 工具链接进智能体的同学,也适合正在做多环境配置收敛的后端同学。
MCP 你可以理解成 AI 世界的 USB-C:模型本身不关心工具怎么实现,只要工具按协议暴露出来,客户端就能发现并调用。Spring AI 把这层协议封装成了 starter,你写@Tool注解、注册ToolCallbackProvider,剩下的握手和 JSON-RPC 消息由框架处理。而 TaoToken 在这里扮演的是模型入口的“统一插座”,让 Chat 请求和工具调用共享同一个出口。
2. TaoToken 前置准备:Key 与接入信息
在动手改配置之前,先把模型侧的入口准备好。打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册后进入控制台,在 API Keys 页面创建一个 Key。这个 Key 后面会同时用在服务端和客户端的模型调用上,所以建议按项目命名,比如spring-ai-mcp-demo,方便后面排查是哪个环境在用。
创建完 Key 之后,你需要记下两个东西:一个是 Key 本身,另一个是 API 的 base-url。TaoToken 的 API 地址是 https://taotoken.net/api ,注意这个地址不带任何查询参数,直接作为base-url使用。如果你用的是 OpenAI 兼容模式,Spring AI 的spring.ai.openai.base-url就填这个值;如果你走的是其他兼容协议,也以控制台文档里的说明为准。
这里有个容易踩的点:很多人会把官网地址和 API 地址混用。官网是给人看的,API 是给程序调的,两者不要互相替换。配置里只写 API 地址,浏览器里访问官网和控制台。另外,Key 不要硬编码进代码提交到仓库,本地用环境变量或者application-local.yml,线上用配置中心或者环境变量注入。
如果你后面要做长期编码或者 Agent 类的常驻任务,可以关注一下 Coding Plan 相关的入口;如果只是先验证模型对话是否通,可以直接用模型对话页面做一次手动测试,确认 Key 有效之后再写进 Spring 配置。接入文档里有各语言 SDK 的示例,Java 这边主要看 OpenAI 兼容的写法即可。
3. 可复制配置:application.yml 与 MCP 客户端骨架
下面这份配置可以直接复制到你的 Spring Boot 项目里,按注释替换 Key 和地址即可。服务端和客户端可以放在同一个工程里用 profile 区分,也可以拆成两个服务。这里为了演示清晰,把 MCP 服务端和客户端的配置都列出来。
先看服务端的application.yml,重点是模型走 TaoToken,MCP 服务端开启并暴露 SSE 端点:
server: port: 8888 spring: ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: gpt-4o-mini stream: true mcp: server: enabled: true name: ai_mcp_server version: 1.0.0 type: ASYNC sse-message-endpoint: /mcp/message服务端的工具类还是用@Tool注解暴露能力,这里保留查询天气和查 IP 两个示例,你可以替换成自己的业务方法:
public class CommonTool { @Tool(description = "获取当前天气预报") WeatherResponse getCurrentWeather(WeatherRequest request) { System.err.printf("准备查询【%s】天气预报%n", request.city()); RestClient client = RestClient.create(URI.create("https://api.vvhan.com")); Map<?, ?> result = client.get() .uri("/api/weather?city={0}", request.city()) .retrieve() .body(Map.class); try { return new WeatherResponse(new ObjectMapper().writeValueAsString(result)); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } @Tool(description = "获取IP地址详细信息") String getIpAddressInfo(String ip) { System.err.printf("准备查询【%s】详细信息%n", ip); RestClient client = RestClient.create(URI.create("https://api.vvhan.com")); Map<?, ?> result = client.get() .uri("/api/ipInfo?ip={0}", ip) .retrieve() .body(Map.class); try { return new ObjectMapper().writeValueAsString(result); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } }工具注册用ToolCallbackProvider统一收口,这样客户端启动时能自动发现:
@Configuration public class ToolsConfig { @Bean ToolCallbackProvider tools() { ToolCallback[] toolCallbacks = ToolCallbacks.from(new CommonTool()); return ToolCallbackProvider.from(toolCallbacks); } }再看客户端的application.yml,模型同样走 TaoToken,MCP 客户端指向服务端的 SSE 地址:
server: port: 8080 spring: ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: gpt-4o-mini stream: true mcp: client: enabled: true name: ai-mcp-client initialized: true type: ASYNC sse: connections: server1: url: http://localhost:8888客户端的 Controller 里注入ToolCallbackProvider,把它挂到ChatClient的默认工具上,这样一次对话就能触发 MCP 工具调用:
@RestController @RequestMapping("/tools") public class ToolController { private final ChatClient chatClient; public ToolController(ChatClient.Builder aiClientBuilder, ToolCallbackProvider mcpTools) { this.chatClient = aiClientBuilder .defaultTools(mcpTools) .build(); } @GetMapping("/weather") public ResponseEntity<String> getCurrentWeather(String prompt) { System.err.println(prompt); String response = this.chatClient .prompt(prompt) .call().content(); return ResponseEntity.ok(response); } @GetMapping("/ip") public ResponseEntity<String> getIpAddressInfo(String prompt) { System.err.println(prompt); String response = this.chatClient .prompt(prompt) .call().content(); return ResponseEntity.ok(response); } }依赖方面,服务端引入spring-ai-mcp-server-spring-boot-starter和spring-ai-mcp-server-webflux-spring-boot-starter,客户端引入spring-ai-mcp-client-spring-boot-starter和spring-ai-mcp-client-webflux-spring-boot-starter,模型侧用 Spring AI 的 OpenAI starter 即可。版本上注意 Spring Boot 3.4.x 搭配 Spring AI 1.0.x 的 M6 系列,避免 starter 版本错配导致自动配置不生效。
4. 三步验证:启动、工具注册、端到端调用
配置写完不要急着写业务,先按这三步把链路验通,后面出问题也好定位。
第一步,启动 MCP 服务端。运行 Spring Boot 主类,观察控制台是否出现 SSE 端点注册的日志。默认会开启/sse端点,同时还有一个消息传输端点。如果端口 8888 被占用,改server.port即可。启动成功后,你可以先用浏览器或者 curl 访问一下 SSE 端点,确认服务端在监听。
第二步,启动客户端并确认工具注册成功。客户端启动时会主动连接服务端,拉取可用工具列表。控制台会打印类似下面的 JSON-RPC 消息,说明工具已经注册进来了:
{ "jsonrpc": "2.0", "id": "66d12dae-1", "result": { "tools": [ { "name": "getCurrentWeather", "description": "获取当前天气预报", "inputSchema": { "type": "object", "properties": { "request": { "type": "object", "properties": { "city": { "type": "string", "description": "城市" } }, "required": ["city"] } }, "required": ["request"], "additionalProperties": false } }, { "name": "getIpAddressInfo", "description": "获取IP地址详细信息", "inputSchema": { "type": "object", "properties": { "ip": { "type": "string" } }, "required": ["ip"], "additionalProperties": false } } ] } }看到getCurrentWeather和getIpAddressInfo两个工具名,就说明 MCP 客户端和服务端的握手完成了。如果这里只看到一个或者一个都没有,先检查服务端是否真的启动了、客户端sse.connections.server1.url是否写对、端口是否一致。
第三步,发一次端到端请求。用 curl 或者浏览器访问客户端的接口,prompt 里带上城市名,让模型自己决定调用哪个工具:
curl "http://localhost:8080/tools/weather?prompt=帮我查一下杭州现在的天气"如果链路通了,你会看到模型返回的天气信息,同时服务端控制台会打印准备查询【杭州】天气预报,说明工具真的被调用了,而不是模型在编答案。这一步同时验证了三件事:TaoToken 的 Key 有效、Spring AI 的 Chat 调用正常、MCP 工具调用链路打通。
5. 本篇常见错排查
实际跑的时候,报错基本集中在几个地方,这里按现象列一下排查顺序。
第一个高频问题是启动客户端时报连接被拒绝。先确认服务端是不是真的起来了,http://localhost:8888/sse能不能访问。如果服务端在另一台机器或者容器里,localhost要换成实际地址。另外注意 SSE 是长连接,中间如果有反向代理,要确认代理没有把连接超时设得太短。
第二个是工具注册为空。控制台没有打印工具列表,或者打印了但tools数组是空的。这种情况先看服务端的ToolsConfig有没有被扫描到,@Configuration类是否在启动类的包路径下。再看@Tool注解的方法是不是 public,参数类型是否可序列化。Spring AI 通过反射读取方法签名生成 inputSchema,如果参数是复杂对象,嵌套层级不要太深。
第三个是模型调用返回 401 或 403。这通常是 Key 的问题,检查TAOTOKEN_API_KEY环境变量有没有真的注入进去,base-url是不是写成了官网地址。注意base-url只写到https://taotoken.net/api,不要在后面拼/v1或者/chat/completions,Spring AI 会自己补路径。如果还是 401,去控制台确认 Key 有没有被禁用或者额度耗尽。
第四个是模型不调用工具,直接自己回答。这往往是因为ChatClient构建时没有挂defaultTools,或者ToolCallbackProvider注入失败。检查构造函数里是不是真的把mcpTools传进去了。另外 prompt 要稍微明确一点,比如“帮我查一下杭州现在的天气”,比“天气”更容易触发工具调用。
第五个是版本冲突导致的NoSuchMethodError或者自动配置不生效。Spring AI 的 starter 版本要和 Spring Boot 版本对齐,MCP 相关的 starter 之间版本也要一致。如果用了 Alibaba 的 starter,注意它和官方 starter 的依赖关系,避免同一个类被两个版本加载。
6. 把统一通道用起来:后续接入与长期编码
链路验通之后,你可以把 TaoToken 的 Key 收敛到配置中心,服务端和客户端共用同一个环境变量,换环境只改变量不改代码。MCP 工具这边,新增工具只需要在服务端加@Tool方法并重新注册ToolCallbackProvider,客户端重启后会自动发现,不需要改客户端代码。
如果你要接更多模型或者做多模型路由,统一 base-url 的好处就体现出来了:客户端配置不用动,只在 TaoToken 控制台调整模型映射即可。需要看具体接入参数的话,接入文档里有各协议的说明;想先手动验证模型是否可用,可以用模型对话页面发一条消息试试;长期跑编码类或者 Agent 类任务,可以了解 Coding Plan 的额度方式。API Keys 页面负责创建和轮换 Key,控制台负责查看用量和调用记录,这几个入口配合起来,基本能覆盖从开发到上线的整个流程。
最后提醒一句,MCP 工具方法里如果涉及外部 HTTP 调用,记得加超时和异常兜底,否则工具卡住会把整个对话链路拖死。工具描述写清楚一点,模型选择工具的准确率会明显高一些。