1. Spring AI MCP 工具动态更新到底解决了什么问题
如果你正在用 Spring AI 搭 MCP 智能体,大概率遇到过这个场景:工具类写好了,服务也起来了,但每次想加一个新工具,就得重启整个 Spring Boot 应用。本地联调时还好,一旦工具数量多起来,重启一次等半分钟,改一行代码验证一次,节奏全被打断。MCP 的动态工具更新能力就是冲着这个痛点来的——MCP Server 在运行期就能往工具列表里加东西或删东西,MCP Client 端能感知到变化,大模型下一轮对话就能直接调用新工具,全程不用重启。
这件事对 Java 开发者的意义在于:你可以把工具当成可插拔的模块来管理。比如天气查询工具是常驻的,数学计算工具是临时挂上去做验证的,验证完就摘掉。整个过程通过 HTTP 接口触发,配合 Cline 或 CC Switch 这类客户端刷新一下工具列表,就能看到变化。本文聚焦本地联调场景,交付可复制的配置骨架、验证命令和报错排查步骤,目标是一次跑通统一 Key 通道。
需要提前说明的是,MCP 本身是协议层的东西,它不绑定具体模型供应商。但你在本地联调时,Client 端总得连一个大模型来做对话验证。这时候如果每个工具、每个客户端都去单独配 Key,管理成本会很高。下面会讲怎么用统一 Key 通道把这件事收拢。
2. TaoToken 统一 Key 通道的前置准备
在进入 Spring AI 配置之前,先把 Key 通道这件事理清楚。你本地联调时,MCP Client 需要调用大模型来完成对话和工具调用决策,Cline 或 CC Switch 也需要模型配置。如果每个地方都填不同的 Key,排查问题时你分不清是工具没注册上还是 Key 配错了。
统一 Key 通道的思路是:所有客户端和工具链都指向同一个 API 入口,用同一套 Key。TaoToken 的 API 地址是https://taotoken.net/api,你可以在控制台创建 Key,然后在各个客户端里复用。
具体操作路径:
- 打开控制台
https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console,创建一个 API Key。 - 如果你需要查看当前 Key 的可用模型列表,去模型对话页
https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat确认。 - Key 管理在 API Keys 页面
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys。 - 接入文档参考
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc。
拿到 Key 之后,先别急着往 Spring AI 里塞。建议先用 curl 验证一下通道是否通:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的Key" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "ping"}] }'如果返回正常,说明 Key 通道没问题,接下来再配 Spring AI 的 MCP Client。这一步很关键,因为后面 MCP Client 报错时,你要能区分是通道问题还是配置问题。
3. 可复制的 Spring AI MCP 配置骨架
3.1 Maven 依赖与版本对齐
先看依赖。MCP Server 和 Client 的版本要分开管理,因为 Spring AI 的 MCP 模块在 1.0.0 和 1.0.0-M7 之间有 API 差异。下面这套是本地联调验证过的组合:
<!-- MCP Server 端 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <version>1.0.0</version> </dependency> <!-- MCP Client 端 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> <version>1.0.0-M7</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> <version>1.0.0-M7</version> </dependency>注意 Server 用 1.0.0,Client 用 1.0.0-M7。这不是随意选的,是因为 Client 端如果要用某些模型供应商的 starter,1.0.0 版本可能不兼容。版本混用是本地联调最常见的坑之一,后面排障章节会展开。
3.2 application.yml 配置骨架
Server 端配置:
spring: ai: mcp: server: enabled: true name: ai_mcp_server version: 1.0.0 type: SYNC tool-change-notification: truetool-change-notification这个参数控制工具变更后是否自动通知 Client。默认是 true,如果你在调试时发现 Client 没刷新,可以先确认这个值。
Client 端配置:
spring: ai: mcp: client: name: ai-mcp-client initialized: true type: ASYNC sse: connections: server1: url: http://localhost:8888这里type: ASYNC配合 SSE 连接,Client 启动后会自动发现 Server 暴露的工具。initialized: true表示启动时立即初始化连接。
3.3 工具注册与动态管理代码
常驻工具用@Tool注解加ToolCallbackProvider注册:
@Component public class WeatherService { @Tool(description = "获取当前天气预报") WeatherResponse getCurrentWeather(WeatherRequest request) { // 实际调用天气 API return new WeatherResponse("晴", 25); } } @Configuration public class McpToolConfig { @Bean ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } }动态管理的工具单独放一个 Service,不通过 Bean 自动注册:
@Service public class MathService { @Tool(name = "sum", description = "计算2个数的和") public int sum(int a, int b) { return a + b; } @Tool(name = "sub", description = "计算2个数的差值") public int sub(int a, int b) { return a - b; } }动态添加和删除通过 Controller 触发:
@RestController @RequestMapping("/mcp") public class McpToolController { private final McpSyncServer mcpSyncServer; private final MathService mathService; public McpToolController(McpSyncServer mcpSyncServer, MathService mathService) { this.mcpSyncServer = mcpSyncServer; this.mathService = mathService; } @GetMapping("/add") public ResponseEntity<?> addTool() { List<SyncToolSpecification> newTools = McpToolUtils .toSyncToolSpecifications(ToolCallbacks.from(this.mathService)); for (SyncToolSpecification newTool : newTools) { this.mcpSyncServer.addTool(newTool); } return ResponseEntity.ok("添加成功"); } @GetMapping("/remove") public ResponseEntity<?> removeTool(String toolName) { this.mcpSyncServer.removeTool(toolName); return ResponseEntity.ok("删除工具【" + toolName + "】成功"); } }3.4 Cline 与 CC Switch 配置片段
Cline 的 MCP Server 配置,在设置里找到 MCP Servers,添加:
{ "mcpServers": { "ai-mcp-server": { "url": "http://localhost:8888/sse", "type": "sse" } } }CC Switch 的配置类似,关键是 URL 指向 Server 的 SSE 端点。如果你用的是 stdio 模式,配置方式不同,但本地联调推荐 SSE,因为动态更新通知在 SSE 下更直观。
模型配置部分,把 API Base 指向https://taotoken.net/api,Key 填你创建的那个。这样 Cline 和 Spring AI Client 用的是同一个通道。
4. 验证动态更新是否生效
配置写完之后,按这个顺序验证。
第一步,启动 Server,确认常驻工具被发现。访问 Server 的 SSE 端点或看启动日志,应该能看到 WeatherService 的两个工具。
第二步,启动 Client,看日志里是否打印出发现的工具列表。正常情况下会看到 weather 相关工具。
第三步,调用添加接口:
curl http://localhost:8888/mcp/add返回「添加成功」后,去 Cline 里刷新 MCP 服务。刷新后工具列表应该多出 sum 和 sub。
第四步,调用删除接口:
curl "http://localhost:8888/mcp/remove?toolName=sum"再次刷新 Cline,sum 工具消失,sub 还在。
第五步,用 Client 做一次对话验证。写一个测试接口:
@RestController @RequestMapping("/tools") public class ToolController { private final ChatClient chatClient; public ToolController(ChatClient.Builder aiClientBuilder, ToolCallbackProvider mcpTools) { this.chatClient = aiClientBuilder.defaultTools(mcpTools).build(); } @GetMapping("/calc") public ResponseEntity<String> calc(String prompt) { String response = this.chatClient.prompt(prompt).call().content(); return ResponseEntity.ok(response); } }请求http://localhost:8888/tools/calc?prompt=帮我算一下 3 加 5,如果模型返回 8,说明工具调用链路通了。如果返回的是模型自己编的答案而不是工具计算结果,说明工具没被正确注册或 Client 没刷新。
5. 本篇常见报错排查
5.1 Client 启动报版本不兼容
现象:启动时抛NoSuchMethodError或ClassNotFoundException,指向 MCP 相关类。
原因:Server 和 Client 的 Spring AI 版本不一致,或者 Client 依赖的模型 starter 与 MCP Client 版本冲突。
处理:确认 Server 用 1.0.0,Client 用 1.0.0-M7。如果 Client 还要接其他模型 starter,检查那个 starter 的版本是否要求 1.0.0。必要时把模型调用统一走 API 通道,减少本地 starter 依赖。
5.2 工具添加后 Client 没刷新
现象:调了/mcp/add返回成功,但 Cline 或 Client 的工具列表没变化。
排查顺序:先确认tool-change-notification是否为 true;再确认 Client 的 SSE 连接是否还活着,看 Client 日志有没有断连重连记录;最后手动触发一次 Client 的工具发现,有些客户端需要手动刷新。
如果用的是 Cline,刷新按钮在 MCP 服务列表旁边。CC Switch 类似。
5.3 模型对话时不调用工具
现象:工具列表里有 sum,但问「3 加 5 等于几」时模型直接回答,没走工具。
原因:Client 构建 ChatClient 时没有把ToolCallbackProvider传进去,或者传了但模型不支持 function calling。
处理:确认aiClientBuilder.defaultTools(mcpTools)这行在。然后确认你用的模型支持工具调用。可以在模型对话页https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat测试一下当前 Key 下哪些模型支持 function calling。
5.4 SSE 连接超时或 404
现象:Client 启动时报连接http://localhost:8888失败。
原因:Server 的 SSE 端点路径不对。Spring AI MCP Server WebMVC 默认的 SSE 路径是/sse,不是根路径。
处理:把 Client 配置里的 url 改成http://localhost:8888/sse。Cline 配置里同理。
5.5 动态删除工具后模型仍然调用
现象:删了 sum,但模型还是尝试调 sum。
原因:Client 端缓存了工具列表,删除通知没到达,或者模型上下文里还留着旧工具定义。
处理:删除后强制刷新 Client,重新建立对话会话。如果用的是长连接会话,开一个新会话再测。
6. 把统一 Key 通道固化到你的本地联调流程
本地联调最怕的是环境变量散落在各处。我的做法是:在项目根目录放一个.env.local,里面只放一个TAOTOKEN_API_KEY,然后 Spring AI 的配置文件、Cline 的配置、CC Switch 的配置都从这个变量读。这样换 Key 的时候只改一个地方。
Spring AI 这边,可以在application.yml里用占位符:
spring: ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/apiCline 和 CC Switch 如果支持环境变量引用,也指向同一个变量。不支持的话,手动填一次,但保证和 Spring AI 用的是同一个 Key。
长期做编码和 Agent 联调的话,可以考虑 Coding Planhttps://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan,把 Key 管理和用量集中起来。Claude Code 相关的接入参考https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=claude-code。
最后提醒一个实操细节:MCP 工具动态更新在本地验证时,建议把 Server 和 Client 分开两个终端启动,日志分开看。Server 日志关注工具注册和通知发送,Client 日志关注工具发现和连接状态。这样出问题时能快速定位是 Server 没发通知,还是 Client 没收到。