真心强大!Spring AI + MCP 智能体工具动态更新:把 MCP 配置改到 TaoToken 的完整实践 1. Spring AI MCP 工具动态更新到底解决什么问题如果你正在用 Spring AI 搭智能体大概率遇到过这个场景工具类写好了Tool注解也标了但每次加一个新工具就得重启服务。开发阶段还能忍生产环境重启一次就是一次抖动。MCPModel Context Protocol在 Spring AI 里的动态工具更新能力就是冲着这个痛点来的——服务端不重启客户端能感知模型下一轮对话立刻用上新工具。先把概念对齐。MCP 是一套标准化接口让 AI 应用和智能体能够访问外部工具、获取资源、使用提示模板。它遵循 client-server 架构MCP Server 暴露工具、资源和提示MCP Client 连接 Server 并消费这些能力AI 模型通过 Client 与外部世界交互。Spring AI 提供了完整的 MCP 实现包含客户端和服务端组件。动态工具更新的价值在于三点Server 无需重启即可增删工具Client 能检测到变更模型可以立即使用新功能。这三点串起来才叫真正的热更新。但实际落地时很多人卡在另一个地方模型 Key 和 MCP 端点散落在各个配置文件里测试环境一套、生产环境一套换个模型要改好几个地方。这篇就聚焦这个组合场景——把 MCP 配置统一改到 TaoToken同时把工具动态注册和调用链路自检跑通。适合谁需要统一管理多模型 Key 与 MCP 服务端点、又想让工具热更新真正可用的 Spring AI 开发者。我试过把 Server 和 Client 拆成两个独立进程来验证动态更新这样能清楚看到通知到底有没有生效。下面按可复制的顺序来。2. TaoToken 前置准备与 MCP 端点统一管理在写配置之前先把Key 和端点从哪来这件事定下来。TaoToken 在这里扮演的角色是统一的模型接入层你拿到一个 Base URL 和一个 API Key就能在 Spring AI 里指向它不用为每个模型单独维护一套凭证。第一步去官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册并进入控制台。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_springai_consoleutm_campaignrewrite 在里面创建 API Key。创建完先别关页面Key 只完整显示一次复制到安全的地方。第二步确认你要用的模型 ID。不同模型在请求里用的 model 字段不一样这个值后面要写进application.yml。可以在模型对话页面 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_springai_chatutm_campaignrewrite 里先手动发一条消息确认模型能正常响应顺便把返回里的 model 名称记下来。第三步理解 API 端点。TaoToken 的 API 地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数直接作为 Spring AI 的 base-url 使用。Spring AI 的 OpenAI 兼容客户端会自动在 base-url 后面拼接/v1/chat/completions这类路径所以配置里只写到/api即可。这里有个容易踩的坑MCP Server 和 MCP Client 是两套配置。Server 负责暴露工具Client 负责连接 Server 并把工具喂给模型。TaoToken 的 Key 是给模型调用用的不是给 MCP 传输用的。也就是说MCP 的 SSE 连接走的是你自己的 localhost 或内网地址而模型推理走的是 TaoToken 的 API。这两条链路要分清否则会出现工具注册成功了但模型调不动的假象。如果你打算长期跑编码类 Agent可以顺带看下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_springai_planutm_campaignrewrite 它更适合高频调用的场景。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_springai_docutm_campaignrewrite API Keys 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_springai_keysutm_campaignrewrite 。这几个地址先存着后面排障会用到。3. 可复制的 application.yml 与 MCP 客户端配置这一节是全文的核心配置片段可以直接抄。先明确版本MCP Server 用1.0.0MCP Client 用1.0.0-M7。为什么 Client 用 M7因为部分模型服务与1.0.0的 Client 存在兼容问题M7 更稳。这个版本差异是实测出来的不是随便选的。先看依赖。Server 侧dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId version1.0.0/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version1.0.0/version /dependencyClient 侧dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId version1.0.0-M7/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client-webflux/artifactId version1.0.0-M7/version /dependency然后是 Server 的application.yml这里把 MCP Server 打开并指向 TaoToken 作为模型后端spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: your-model-id mcp: server: enabled: true name: ai_mcp_server version: 1.0.0 type: SYNC tool-change-notification: true注意tool-change-notification: true这一行。它控制工具变更后是否自动通知客户端。默认行为就是通知但显式写出来更清楚。如果你在调试阶段想手动控制刷新时机可以临时设为false等确认链路通了再打开。Client 的application.ymlspring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: your-model-id mcp: client: name: ai-mcp-client initialized: true type: ASYNC sse: connections: server1: url: http://localhost:8888这里type: ASYNC是关键。同步模式下工具变更通知的处理会阻塞主线程异步模式才能让 Client 在后台接收通知并更新工具列表。sse.connections.server1.url指向你的 MCP Server 地址本地调试就是http://localhost:8888。把 Key 放进环境变量TAOTOKEN_API_KEY不要硬编码在 yml 里。启动前 export 一下export TAOTOKEN_API_KEYsk-你的key如果你用的是 IDE 运行在 Run Configuration 里加环境变量即可。这一步做完配置层面就齐了。4. 工具动态注册与调用链路验证配置写完接下来验证动态两个字是不是真的生效。先写两个工具类。一个是启动时自动注册的Component public class WeatherService { Tool(description 获取当前天气预报) WeatherResponse getCurrentWeather(WeatherRequest request) { // 实际实现 } Tool(description 获取IP地址详细信息) String getIpAddressInfo(String ip) { // 实际实现 } }另一个是需要动态管理的Service public class MathService { Tool(name sum, description 计算2个数的和) public int sum(int a, int b) { return a b 1000; } Tool(name sub, description 计算2个数的差值) public int sub(int a, int b) { return a - b - 1000; } }注意sum和sub里故意加了1000和-1000这是为了在验证时能一眼看出模型到底调没调这个工具——如果返回结果带偏移量说明工具真的被执行了。注册自动工具Configuration public class McpToolConfig { Bean ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } }动态增删的 ControllerRestController 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() { ListSyncToolSpecification 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 】成功); } }启动 Server 后先访问http://localhost:8888/mcp/add再访问http://localhost:8888/mcp/remove?toolNamesum。每次操作后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(/sum) public ResponseEntityString calcSum(String prompt) { String response this.chatClient.prompt(prompt).call().content(); return ResponseEntity.ok(response); } }调用http://localhost:8080/tools/sum?prompt帮我算一下 3 加 5。如果返回结果里出现 1008351000说明工具被正确调用且动态注册生效。如果返回 8说明模型没走工具直接心算了——这时候要回去检查 Client 的initialized和type配置。5. 常见报错排查401、local proxy failed、reading choices、OAuth动态更新跑不通报错通常集中在几个地方。下面按真实报错对照排查。401 Unauthorized。这个最常见八成是 Key 的问题。检查三处环境变量TAOTOKEN_API_KEY是否真的 export 了application.yml里api-key的占位符拼写是否一致Key 是否在控制台被删除或过期。如果 Key 没问题检查base-url是否写成了https://taotoken.net/api多一个斜杠或少一个/api都会导致 401。local proxy failed。这个报错通常出现在 Client 连接 Server 的阶段。检查sse.connections.server1.url是否可达本地调试确认 Server 真的在 8888 端口监听。如果 Server 和 Client 在不同机器把 localhost 换成实际 IP。另外确认没有多余的代理配置干扰本地回环地址。reading choices 相关报错。这类错误一般出现在模型响应解析阶段说明请求发出去了但返回结构不符合预期。检查model字段是否填了正确的模型 ID以及该模型是否支持工具调用function calling。不是所有模型都支持工具调用选一个明确支持 tool use 的模型。OAuth 相关报错。如果你在配置里误加了 OAuth 认证流程而 TaoToken 的 API Key 走的是 Bearer Token 方式就会冲突。检查application.yml里有没有多余的oauth2配置块有的话删掉。API Key 认证只需要api-key一个字段。还有一个隐蔽的坑MCP Client 的initialized: true如果没写Client 启动后不会主动拉取工具列表表现就是Server 明明有工具Client 一个都看不到。这个字段默认值在不同版本里行为不一致显式写true最保险。排查顺序建议先确认 401Key 和 base-url再确认 local proxy failed网络连通性最后看工具列表是否为空initialized 和 type。按这个顺序走大部分问题能在五分钟内定位。6. 把配置沉淀下来让工具热更新真正可用走到这里你应该已经跑通了Server 动态增删工具 → Client 自动感知 → 模型调用新工具这条完整链路。回头看真正让这套东西可用的不是某一个配置项而是把模型接入层和 MCP 传输层分清楚了TaoToken 负责模型推理的 Key 和端点统一MCP 的 SSE 连接负责工具传输两者各司其职。实际项目里我建议把tool-change-notification保持为true让通知自动化。只有在需要精确控制刷新时机比如批量增删工具时避免频繁通知才临时关掉。另外MathService那种带偏移量的返回值设计在联调阶段非常有用上线前记得改回正常逻辑。如果你还要接 Claude Code 这类工具配置三件套要写全Base URL 填https://taotoken.net/apiKey 填控制台创建的 API KeyModel ID 填你验证过的模型名。这三项缺一个都连不上。Claude Code 的接入说明在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_springai_ccutm_campaignrewrite 里有详细步骤。最后留一个实用技巧把 Server 和 Client 的启动日志级别调到 DEBUG能看到工具注册和通知的完整过程。logging.level.org.springframework.ai.mcpDEBUG这一行加进 yml排查动态更新问题时比猜快得多。