1. 从一个最小 MCP 实例说起:Spring AI 客户端怎么把工具调用跑通
Spring AI 接入 MCP 这件事,说复杂也复杂,说简单也简单。复杂在于 MCP 协议本身有 Host、Client、Server 三层角色,还有 Tools、Resources、Prompts、Sampling 这些能力原语;简单在于,如果你只是想先确认「链路能不能通」,其实一个 MCP Client + 一个远程模型通道就够了。这篇要做的,就是后者:用 Spring AI 写一个最小可运行的 MCP 客户端骨架,通过 TaoToken 的统一 Key 和 API 通道完成一次真实的工具调用,然后看启动日志和返回结果,确认整条链路是活的。
适合谁看?适合已经在本地跑过 Spring Boot、想快速验证 Spring AI + MCP 是否可用的开发者;也适合手上有一堆模型 Key、想收敛成一个统一入口再接入 MCP 的人。本文不铺开讲 MCP 协议全貌,只聚焦一件事:application.yml 怎么写、MCP 客户端怎么配、工具调用怎么触发、日志和返回值怎么验证。环境基线按 Spring AI 1.0.x 系列来,JDK 17+,Maven 3.8+,Spring Boot 3.4.x 或 3.5.x 都可以。
我试过把 MCP 客户端和模型通道分开配,结果 Key 散落在好几个地方,调试时特别容易搞混。后来改成 TaoToken 统一 Key,客户端只认一个 base-url 和一个 api-key,MCP 工具调用走同一套通道,排查问题时链路清晰很多。下面按这个思路来。
2. 前置准备:TaoToken 统一 Key 与依赖骨架
TaoToken 在这里的角色是「统一模型通道」:你不需要在 Spring AI 里分别配 Anthropic、OpenAI 的 Key,而是拿一个 TaoToken 的 API Key,把 base-url 指向它的 API 地址,模型名按需选。这样 MCP 客户端在触发工具调用时,模型请求和工具回调都走同一条出口,日志里能一眼看到是哪次调用。
先拿 Key。打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后在控制台创建 API Key。控制台入口在这里:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。Key 创建后只显示一次,复制到本地环境变量里,别硬编码进代码。
API 地址是 https://taotoken.net/api ,注意这个地址不带 UTM 参数,配置里直接写它。模型名按你实际要用的填,比如 claude 系列或 gpt 系列,具体以控制台模型列表为准。
依赖方面,MCP 客户端和模型 starter 各一个。pom.xml 里加:
<dependencies> <!-- MCP 客户端 starter --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> <!-- 模型通道:用 OpenAI 兼容协议接 TaoToken --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>这里选 OpenAI starter 是因为 TaoToken 的 API 走 OpenAI 兼容格式,base-url 一改就能用,不用额外写适配层。如果你更习惯 Anthropic 的调用风格,也可以换成 anthropic starter,但 base-url 和 Key 的配法思路一样。
3. 可复制配置:application.yml 与 MCP 客户端骨架
配置文件是这篇的核心。MCP 客户端要连一个 Server,模型通道要连 TaoToken,两者在同一个 yml 里配清楚。
server: port: 8081 spring: main: web-application-type: none # CLI 应用,不需要 Web 容器 ai: openai: # TaoToken 统一通道 base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-3-5-sonnet # 按控制台实际模型名替换 temperature: 0.2 mcp: client: # 用 streamable-http 连远程 MCP Server streamable-http: connections: demo-server: url: http://localhost:8080 request-timeout: 30s headers: X-Client-Version: 1.0几个点解释一下。web-application-type: none是因为这个最小实例用命令行交互,不需要 Tomcat。base-url指向 TaoToken 的 API 地址,api-key从环境变量读,避免泄露。MCP 客户端这边,streamable-http是当前推荐的远程传输方式,比 SSE 更省事;connections下面给 Server 起个名字demo-server,后面注入工具时会用到这个名字。
如果你本地还没有 MCP Server,可以先用一个最简单的 stdio 版本验证,把streamable-http换成stdio:
spring: ai: mcp: client: stdio: connections: local-tools: command: java args: - "-jar" - "/opt/mcp/demo-server.jar"stdio 适合本地进程,streamable-http 适合已经跑起来的服务。两种配法在客户端代码层面几乎无差别,Spring AI 会自动把工具注册进ToolCallbackProvider。
4. 客户端代码与验证:一次工具调用的完整过程
主类里注入ChatClient和ToolCallbackProvider,后者会自动收集所有 MCP Server 暴露的工具。
@SpringBootApplication public class McpClientApplication { public static void main(String[] args) { SpringApplication.run(McpClientApplication.class, args).close(); } @Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder.build(); } @Bean public CommandLineRunner run(ChatClient chatClient, ToolCallbackProvider mcpTools) { return args -> { var tools = mcpTools.getToolCallbacks(); System.out.println("已注册 MCP 工具数量: " + tools.length); for (var t : tools) { System.out.println(" - " + t.getToolDefinition().name()); } String question = "帮我查一下当前可用的工具里,有没有能返回时间的,并调用它"; String answer = chatClient.prompt(question) .toolCallbacks(mcpTools) .call() .content(); System.out.println("模型回复: " + answer); }; } }启动前把 Key 塞进环境变量:
export TAOTOKEN_API_KEY="sk-你的TaoTokenKey" mvn spring-boot:run启动日志里你会先看到工具注册信息,类似:
已注册 MCP 工具数量: 2 - getCurrentTime - echo然后模型收到问题,判断需要调用getCurrentTime,触发一次工具调用,MCP Server 执行后把结果回传,模型再组织成自然语言。最终输出类似:
模型回复: 当前时间是 2025-01-15T10:32:18,来自 MCP 工具 getCurrentTime 的返回。看到这段就说明链路通了:Spring AI 客户端 → TaoToken 模型通道 → 模型决策 → MCP 工具调用 → 结果回传 → 模型总结。整个过程里,模型请求和工具回调都走 TaoToken 的统一出口,日志里能对上号。
如果你想更直观地验证模型本身,可以打开模型对话页面手动问一句:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite ,确认 Key 和模型名没问题,再回到代码里跑 MCP 调用,能省掉一半排查时间。
5. 本篇常见错排查
报 401 或 invalid api key:九成是环境变量没生效。echo $TAOTOKEN_API_KEY确认一下,或者直接在 yml 里临时写死测试(测完记得改回去)。另外注意 base-url 结尾不要多加/v1,TaoToken 的地址就是https://taotoken.net/api。
工具数量为 0:说明 MCP 客户端没连上 Server。先确认 Server 是否在http://localhost:8080监听,再检查 yml 里connections的名字和传输方式是否匹配。stdio 模式下如果 jar 路径不对,启动时不会报错,但工具列表是空的,这点特别隐蔽。
模型不调用工具,直接瞎答:通常是工具描述太模糊,或者模型没拿到工具列表。确认.toolCallbacks(mcpTools)这行加上了;如果加了还不调,把问题问得更明确一点,比如「调用 getCurrentTime 工具告诉我现在几点」,强制模型走工具路径。
超时:request-timeout: 30s对大多数工具够用,但如果 Server 端有慢查询,调大到 60s。stdio 模式下没有这个配置项,超时由进程本身控制。
模型名报错:TaoToken 控制台的模型名和 OpenAI 官方命名可能不完全一样,以控制台列表为准。填错会返回 model not found,换一个再试。
6. 下一步:把这条链路用起来
链路通了之后,接下来就是替换和扩展。把demo-server换成你自己的 MCP Server,工具描述写清楚,模型就能自动决策调用。如果你要长期跑编码类任务或者 Agent 工作流,建议单独配一个 Coding Plan,把模型通道和工具调用分开管理,调试时互不干扰:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。接入文档在这里,遇到 starter 版本或配置项对不上时可以查:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
Key 管理统一在 API Keys 页面,多环境多项目时建议一个项目一个 Key,方便按调用量排查:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。如果你用的是 Claude Code 这类工具链,Anthropic 兼容通道的配法可以参考:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite 。
最后留一个实用习惯:每次改完 yml,先跑一次启动日志看工具数量,再看模型回复里有没有工具调用痕迹。这两步能覆盖八成配置问题,比直接读源码快得多。