1. 为什么本地工具需要一条统一 Key 通道
如果你手上有几个用 Spring Boot 写的内部工具,比如查数据库元信息、跑一段业务校验、读本地文件目录,这些能力本身不复杂,但每次想让模型调用它们,就得单独配一套模型客户端、单独管一个 Key、单独处理超时和重试。工具越多,Key 越乱,换模型的时候每个工具都要改一遍配置。
MCP(Model Context Protocol)解决的正是「工具怎么被模型发现和调用」这件事。Spring AI 提供了spring-ai-starter-mcp-server-webmvc,让你用几个 Bean 就能把本地 Java 方法注册成 MCP 工具、资源和提示词。但 MCP Server 只负责「暴露工具」,真正去调模型的那一步,仍然需要一个稳定的模型入口。
这就是把本地工具接入 TaoToken 统一 Key 通道的价值:MCP Server 负责工具注册与协议转发,TaoToken 负责模型调用的统一入口。你只需要维护一个 Base URL、一个 API Key、一个 Model ID,所有工具走同一条通道。对 Java 开发者来说,这意味着不用在每个工具里重复写模型配置,换模型时改一处即可。
这篇文章面向已经有一些本地工具、希望统一模型调用入口的 Java 开发者。我会从零搭一个 Spring AI MCP Server,把工具、资源、提示词都注册进去,然后接上 TaoToken 的 API 通道,最后做一次端到端调用验证,确认工具注册和请求转发都正常。整个过程你可以直接跟着敲。
需要先明确一点:MCP Server 本身不绑定任何模型厂商,它是一个协议层。你把它接到 TaoToken 的 API 上,本质是让 MCP 的工具调用请求通过统一通道转发出去。所以配置的重点有两个,一是 MCP Server 自身的工具注册,二是模型客户端的 Base URL 和 Key 指向 TaoToken。
2. 用 spring-ai-starter-mcp-server-webmvc 搭起 MCP Server
2.1 依赖与项目结构
先建一个标准的 Spring Boot 3.x 项目,JDK 17 以上。核心依赖是spring-ai-starter-mcp-server-webmvc,它基于 WebMVC 提供 MCP 的传输层。pom.xml 里加上:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency>如果你用的是 Spring AI 的 BOM 管理版本,记得把 BOM 也引入,避免版本对不齐。项目结构建议按职责拆开:config放 MCP 注册相关的 Bean,service放真正的工具方法,controller一般不需要,因为 MCP 的端点由 starter 自动暴露。
2.2 注册 tools:用 @Tool 注解暴露本地方法
工具注册是 MCP Server 最常用的能力。Spring AI 的做法是:把带有@Tool注解的方法所在的对象,通过MethodToolCallbackProvider包装成ToolCallbackProvider,再交给 Spring 托管。starter 里的McpServerAutoConfiguration会自动把这些ToolCallback转成SyncToolSpecification注册到 server 上。
先写一个工具服务类:
@Service public class DemoService { @Tool(description = "根据参数返回拼接后的问候语") public String query(String param1) { return "hello " + param1; } @Tool(description = "查询本地表结构元信息") public String tableMeta(String tableName) { return "table=" + tableName + ", columns=[id,name,created_at]"; } }然后在配置类里把它注册成ToolCallbackProvider:
@Configuration public class McpToolConfig { @Bean public ToolCallbackProvider dbTools(DemoService demoService) { return MethodToolCallbackProvider.builder() .toolObjects(demoService) .build(); } }这里有个容易踩的点:MethodToolCallbackProvider.builder().toolObjects(...)传入的对象里,所有带@Tool的方法都会被扫描。如果你一个类里既有想暴露的方法,又有不想暴露的内部方法,要么拆类,要么用@Tool精确控制。实测下来,拆成独立的工具类最省心。
2.3 注册 resources:以 List 形式注入才能被识别
资源(resources)用来暴露只读数据,比如数据库表结构、配置文件内容。Spring AI 要求以List<McpServerFeatures.SyncResourceSpecification>的形式注入,否则自动配置扫描不到。
@Configuration public class McpResourceConfig { @Bean public List<McpServerFeatures.SyncResourceSpecification> resourceSpecList() { McpSchema.Resource resource = new McpSchema.Resource( "mysql://table1/meta", "table1", "meta data", "text/plain", null ); McpServerFeatures.SyncResourceSpecification spec = new McpServerFeatures.SyncResourceSpecification(resource, (exchange, request) -> { McpSchema.ResourceContents contents = new McpSchema.TextResourceContents( "meta1", "text/plain", "meta" ); return new McpSchema.ReadResourceResult(List.of(contents)); }); return List.of(spec); } }注意resources/list和resources/read是配套的:SyncResourceSpecification里的 lambda 就是 read 的实现,而 list 由注册的 resource 列表自动生成。你不需要单独写 list 方法。
2.4 注册 prompts:同样是 List 注入
提示词(prompts)用于预置一些可复用的对话模板。注册方式和 resources 类似,必须是 List 形式:
@Configuration public class McpPromptConfig { @Bean public List<McpServerFeatures.SyncPromptSpecification> syncPromptSpecList() { McpSchema.Prompt prompt = new McpSchema.Prompt( "greeting", "description", List.of(new McpSchema.PromptArgument("name", "description", true)) ); McpServerFeatures.SyncPromptSpecification spec = new McpServerFeatures.SyncPromptSpecification(prompt, (exchange, request) -> { return new McpSchema.GetPromptResult("description", List.of()); }); return List.of(spec); } }到这里,tools、resources、prompts 三类能力都注册完了。starter 的McpServerAutoConfiguration会在启动时把这些 List 收集起来,塞进McpSyncServer的serverBuilder。启动日志里会打印Registered tools: N,你可以用它确认注册数量是否符合预期。
3. 把模型调用指向 TaoToken 统一 Key 通道
3.1 为什么要在 MCP Server 之外配模型客户端
MCP Server 负责「工具被调用」,但工具执行过程中如果需要模型能力,比如让模型决定调哪个工具、或者对工具结果做二次加工,就需要一个模型客户端。Spring AI 的ChatClient或ChatModel就是干这个的。把它的 Base URL 指向 TaoToken,Key 用 TaoToken 的 Key,Model ID 用你选的模型,这样所有工具共享同一条模型通道。
3.2 application.yml 配置片段
下面是一份可直接复制的配置。注意 Base URL 用https://taotoken.net/api,不要带多余路径:
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: local-tool-server version: 1.0.0 type: SYNC这里spring.ai.mcp.server.type: SYNC对应自动配置里的havingValue = "SYNC",也是默认值。name和version会作为McpSchema.Implementation的 serverInfo 上报给客户端。
3.3 用环境变量管理 Key
不要把 Key 硬编码进 yml。用环境变量:
export TAOTOKEN_API_KEY="你的Key"然后在 IDE 或启动脚本里注入。如果你用 Docker,就在docker run -e TAOTOKEN_API_KEY=...里传。这样换 Key 不用改代码,也避免误提交。
3.4 三件套对照表
接入任何模型通道,核心就是三件套:Base URL、Key、Model ID。对照如下:
| 配置项 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 统一 API 入口,不加 UTM |
| API Key | 你的 TaoToken Key | 通过环境变量注入 |
| Model ID | 如 gpt-4o-mini | 按需替换 |
如果你用的是 Claude Code 或 Cline 这类工具,配置逻辑一样,只是字段名不同。关键是 Base URL 和 Key 指向同一通道,Model ID 保持一致。
4. 端到端验证:确认工具注册与请求转发
4.1 启动并检查注册日志
启动 Spring Boot 应用,观察日志。你应该能看到类似:
Registered tools: 2, notification: false Registered resources: 1, notification: false Registered prompts: 1, notification: false如果 tools 数量是 0,说明@Tool方法没被扫描到,检查MethodToolCallbackProvider是否注入了正确的对象。如果 resources 或 prompts 是 0,检查是不是用了 List 形式注入。
4.2 用 MCP 客户端发起 tools/list
MCP Server 默认通过 WebMVC 暴露端点。你可以用任意 MCP 客户端连接,也可以用 curl 模拟一次 JSON-RPC 请求。先确认端点路径,通常在/mcp或/sse,具体看 starter 版本。发一个tools/list:
curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }'预期返回里包含你注册的query和tableMeta两个工具,每个都有 name、description、inputSchema。
4.3 发起一次 tool/call
接着调tools/call,验证工具真的能执行:
curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "query", "arguments": {"param1": "world"} } }'预期返回hello world。如果返回错误,先看是不是参数名对不上,@Tool方法的参数名就是 inputSchema 里的字段名。
4.4 验证模型通道
最后验证模型调用走的是 TaoToken。在工具方法里注入ChatClient,发一句测试:
@Service public class DemoService { private final ChatClient chatClient; public DemoService(ChatClient.Builder builder) { this.chatClient = builder.build(); } @Tool(description = "让模型回答一个问题") public String ask(String question) { return chatClient.prompt(question).call().content(); } }调用ask工具,如果返回模型生成的文本,说明 Base URL 和 Key 配置生效,请求已经通过 TaoToken 通道转发。如果报 401,检查 Key 是否正确注入;如果报连接错误,检查 Base URL 是否写成了带路径的形式。
5. 常见报错排查:401、local proxy failed、reading choices
5.1 401 Unauthorized
最常见的原因是 Key 没注入成功。先确认环境变量:
echo $TAOTOKEN_API_KEY如果为空,说明启动环境没读到。IDE 里要在 Run Configuration 的 Environment variables 里加,命令行启动要在同一个 shell 里 export。另一个原因是 yml 里写了api-key: ${TAOTOKEN_API_KEY}但没配默认值,Spring 解析失败会传空字符串。
5.2 local proxy failed
这个报错通常出现在客户端侧,表示它尝试走本地代理但失败了。检查你的 HTTP 客户端有没有配置代理,或者系统环境变量里有没有HTTP_PROXY。把代理相关配置清掉,让请求直连 TaoToken 的 API 地址。注意 Base URL 必须是https://taotoken.net/api,不要加额外前缀。
5.3 reading choices 相关报错
如果日志里出现reading choices或反序列化失败,多半是返回体结构和客户端预期不一致。先确认 Model ID 写对了,有些模型名不被支持会返回错误结构。再确认请求头里的Content-Type是application/json。如果用的是流式,检查客户端是否按 SSE 解析。
5.4 OAuth 相关报错
如果你在客户端里看到 OAuth 报错,说明它尝试走 OAuth 流程而不是 API Key。检查配置里是不是误开了 OAuth 模式,把它关掉,改用 API Key 认证。Base URL、Key、Model ID 三件套配齐,一般不会触发 OAuth。
5.5 工具注册数为 0
回到启动日志,如果Registered tools: 0,按顺序排查:@Tool注解是否加在 public 方法上;MethodToolCallbackProvider是否作为 Bean 注入;toolObjects传入的对象是否是 Spring 托管的 Bean。这三点任一不满足,工具都不会被注册。
6. 把通道固定下来,后续只改 Model ID
走到这里,你已经有一个能注册工具、资源、提示词的 Spring AI MCP Server,并且模型调用走的是 TaoToken 统一通道。后续要做的,是把这套配置固定成团队规范:Base URL 和 Key 通过环境变量注入,Model ID 单独抽成一个配置项,换模型时只改这一处。
如果你要长期跑编码类或 Agent 类任务,可以考虑用 Coding Plan 把额度固定下来,避免每次临时申请。需要看具体接入参数和端点,直接翻接入文档;想先验证模型返回是否符合预期,用模型对话页面发一条测试最快;Key 的管理在 API Keys 页面。这几个入口配合起来,基本覆盖了从验证到上线的全流程。
最后留一个实用技巧:把 MCP Server 的启动日志级别调到 INFO 以上,Registered tools那行会一直帮你确认注册状态。工具数量对不上时,第一时间看这行,比翻代码快得多。