1. Spring AI MCP 服务端接入模型通道时到底卡在哪
如果你正在用 Spring AI 写 MCP 服务端,大概率会遇到一个很具体的场景:工具方法写好了,@Tool注解也加了,ToolCallbackProvider也注册成 Bean 了,但服务端一启动,模型侧就是没反应。日志里翻来翻去,要么是连接超时,要么是 401,要么是reading choices解析失败。问题往往不在 MCP 协议本身,而在服务端到模型服务这一段通道没有配通。
Spring AI MCP 服务端本质上是一个“工具暴露层”。它把 Java 方法包装成 MCP 工具,通过 STDIO 或 SSE 传输层暴露给客户端。但工具要被模型调用,服务端自己得先能访问模型服务。也就是说,MCP 服务端同时扮演两个角色:对客户端它是工具提供方,对模型服务它是调用方。很多人只关注了前者,忽略了后者,结果就是工具注册成功、SSE 端点也能连上,但模型请求发不出去。
这篇面向的是本地开发与联调场景。你不需要先搞一套复杂的网关,也不需要把每个模型厂商的 SDK 都接一遍。核心思路是:用统一的 Key 和统一的 API 通道,让 Spring AI MCP 服务端通过一套配置同时完成“工具暴露”和“模型调用”两件事。配置一次,服务端到模型的请求链路就能跑通。
适合谁看:正在用 Spring AI 1.0 以上版本写 MCP 服务端的 Java 开发者;需要本地联调 MCP 工具与模型交互的后端同学;以及想把现有 Spring Boot 服务快速改造成 MCP 服务端的团队。下面会给出可复制的application.yml、Maven 依赖、启动参数,以及一次完整的调用验证动作。你照着做,能少走不少弯路。
2. TaoToken 统一 Key 与 API 通道的前置准备
在动手改配置之前,先把“统一 Key 与 API 通道”这件事说清楚。Spring AI 本身支持多种模型服务,但不同厂商的 Base URL、鉴权头、模型 ID 格式都不一样。如果你在 MCP 服务端里直接写死某一家,后面换模型就得改代码。更麻烦的是,MCP 服务端通常还要同时处理工具调用和普通对话,如果通道不统一,联调时很难判断问题出在工具层还是模型层。
TaoToken 在这里的角色是一个统一的 API 通道。你只需要一个 Key,就可以通过同一个 Base URL 访问不同的模型。对 Spring AI 来说,这意味着spring.ai.openai.base-url和spring.ai.openai.api-key可以固定下来,模型 ID 通过配置切换。MCP 服务端的工具注册逻辑完全不用动,换模型只是改一行配置。
前置准备分三步。第一步,拿到 Key。访问https://taotoken.net/api-keys,在控制台里创建一个 API Key。注意这个 Key 只在创建时完整显示一次,复制后先存到本地环境变量里,不要直接写进代码仓库。第二步,确认 Base URL。TaoToken 的 API 入口是https://taotoken.net/api,这个地址后面会用在application.yml里。第三步,确认你要用的模型 ID。不同模型在工具调用能力上差异很大,MCP 场景建议选支持 function calling 的模型,否则@Tool注册了也不会被调用。
这里有个容易踩的坑:Spring AI 的 OpenAI 兼容层默认会拼接/v1/chat/completions。如果你填的 Base URL 末尾多了斜杠或者少了路径,请求就会 404。TaoToken 的 API 地址是https://taotoken.net/api,Spring AI 会自动补全后续路径,所以配置里不要手动加/v1。另外,Key 建议通过环境变量注入,比如TAOTOKEN_API_KEY,这样本地联调和 CI 环境可以用同一套配置。
如果你还没决定用哪个模型,可以先到模型对话页面试一下工具调用效果。地址是https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite。在对话里发一个需要调用工具的问题,看模型是否能正确返回 tool_calls。这一步能帮你提前排除模型不支持工具调用的情况,省得后面在 Spring AI 里反复调试。
3. 可复制的 application.yml 与 Maven 配置片段
这一节是核心。我会给出完整的 Maven 依赖和application.yml,你可以直接复制到项目里。先看依赖。Spring AI MCP 服务端有三种传输方式:STDIO、WebMVC SSE、WebFlux SSE。本地联调最常用的是 WebMVC SSE,因为它自带 HTTP 端点,方便用 curl 或 Postman 验证。Maven 配置如下:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency>第一个依赖提供 MCP 服务端自动配置和 SSE 传输层,第二个依赖提供 OpenAI 兼容的模型客户端。两个都加上,才能同时完成工具暴露和模型调用。版本方面,Spring AI 1.0.0 及以上都支持,建议用最新的稳定版。
接下来是application.yml。这份配置同时覆盖了 MCP 服务端和模型通道两部分:
server: port: 8080 spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 mcp: server: name: spring-ai-mcp-server version: 1.0.0 type: SYNC instructions: "This server provides weather and time tools" sse-message-endpoint: /mcp/messages capabilities: tool: true resource: true prompt: true completion: true request-timeout: 30s几个关键点解释一下。base-url填https://taotoken.net/api,不要加/v1。api-key用环境变量注入,启动前先export TAOTOKEN_API_KEY=你的Key。model填你要用的模型 ID,比如gpt-4o-mini或claude-3-5-sonnet,具体支持列表可以在文档里查。type: SYNC表示同步模式,本地联调够用;如果你的工具方法里有阻塞操作,可以改成ASYNC。sse-message-endpoint是客户端发送消息的路径,默认是/mcp/messages,保持默认即可。
如果你用的是 STDIO 传输,配置会不一样。STDIO 模式不需要server.port,也不需要 SSE 端点,但需要把spring.ai.mcp.server.stdio设为true。不过 STDIO 模式下模型调用仍然走spring.ai.openai那一段,所以 Base URL 和 Key 的配置是一样的。本地联调建议先用 WebMVC SSE,因为可以直接用 HTTP 请求验证,不用挂客户端。
还有一个细节:request-timeout默认是 20 秒,我改成了 30 秒。因为工具调用加上模型推理,有时候会超过 20 秒,尤其是模型在决定是否调用工具时。如果你发现请求偶尔超时,可以适当调大这个值。但也不要设太大,否则客户端会一直等。
配置写完后,启动类里需要注册ToolCallbackProvider。参考代码如下:
@SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } @Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } }WeatherService里用@Tool注解标记方法。这样 MCP 服务端启动时,会自动扫描这些工具并注册到 SSE 端点。模型侧通过spring.ai.openai的配置访问 TaoToken 通道,工具调用请求会带着工具定义一起发给模型。
4. 启动服务端并完成一次完整调用验证
配置就绪后,启动服务端。在项目根目录执行:
export TAOTOKEN_API_KEY=你的Key ./mvnw spring-boot:run启动日志里会看到McpWebMvcServerAutoConfiguration和McpServerAutoConfiguration被激活,SSE 端点注册在/sse,消息端点在/mcp/messages。如果看到Tomcat started on port 8080,说明服务端起来了。
接下来验证模型通道。先确认服务端能访问 TaoToken。用一个简单的 curl 测试:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "你好"}] }'如果返回正常的choices结构,说明 Key 和通道没问题。如果返回 401,检查 Key 是否复制完整;如果返回 404,检查 Base URL 是否多了/v1。
然后验证 MCP 服务端的工具调用。先连上 SSE 端点:
curl -N http://localhost:8080/sse这个命令会保持连接,你会看到类似event: endpoint和data: /mcp/messages?sessionId=xxx的输出。记下sessionId,然后另开一个终端,发送一个工具调用请求:
curl -X POST "http://localhost:8080/mcp/messages?sessionId=你的sessionId" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "getWeather", "arguments": {"cityName": "北京"} } }'如果工具方法正确注册,你会收到工具执行结果。但这一步只验证了工具层,还没验证模型层。要验证模型是否真的能通过 TaoToken 调用工具,需要在 Spring AI 里发一个带工具的对话请求。最简单的方式是写一个CommandLineRunner,在启动后自动发一条消息:
@Bean public CommandLineRunner testModelCall(ChatClient.Builder builder) { return args -> { String response = builder.build() .prompt("北京今天天气怎么样?") .tools(new WeatherService()) .call() .content(); System.out.println("模型返回: " + response); }; }启动后观察控制台。如果模型返回了天气信息,说明整条链路通了:Spring AI 把工具定义发给 TaoToken 通道,模型决定调用getWeather,工具执行后结果回传给模型,模型生成最终回答。如果模型没有调用工具,检查@Tool的description是否足够清晰,以及模型是否支持 function calling。
实测下来,最容易出问题的是模型 ID 和工具描述。有些模型对工具描述很敏感,描述太模糊就不会调用。另外,temperature设太高也会影响工具调用的稳定性,联调阶段建议设成 0.2 到 0.7 之间。
5. 常见报错排查:401、local proxy failed、reading choices
联调过程中会遇到几类典型报错,这里逐一拆解。
第一类:401 Unauthorized。这个最直接,Key 不对或者没传。检查TAOTOKEN_API_KEY环境变量是否生效,可以在启动日志里搜索api-key确认。如果用的是 IDE 启动,注意 IDE 的环境变量配置可能和终端不一样。另外,Key 如果包含特殊字符,YAML 里要用引号包起来。还有一种情况是 Key 被禁用或额度用完,去控制台确认一下状态。
第二类:local proxy failed或连接超时。这个报错通常出现在服务端无法访问taotoken.net的时候。先确认本机网络能正常访问外网,然后检查base-url是否写错。注意不要配置任何本地代理相关的环境变量,Spring AI 的 HTTP 客户端会读取系统代理设置,如果代理配置有问题,请求会直接失败。可以临时取消HTTP_PROXY和HTTPS_PROXY环境变量再试。
第三类:reading choices解析失败。这个报错说明请求发出去了,但返回的 JSON 结构不符合 OpenAI 格式。常见原因有两个:一是 Base URL 路径不对,比如填了https://taotoken.net/api/v1,Spring AI 又拼了一次/v1,导致请求打到了错误的路由;二是模型 ID 不存在,服务端返回了错误信息而不是正常的choices数组。检查base-url只保留https://taotoken.net/api,模型 ID 从文档里复制,不要手写。
第四类:OAuth 或鉴权头冲突。如果你在项目里同时引入了其他模型 SDK,可能会有多个Authorization头。Spring AI 的 OpenAI 客户端默认用Bearer方式,如果和其他客户端的配置冲突,请求会被拒绝。检查application.yml里是否有多余的spring.ai配置,只保留一份。
第五类:工具注册了但模型不调用。这个不是报错,但很常见。先确认模型支持 function calling,然后在@Tool的description里写清楚“什么时候用这个工具”。比如“Get weather information by city name”就比“weather”好很多。另外,@ToolParam的description也要写,模型需要知道参数格式。
排查时建议打开 Spring AI 的 debug 日志:
logging: level: org.springframework.ai: DEBUG这样能看到完整的请求和响应体,定位问题会快很多。如果日志里看到请求发出去了但响应是空的,大概率是模型侧的问题,换个模型 ID 试试。
6. 统一通道下的后续扩展与接入入口
链路跑通之后,你可以在这个基础上做几件事。第一,把模型 ID 抽成配置项,通过 Spring Profile 切换不同模型,比如application-dev.yml用轻量模型,application-prod.yml用能力更强的模型。第二,把工具方法按业务域拆分,每个域一个ToolCallbackProvider,这样 MCP 服务端的工具列表会更清晰。第三,如果你要长期跑编码类 Agent,可以考虑用 Coding Plan,地址是https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite,它针对长时间编码场景做了通道优化。
接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有各语言 SDK 的配置示例和模型列表。如果你在配置过程中遇到 Key 或通道问题,先去 API Keys 页面确认 Key 状态,地址是https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。控制台入口是https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite,可以查看调用量和余额。
最后提醒一点:MCP 服务端的工具方法不要直接连生产数据库。本地联调阶段用 mock 数据或者测试库,等链路稳定后再考虑接入真实数据源。工具方法的返回值尽量保持结构简单,复杂的嵌套对象会增加模型解析的负担,也容易导致reading choices类错误。配置一次跑通之后,后面换模型、加工具都只是改配置和加注解的事,不用再动通道层。