资讯动态

Spring AI 2.0 MCP 协议实战:Model Context Protocol SDK 与多服务器编排

发布时间:2026/10/16 16:53:37 来源:尧图企业网站定制
1. 从三套适配代码到一套协议Spring AI 2.0 MCP 到底解决什么问题如果你正在做 AI 应用落地大概率踩过这个坑OpenAI 有 function callingClaude 有 tool useGemini 又是另一套 declarative tools。接三个模型就得写三套工具适配代码。更麻烦的是企业内部那一堆能力孤岛——财务的智能报表、CRM 的客户洞察、知识库的语义检索每个都是独立服务想串给大模型调用全靠手写胶水代码。Model Context ProtocolMCP就是冲着这件事来的。它定义了一套标准化协议让 AI 模型以统一方式与外部工具和资源交互。Spring AI 2.0 把底层 MCP Java SDK 升级到了 0.17.2并引入McpSyncServerCustomizer、McpAsyncServerCustomizer等接口同时修复了非 Web 上下文下的自动配置问题。这篇聚焦一个具体目标用 Spring AI 2.0 的 Model Context Protocol SDK 搭一套多服务器编排骨架覆盖服务注册、工具发现与调用路由。我会给出可复制的依赖与配置片段演示本地启动多个 MCP 服务、验证工具列表与调用链路的完整动作。适合已经写过 Spring Boot、想快速把 MCP 跑起来的同学。MCP 的核心能力有四类Tools模型可调用的函数、Resources模型可读取的数据、Prompts预定义提示模板、Sampling服务器反调模型。多服务器编排主要围绕 Tools 展开因为工具发现和调用路由是编排的核心。MCP Java SDK 分三层Client/Server 层、Session 层、Transport 层。Transport 层负责 JSON-RPC 消息的序列化与传输Spring AI 目前支持 Stdio、SSE、Streamable HTTP 三种方式。Stdio 适合本地进程SSE 适合远程长连接Streamable HTTP 是 0.17.2 引入的新传输方式在保持无状态的同时提供流式响应能力。多服务器编排要解决三个问题服务注册每个 MCP Server 怎么被客户端发现、工具发现不同 Server 的工具如何统一暴露、调用路由LLM 决定调用某个工具时请求怎么落到正确的 Server。Spring AI 通过配置化的多连接 工具名前缀 ToolCallback 桥接把这三件事串了起来。2. TaoToken 前置给多服务器编排准备一个稳定的模型入口多服务器编排跑起来之后最终还是要让 LLM 来决定调用哪个工具。这时候模型入口的稳定性就很关键——如果模型侧频繁超时或鉴权失败你根本分不清是 MCP 编排的问题还是模型接入的问题。我试过用 TaoToken 作为模型接入层它的 API 地址是https://taotoken.net/api兼容 OpenAI 风格的调用方式。对于 Spring AI 来说你只需要把base-url指向它再配一个 API Key 就能用。这样做的好处是MCP 编排层和模型接入层解耦排查问题时可以分别验证。先拿到 API Key。访问https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_springai创建密钥复制出来备用。注意不要把它硬编码进代码用环境变量注入。然后在 Spring AI 的配置里指定 base-url 和 api-key。Spring AI 2.0 的 OpenAI starter 支持自定义 base-url这样就能把请求转发到 TaoToken 的兼容端点。模型 ID 按你实际使用的填写比如gpt-4o或claude-3-5-sonnet这类。如果你需要长期跑编码类 Agent 任务可以了解下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_springai。它更适合持续性的编码场景和本篇的多服务器编排可以配合使用。这里要强调一点TaoToken 在这里的角色是模型接入层不是 MCP Server 本身。MCP Server 是你自己写的、暴露内部能力的服务TaoToken 提供的是 LLM 的调用入口。两者职责不同不要混淆。配置好模型入口后你就可以专注于 MCP 编排本身了。接下来看具体的依赖和配置。3. 可复制配置依赖、application.yml 与多服务器连接片段这一节给出可以直接复制的配置。先看 Maven 依赖。Spring AI 的 MCP 集成分 Client 和 Server 两个维度各自有对应的 starter。dependencies !-- MCP 客户端连接外部 MCP 服务器 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency !-- MCP 服务器暴露本应用的工具能力 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId /dependency /dependencies版本管理通过 BOM 引入避免手动对齐版本号dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version2.0.0-M2/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagementSpring AI 的里程碑版本需要单独添加仓库repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://repo.spring.io/milestone/url snapshots enabledfalse/enabled /snapshots /repository /repositories接下来是application.yml。这是多服务器编排的核心我把它拆成模型接入和 MCP 客户端两部分。模型接入指向 TaoTokenspring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o temperature: 0.7MCP 客户端部分同时连接本地 Stdio 服务和远程 SSE 服务演示混合编排spring: ai: mcp: client: enabled: true type: SYNC request-timeout: 30s toolcallback: enabled: true stdio: connections: local-fs: command: npx args: - -y - modelcontextprotocol/server-filesystem - /data/docs sse: connections: order-service: url: http://localhost:8081 sse-endpoint: /mcp/sse crm-service: url: http://localhost:8082 sse-endpoint: /mcp/sse如果你在 Windows 下跑 Stdionpx需要用cmd.exe /c包装stdio: connections: local-fs: command: cmd.exe args: - /c - npx.cmd - -y - modelcontextprotocol/server-filesystem - D:\\data\\docstoolcallback.enabled: true是关键配置。它让所有从 MCP Server 发现的工具自动注册为 Spring AI 的ToolCallback这样在ChatClient里就能直接调用不用手动桥接。如果你需要自定义客户端行为比如针对某个慢服务单独设超时可以实现McpSyncClientCustomizerComponent public class CustomMcpSyncClientCustomizer implements McpSyncClientCustomizer { Override public void customize(String serverConfigurationName, McpClient.SyncSpec spec) { if (slow-external-api.equals(serverConfigurationName)) { spec.requestTimeout(Duration.ofSeconds(60)); } spec.toolsChangeConsumer(tools - System.out.println(Tools changed for [ serverConfigurationName ]: tools.stream().map(McpSchema.Tool::name).toList())); } }工具过滤也是多服务器编排里常用的。当某个 Server 暴露了太多工具你只想放行一部分可以实现McpToolFilterComponent public class SecurityToolFilter implements McpToolFilter { private static final SetString ALLOWED Set.of( query_orders, get_order_detail, search_products, read_file); Override public boolean test(McpConnectionInfo connectionInfo, McpSchema.Tool tool) { if (tool.name().contains(write) || tool.name().contains(delete)) { return false; } return ALLOWED.contains(tool.name()); } }注意McpToolFilter在应用上下文里只能定义一个 Bean它会作用于所有 MCP 连接。命名冲突由McpToolNamePrefixGenerator处理默认会给工具名加上服务器前缀比如order-service__query_orders这样不同 Server 的同名工具不会打架。4. 验证请求本地启动多 MCP 服务并检查工具列表与调用链路配置写完了接下来验证。分三步启动 MCP Server、检查工具注册、发起一次真实调用。先写一个 MCP Server暴露订单查询工具。用注解方式最直观Component public class OrderMcpTools { private final OrderService orderService; public OrderMcpTools(OrderService orderService) { this.orderService orderService; } McpTool(name query_orders, description 根据客户ID查询订单列表) public ListOrderSummary queryOrders( McpToolParam(description 客户ID, required true) String customerId, McpToolParam(description 订单状态) String status) { return orderService.findByCustomerAndStatus(customerId, status) .stream() .map(o - new OrderSummary(o.getId(), o.getStatus(), o.getAmount())) .toList(); } McpTool(name get_order_detail, description 获取订单详细信息) public OrderDetail getOrderDetail( McpToolParam(description 订单ID, required true) String orderId) { Order order orderService.findById(orderId); return new OrderDetail(order.getId(), order.getCustomerName(), order.getItems(), order.getTotalAmount()); } }Server 侧的配置spring: ai: mcp: server: type: SYNC protocol: SSE server-info: name: order-mcp-server version: 1.0.0 annotation-scanner: enabled: true启动这个 Server它会在 8081 端口暴露 SSE 端点。再启动一个 CRM Server 在 8082暴露get_customer_info工具。两个 Server 都起来后回到客户端应用。客户端启动时Spring AI 会自动连接所有配置的 MCP Server并把工具注册到SyncMcpToolCallbackProvider。写一个CommandLineRunner打印注册结果Component public class ToolRegistryInspector implements CommandLineRunner { private final SyncMcpToolCallbackProvider toolProvider; public ToolRegistryInspector(SyncMcpToolCallbackProvider toolProvider) { this.toolProvider toolProvider; } Override public void run(String... args) { ToolCallback[] callbacks toolProvider.getToolCallbacks(); System.out.println( Registered MCP Tools ); for (ToolCallback cb : callbacks) { System.out.printf( - %s: %s%n, cb.getName(), cb.getDescription()); } } }预期输出类似 Registered MCP Tools - local-fs__read_file: Read the contents of a file - local-fs__list_directory: List directory contents - order-service__query_orders: 根据客户ID查询订单列表 - order-service__get_order_detail: 获取订单详细信息 - crm-service__get_customer_info: 获取客户信息看到前缀了吗local-fs__、order-service__、crm-service__这是默认前缀生成器自动加的跨服务器命名冲突就这样解决了。最后验证调用链路。把工具桥接进ChatClientConfiguration public class ChatClientConfig { Bean public ChatClient chatClient(ChatModel chatModel, SyncMcpToolCallbackProvider toolCallbackProvider) { return ChatClient.builder(chatModel) .defaultToolCallbacks(toolCallbackProvider.getToolCallbacks()) .build(); } }发起一次真实请求Service public class AssistantService { private final ChatClient chatClient; public AssistantService(ChatClient chatClient) { this.chatClient chatClient; } public String ask(String message) { return chatClient.prompt() .system(你是客服助手可以使用订单查询和客户信息工具。) .user(message) .call() .content(); } }调用ask(帮我查一下客户 C1001 的订单)LLM 会自动决定调用order-service__query_orders请求通过 MCP 协议落到订单 Server返回结果后再由模型组织成自然语言。整条链路用户输入 → ChatClient → LLM 决策 → ToolCallback → MCP Client → MCP Server → 业务服务 → 原路返回。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth多服务器编排跑起来的过程中报错基本集中在几个地方。这一节按真实报错对照排查。401 Unauthorized。这个最常见分两种来源。一种是模型侧TAOTOKEN_API_KEY没注入或写错检查环境变量是否生效可以在启动日志里确认 base-url 和 key 是否被正确读取。另一种是 MCP Server 侧如果 Server 配了鉴权客户端连接时没带 token也会 401。排查方法先用 curl 单独测模型端点再单独测 MCP Server 的 SSE 端点定位是哪一层。local proxy failed。这个报错通常出现在 Stdio 连接场景。原因一般是command或args写错子进程根本没起来。检查npx是否在 PATH 里Windows 下是否用了cmd.exe /c包装。还有一个容易忽略的点args里的路径如果不存在子进程会启动失败但报错信息不直观。先把命令复制到终端手动跑一遍确认能起来再放进配置。reading choices 相关报错。这类报错一般来自模型响应解析阶段说明请求发出去了但返回体不符合预期。常见原因是 base-url 配错比如漏了/api或者多了斜杠。TaoToken 的 API 地址是https://taotoken.net/apiSpring AI 会在后面拼接/v1/chat/completions这类路径所以 base-url 不要自己再加/v1。另外模型 ID 写错也会导致返回体异常确认你填的模型名在服务端是存在的。OAuth 相关报错。如果 MCP Server 用了 OAuth 鉴权客户端需要配置对应的认证信息。Spring AI 的 MCP Client 支持在连接配置里加 header。排查时先确认 token 是否过期再看 header 名称是否和服务端要求一致。有些服务端要求Authorization: Bearer xxx有些要求自定义 header对不上就会报 OAuth 错误。工具列表为空。配置看起来都对但getToolCallbacks()返回空数组。检查三点toolcallback.enabled是否为 trueannotation-scanner.enabled是否为 trueMCP Server 是否真的启动成功。可以在客户端启动日志里搜索连接相关的 INFO 日志确认每个 Server 的连接状态。工具名冲突。如果看到两个工具同名说明前缀生成器没生效。默认情况下 Spring AI 会加前缀但如果你自定义了McpToolNamePrefixGenerator并返回了空字符串就会冲突。检查是否有自定义的前缀生成器 Bean。排查的核心思路是分层验证模型层、MCP 连接层、工具注册层、调用层一层一层单独测不要一上来就端到端跑。6. 把编排跑稳之后模型入口与长期编码的配合多服务器编排骨架搭起来之后你会发现真正影响体验的往往是模型侧的稳定性。MCP 负责把工具串起来但决策调用哪个工具的是 LLM。如果模型入口不稳定编排再优雅也白搭。TaoToken 在这里的价值是提供一个统一的模型接入点。你可以在application.yml里把 base-url 指向https://taotoken.net/api用同一个 Key 切换不同模型不用改 MCP 编排层的代码。模型对话入口在https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_springai可以用来快速验证某个模型对工具调用的支持情况。如果你在搭的是长期运行的编码 AgentMCP 编排会涉及大量工具调用和上下文管理这时候 Coding Plan 更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_springai。它针对持续性编码场景做了优化和本篇的多服务器编排可以配合。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_springai里面有完整的参数说明和示例。控制台在https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_springai可以查看调用量和余额。最后给一个实操建议先把 MCP 编排跑通用本地 Stdio 服务验证工具发现和调用链路确认没问题后再接入远程 SSE 服务。模型入口先用一个稳定的端点等编排稳定了再考虑多模型切换。这样排查问题时变量最少定位最快。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑