 Spring AI+nacos的MCP实现:从注册发现到调用链路)
1. 为什么要把 MCP 服务塞进 nacos多 Agent 场景下的注册发现痛点如果你正在用 Spring AI 做智能体大概率踩过这样一个坑工具方法Tool Function写在一个服务里Agent 直接本地调用单机跑没问题。可一旦拆成多个微服务A 服务想用 B 服务的快递查询工具C 服务想用 D 服务的路线规划工具调用关系就变成了一张蜘蛛网。每个 Agent 都要硬编码对方地址改一个端口就得重新打包这显然不是微服务该有的样子。MCPModel Context Protocol解决的正是「模型怎么标准化地连到数据和工具」这件事。它把工具能力抽象成 MCP Server用统一的协议暴露出去Agent 作为 MCP Client 去消费。但 MCP 本身只定义了协议没规定服务怎么被发现。这时候 nacos 就派上用场了——它本来就是干注册发现的把 MCP Server 注册进 nacosClient 通过服务名去拉取地址变了、实例扩了Client 完全无感。这套组合适合谁适合已经在用 Spring Cloud Alibaba 体系、手里有多个模型工具服务、想让 Agent 动态发现工具的团队。我试过把三个工具服务分别注册进 nacosAgent 侧只配一个 service-name新增工具服务时客户端零改动这个体验比硬编码强太多。整条链路是这样的MCP Server 启动时把工具元信息注册到 nacos → MCP Client 从 nacos 订阅服务列表 → Client 拿到实例后建立 SSE 连接 → 拉取工具列表 → 注入到 ChatClient → 模型决定调用哪个工具 → 请求经统一 API 通道转发到模型 → 返回结果。下面按这个顺序一步步落地。2. TaoToken 前置统一 Key 与 API 通道让 MCP 调用链路只认一个出口在动手写 nacos 配置之前先把模型调用这一层理顺。MCP 链路里最容易被忽略的是工具调用最终还是要落到 LLM 上而 LLM 的 Key 管理、Base URL 切换、多模型路由如果散落在每个 MCP Client 里维护成本会爆炸。我的做法是让所有 MCP Client 的模型请求都走同一个 API 通道Key 只配一份。TaoToken 在这里扮演的就是统一出口的角色。它的 API 地址是https://taotoken.net/api兼容 OpenAI 风格的接口Spring AI 的 OpenAI Starter 可以直接对接。你只需要在配置里把 base-url 指过去api-key 填上在控制台生成的 Key模型 ID 按需选。这样 MCP Client 不用关心背后是哪个模型厂商换模型只改一个 model 字段。具体操作路径先到控制台的 API Keys 页面生成一个 Key然后打开接入文档对照 Spring AI 的配置项。文档里对 base-url、model、api-key 三个字段的写法有明确说明照着填就行。如果你后面要跑长期编码类 Agent可以考虑 Coding Plan额度更划算只是验证模型连通性的话用模型对话页面直接测更快。这里要强调一点MCP 的注册发现走 nacos模型调用走统一 API 通道这两条线是分开的。nacos 管的是「工具有哪些、在哪」API 通道管的是「模型怎么调」。把这两件事解耦后面排障会清晰很多——工具拉不到是 nacos 的问题模型报 401 是 Key 的问题不会混在一起。3. 可复制配置MCP Server 注册 MCP Client 订阅的完整片段这一节是全文的核心所有配置都可以直接抄。先看 MCP Server 侧的 pom 依赖关键是三个包spring-ai-starter-mcp-server-webflux提供 MCP 协议支持spring-ai-alibaba-starter-mcp-registry负责往 nacos 注册版本用1.0.0.3。properties java.version17/java.version spring-ai.version1.0.3/spring-ai.version spring-boot.version3.5.5/spring-boot.version spring-ai-alibaba.version1.0.0.3/spring-ai-alibaba.version /properties dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webflux/artifactId /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-mcp-registry/artifactId version${spring-ai-alibaba.version}/version /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement工具类用Tool注解暴露两个模拟工具分别对应快递查询和路线规划。注意ToolParam的 description 要写清楚模型靠它判断该传什么参数。Service public class KuaiDiQueryTools { Tool(description Please help me check the logistics information for the logistics tracking number) public String getKuaiDiQuery( ToolParam(description logistics tracking number,such as YT196807550996) String logisticsTrackingNumber) { return 已发货\n09-11 14:09正在安排圆通快递揽收\n仓库处理中; } } Service public class BaiduMapTools { Tool(description Help me plan the route from the departure point to the destination) public String getLine( ToolParam(description departure point,such as beijing) String departurePoint, ToolParam(description destination,such as shanghai) String destination) { return 自驾出行; } }把工具注入ToolCallbackProvider这里用MethodToolCallbackProvider聚合多个工具对象Configuration public class ToolCallbackConfig { Bean public ToolCallbackProvider toolCallbackProvider( BaiduMapTools baiduMapTools, KuaiDiQueryTools kuaiDiQueryTools) { return MethodToolCallbackProvider.builder() .toolObjects(baiduMapTools, kuaiDiQueryTools) .build(); } }Server 侧的 yml 配置重点是alibaba.mcp.nacos.register这一段service-name就是 Client 要订阅的名字spring: ai: mcp: server: name: tools version: 1.0.0 type: ASYNC instructions: tool sse-message-endpoint: /mcp/messages capabilities: tool: true resource: true prompt: true completion: true alibaba: mcp: nacos: namespace: your-namespace-id server-addr: 127.0.0.1:8848 username: nacos password: nacos register: enabled: true service-group: mcp-server service-name: toolsClient 侧 pom 引入spring-ai-starter-mcp-client-webflux和同一个 registry 包dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-mcp-registry/artifactId version${spring-ai-alibaba.version}/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client-webflux/artifactId /dependencyClient 的 yml 里sse.connections下配一个连接service-name必须和 Server 注册的一致spring: ai: mcp: client: enabled: true name: xqxjy-ai version: 1.0.0 request-timeout: 30s type: ASYNC alibaba: mcp: nacos: namespace: your-namespace-id server-addr: 127.0.0.1:8848 username: nacos password: nacos client: enabled: true sse: connections: server1: service-name: tools version: 1.0.0模型调用这块把 base-url 指向 TaoToken 的 API 地址Key 填控制台生成的spring: ai: openai: base-url: https://taotoken.net/api api-key: sk-your-taotoken-key chat: options: model: gpt-4o-mini注入 Client 侧的ToolCallbackProvider注意 Qualifier 名字是loadbalancedMcpAsyncToolCallbacks这个 bean 由 registry 包自动装配Autowired Qualifier(loadbalancedMcpAsyncToolCallbacks) private ToolCallbackProvider toolCallbackProvider;4. 验证请求从 nacos 控制台到端到端对话的完整结果配置写完先启动 MCP Server。启动日志里会看到往 nacos 注册的请求打开 nacos 控制台的服务列表service-group为mcp-server、service-name为tools的实例应该已经在线。点进详情能看到实例的 IP、端口和元数据元数据里包含工具方法的描述信息。这一步能确认注册链路是通的。接着启动 MCP Client日志里会打印从 nacos 拉取到的服务实例以及建立 SSE 连接的过程。如果看到loadbalancedMcpAsyncToolCallbacks初始化完成说明工具列表已经拉到了。这时候可以写一个测试接口GetMapping(/chat/tools) public FluxString chatTools(String msg, HttpServletResponse response) { response.setCharacterEncoding(UTF-8); return chatClient.prompt(msg) .toolCallbacks(toolCallbackProvider.getToolCallbacks()) .stream() .content(); }请求http://localhost:8080/chat/tools?msg帮我查一下快递单号YT196807550996预期结果是模型识别出要调用getKuaiDiQuery工具传入单号参数工具返回物流信息模型再把结果组织成自然语言流式返回。控制台能看到工具调用的入参和出参日志。再测一个路线查询msg帮我规划从北京到上海的路线模型应该调用getLine返回「自驾出行」。两个工具都能被正确路由说明 nacos 注册发现 MCP 协议 模型调用这条链路完整跑通了。如果模型侧返回的是正常文本而不是报错说明 TaoToken 的 API 通道也通了。验证模型本身是否连通可以到模型对话页面直接发一条消息确认 Key 和 base-url 没问题。这一步和 MCP 链路分开测能快速定位问题出在哪一层。5. 本篇常见错排查401、local proxy failed、reading choices 逐个拆报错一401 Unauthorized。这个基本是 API Key 的问题。先检查spring.ai.openai.api-key有没有填对注意不要有多余空格。如果 Key 是从控制台复制的确认没有把前后引号也带进去。还有一种情况是 base-url 写成了https://taotoken.net少了/api请求打到了首页而不是 API 端点也会返回 401。正确写法是https://taotoken.net/api。报错二local proxy failed 或 connection refused。这个通常出在 nacos 连接上。检查server-addr的 IP 和端口本地默认是127.0.0.1:8848。如果 nacos 开了鉴权username和password必须填且 namespace 要用命名空间 ID 而不是名称。另外确认 Server 和 Client 的 namespace 一致不一致的话 Client 订阅不到任何实例日志里会提示没有可用服务。报错三reading choices 相关异常。这个多半是模型返回格式和 Spring AI 预期不匹配。先确认model字段填的模型 ID 在 TaoToken 侧是支持的别填一个不存在的名字。如果用的是流式接口检查type是不是配了ASYNC同步和异步的返回处理不一样。还有一种可能是 request-timeout 太短模型响应慢导致连接被掐断把request-timeout调到60s试试。报错四OAuth 或鉴权相关。如果你用的是 Claude Code 这类工具接入可能会碰到 OAuth 流程。这类场景下确认三件套齐全Base URL 填https://taotoken.net/apiKey 填控制台生成的Model ID 填对应模型。三者缺一不可少一个就会在鉴权阶段失败。Cline MCP 或 Codex 的 auth.json 配置也是同理Base URL、Key、Model ID 三个字段都要对上。报错五工具列表为空。Client 启动后getToolCallbacks()返回空数组说明没拉到工具。先看 nacos 控制台里 Server 实例是否在线再看 Client 日志里有没有订阅成功的记录。常见原因是service-name拼写不一致或者service-group对不上。Server 注册在mcp-server组Client 订阅时也要指定同一个组。排障时建议按「nacos 注册 → MCP 连接 → 工具拉取 → 模型调用」的顺序逐层验证每层都有独立的日志可看不要一上来就怀疑模型。6. 语义一致 CTA把 Key、文档和验证入口一次配齐整条链路跑通后日常维护其实很轻。新增工具服务时只要在 Server 侧加Tool方法、改一下service-name注册进 nacosClient 侧加一个sse.connections条目就行不用动业务代码。模型侧换模型也只改一个model字段Key 和 base-url 保持不变。如果你还没生成 Key到 API Keys 页面创建一个然后对照接入文档把 Spring AI 的配置项填完整。验证模型连通性用模型对话页面最快长期跑编码类 Agent 可以看 Coding Plan。文档里对 base-url、model、api-key 的写法有逐项说明配置时对着抄不会出错。最后留一个实操建议把 MCP Server 和 Client 的 yml 里 nacos 相关配置抽成公共配置用 Spring 的 profile 区分本地和测试环境。namespace 和 server-addr 走环境变量注入这样本地调试和部署到测试环境时不用改代码。工具方法的 description 尽量写具体模型选工具的准确率会明显提升这个在实测中比调参管用。