1. 本地 MCP Server 跑通后,模型调用通道为什么必须统一
你大概率已经经历过这个阶段:本地用uvx mcp-server-fetch或者npx @playwright/mcp@latest把 MCP Server 拉起来了,Cursor 或 Cline 里也能看到工具列表变绿,但一旦把同样的逻辑搬进 Spring AI 项目,问题就来了——MCP 工具能注册、能发现,可模型侧还在用某个默认端点,请求发出去要么超时,要么返回一堆看不懂的reading choices报错。
这不是 MCP 协议本身的问题。MCP 解决的是“工具怎么描述、怎么发现、怎么调用”,它管的是客户端和 Server 之间的 JSON-RPC 通信。但模型推理这一层,走的是另一条链路:你的 Spring AI 应用需要把用户意图发给一个大模型服务,拿到模型返回的 tool_call 决策,再去触发 MCP 工具。这两条链路是分开的。
我见过太多项目卡在这里:MCP Server 配置写得漂漂亮亮,mcp-servers-config.json里工具一个不少,结果ChatClient一调用就 401,或者日志里冒出local proxy failed。根因往往不是 MCP 配置错了,而是模型调用的 Base URL 和 API Key 没有统一到一个稳定通道上。
这篇要解决的就是这个环节:当你的 MCP Server 已经在本地跑通,Spring AI 项目也引入了spring-ai-starter-mcp-client,接下来怎么把application.yml里的模型端点改到 TaoToken,让 MCP 工具调用和模型推理走同一条可控链路,并且用一次真实的工具调用请求验证整条路是通的。
适合谁看:已经在 Spring AI 里写过ChatClient、配过 DashScope 或 OpenAI starter、本地 MCP Server 能启动但还没跟模型侧打通的人。如果你连 MCP Server 都还没跑起来,建议先把 STDIO 方式的 fetch 服务在命令行里跑通再回来。
核心检索词先摆出来:MCP 协议负责工具连接标准化,Spring AI 负责把 MCP 工具挂到对话流程,而 Base URL 决定模型推理请求发到哪里。三者缺一,链路就是断的。
2. TaoToken 前置:Base URL、API Key 与模型 ID 三件套怎么备齐
在改配置之前,先把三样东西拿到手,不然后面每一步都会卡。
第一件是 Base URL。TaoToken 的 API 入口是https://taotoken.net/api,注意这里不加任何查询参数,就是干净的 API 根路径。Spring AI 的 OpenAI 兼容 starter 会在这个根路径后面拼接/v1/chat/completions之类的具体端点,所以你在配置里填的应该是根路径,不要自己补/v1。
第二件是 API Key。登录 TaoToken 控制台,在 API Keys 页面创建一个新的 Key。创建时建议给它起一个能区分用途的名字,比如spring-ai-mcp-dev,这样后面如果要在多个项目里用不同的 Key,排查问题时能一眼看出是哪个。Key 只在创建时完整显示一次,复制下来存到安全的地方。
第三件是 Model ID。这个容易被忽略。MCP 工具调用对模型的 tool_call 能力有要求,不是所有模型都能稳定输出结构化的工具调用决策。在 TaoToken 的模型对话页面可以先试一下你打算用的模型,确认它能正常返回 tool_calls 字段。常见的做法是选一个明确支持 function calling 的模型,把它的 ID 记下来,比如claude-sonnet-4-20250514这类。
三件套备齐后,建议先在命令行里用 curl 验证一次模型端点是否可达,避免把配置问题和服务问题混在一起:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 你的API Key" \ -d '{ "model": "你的Model ID", "messages": [{"role": "user", "content": "回复ok"}] }'如果返回里能看到choices数组和正常的 content,说明模型通道是通的。这一步过了,再进 Spring AI 配置,出问题时就能确定是配置层而不是网络层。
这里插一句踩过的坑:有些人会把 Base URL 填成带/v1的完整路径,结果 Spring AI 又拼了一次/v1,变成/v1/v1/chat/completions,直接 404。记住根路径就是https://taotoken.net/api,后面的路径交给 starter 自己拼。
另外,如果你用的是 Claude Code 这类工具做辅助开发,它的接入配置和 Spring AI 是两套东西,不要混用。Claude Code 的配置走的是它自己的 settings 文件,Spring AI 走的是application.yml。两者可以指向同一个 TaoToken 通道,但配置文件各写各的。
3. application.yml 可复制配置:Base URL 与 API Key 的改法
现在进入正题。假设你的 Spring AI 项目已经引入了 MCP Client starter,依赖大致是这样:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency>注意这里用的是 OpenAI 兼容 starter,因为 TaoToken 提供的是 OpenAI 兼容接口。如果你之前用的是 DashScope starter,需要换成 OpenAI 兼容的那套,否则 Base URL 的配置项名称对不上。
接下来是application.yml的核心改动。先看模型侧:
spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-sonnet-4-20250514 temperature: 0.7三个关键点。base-url填 TaoToken 的 API 根路径,不带/v1。api-key用环境变量引用,不要把 Key 硬编码进配置文件,尤其是这个文件要提交到 Git 的时候。model填你在模型对话页面验证过支持 tool_call 的那个 ID。
然后是 MCP 客户端侧,保持你本地已经跑通的 STDIO 配置不变:
spring: ai: mcp: client: request-timeout: 60000 stdio: servers-configuration: classpath:/mcp/mcp-servers-config.jsonmcp-servers-config.json放在src/main/resources/mcp/目录下,内容还是你本地验证过的那份:
{ "mcpServers": { "fetch": { "command": "uvx", "args": ["mcp-server-fetch"] } } }Windows 环境下如果uvx识别不了,改成uvx.exe或者用完整路径。这个坑在本地命令行里可能不出现,但 Spring AI 启动子进程时环境变量继承不一样,容易翻车。
把模型侧和 MCP 侧拼在一起,完整的application.yml长这样:
server: port: 8080 spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-sonnet-4-20250514 temperature: 0.7 mcp: client: request-timeout: 60000 stdio: servers-configuration: classpath:/mcp/mcp-servers-config.json环境变量在启动时注入:
export TAOTOKEN_API_KEY=你的API KeyIDEA 里跑的话,在 Run Configuration 的 Environment variables 里加一行就行。
配置写完后,Controller 层要把 MCP 工具挂到 ChatClient 上,这一步决定了模型能不能“看到”工具:
@RestController @RequestMapping("/chat") public class ChatController { private final ChatClient chatClient; public ChatController(OpenAiChatModel chatModel, ToolCallbackProvider toolCallbackProvider) { this.chatClient = ChatClient.builder(chatModel) .defaultToolCallbacks(toolCallbackProvider) .build(); } @GetMapping("/call") public String call(@RequestParam String prompt) { return chatClient.prompt() .user(prompt) .call() .content(); } }注意构造器注入的是OpenAiChatModel,不是 DashScope 的那个。ToolCallbackProvider由 Spring AI 的 MCP Client starter 自动装配,它会把你mcp-servers-config.json里所有 Server 暴露的工具收集起来。
到这里配置就完成了。启动项目,日志里应该能看到 MCP Server 启动、工具列表加载、以及模型端点的初始化信息。如果工具列表是空的,说明 MCP 配置没生效;如果模型端点初始化报错,说明 Base URL 或 Key 有问题。两类问题分开看,不要混在一起猜。
4. 验证一次 MCP 工具调用请求,确认链路走通
配置写完不算完,得用一次真实的工具调用把整条链路跑通。这里用 fetch 这个 MCP Server 做验证,因为它不需要额外的 API Key,行为也容易预期。
启动 Spring Boot 项目,观察启动日志。正常的话会看到类似这样的输出:
Registered tools: [fetch] MCP client initialized with 1 server(s)Registered tools这一行很关键,它说明 MCP Server 暴露的工具已经被 Spring AI 收集到了。如果这里是空的,先回去检查mcp-servers-config.json的路径和uvx命令能不能在项目运行环境里执行。
然后发一个会触发工具调用的请求:
curl "http://localhost:8080/chat/call?prompt=请抓取 https://example.com 的内容并总结成一句话"这个 prompt 的设计有讲究。它明确要求“抓取网页内容”,模型会判断需要调用 fetch 工具,而不是直接凭训练数据回答。如果模型只是泛泛地回一句“我无法访问网页”,说明工具没挂上,或者模型没识别出该调用工具。
正常走通的话,你会看到两段日志。第一段是模型返回 tool_call 决策:
Tool call requested: fetch(url=https://example.com)第二段是 MCP Server 执行工具后返回结果,模型再基于结果生成最终回答:
Tool call result received, generating final response...最终 curl 返回的内容应该是一句对 example.com 页面的总结,而不是模型编造的答案。这一步过了,说明模型推理走 TaoToken 通道、工具调用走 MCP 通道、两者在 Spring AI 里成功汇合。
如果你想看得更细,可以在application.yml里把日志级别调一下:
logging: level: org.springframework.ai: DEBUG io.modelcontextprotocol: DEBUG这样能看到 JSON-RPC 消息的收发细节,包括工具调用的参数和返回值。排查问题时这层日志很有用,但生产环境记得调回去,不然日志量会很大。
验证通过后,你可以把 prompt 换成更复杂的组合任务,比如“抓取某个页面,提取其中的链接列表,然后总结这些链接的主题”。这会触发多次工具调用,能进一步确认链路在连续调用下也稳定。
有一点要注意:MCP 工具调用是有超时的。request-timeout: 60000是 60 秒,如果某个工具执行时间超过这个值,会直接抛超时异常。fetch 抓取普通网页通常够用,但如果抓的是大文件或者慢站点,可能需要调大。这个值不要设得太离谱,否则出问题时你要等很久才能看到报错。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
配置和验证过程中,有几个报错出现的频率特别高,这里逐个对照。
401 Unauthorized。这个最直接,API Key 不对或者没传进去。先确认环境变量TAOTOKEN_API_KEY在当前运行环境里真的存在,IDEA 里跑和命令行里跑的环境变量是两套。然后确认 Key 没有多余的空格或换行,复制的时候容易带上。最后确认base-url和 Key 是配套的,别拿 A 通道的 Key 去请求 B 通道的地址。
local proxy failed。这个报错通常出现在模型端点不可达的时候。Spring AI 尝试连接base-url指定的地址,但连接被拒绝或超时。检查base-url是不是写成了https://taotoken.net/api/带尾斜杠,有些 starter 对尾斜杠敏感。另外确认你的网络环境能正常访问这个地址,用前面那条 curl 命令再测一次。
reading choices 相关报错。典型形态是Cannot read field "choices" because response is null或者reading choices时抛 NPE。这说明模型端点返回了非预期结构,Spring AI 解析响应时拿不到choices字段。常见原因是 Base URL 拼错了路径,请求打到了错误的端点,返回了一个 HTML 错误页或者空响应。回去检查base-url是不是干净的根路径,以及模型 ID 是不是有效。
OAuth 相关报错。如果你在配置里误加了 OAuth 相关的参数,或者用了某个需要 OAuth 流程的 starter,会看到 token 获取失败的报错。TaoToken 的 API Key 方式是 Bearer Token,不需要走 OAuth 授权流程。检查application.yml里有没有多余的oauth2配置项,有的话删掉。
工具列表为空。这个不算报错,但比报错更隐蔽。启动日志里Registered tools是空的,模型自然也不会调用任何工具。检查mcp-servers-config.json是否在 classpath 下、servers-configuration路径是否写对、uvx或npx命令在当前环境能否执行。Windows 下命令扩展名的问题在这里特别常见。
工具调用超时。日志里看到Request timeout或者工具执行到一半中断。调大request-timeout,或者检查 MCP Server 本身是不是卡住了。有些 MCP Server 首次启动会下载依赖,第一次调用特别慢,第二次就正常了。
排查的时候有个原则:先确认模型通道通不通(用 curl 测),再确认 MCP 通道通不通(看工具列表),最后才看两者汇合后的行为。把问题范围缩小,比对着日志瞎猜快得多。
如果你在配置过程中需要对照更完整的接入说明,可以看 TaoToken 的接入文档,里面有针对不同框架的配置示例。模型侧的行为验证可以在模型对话页面直接试,不用每次都重启 Spring Boot 项目。
6. 把 MCP 工具调用固定到统一通道之后
链路跑通之后,你会发现之前那些零散的配置问题其实都指向同一件事:模型推理和工具调用是两条独立的链路,各自需要自己的端点配置。MCP 协议把工具侧标准化了,但模型侧的 Base URL 和 Key 还是得你自己管。
把 Base URL 统一到 TaoToken 之后,最直接的好处是模型调用通道变得可控。你可以在一个地方管理 Key,在一个地方看调用情况,不用在多个供应商的配置之间来回切换。对于已经在用 MCP 做工具集成的项目来说,这意味着新增一个 MCP Server 时,只需要改mcp-servers-config.json,模型侧完全不用动。
长期做编码和 Agent 类项目的话,可以考虑用 Coding Plan 把模型调用额度固定下来,避免每次调试都担心用量。如果只是偶尔验证模型行为,模型对话页面就够用了。
配置这件事,跑通一次之后就是复制粘贴。真正花时间的是第一次把每个报错都踩一遍。希望这篇能帮你少踩几个。