news 2026/10/16 16:53:24

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI 2.0 MCP 协议实战:Model Context Protocol SDK 与多服务器编排

1. 从三套适配代码到一套协议:Spring AI 2.0 MCP 到底解决什么问题

如果你正在做 AI 应用落地,大概率踩过这个坑:OpenAI 有 function calling,Claude 有 tool use,Gemini 又是另一套 declarative tools。接三个模型,就得写三套工具适配代码。更麻烦的是企业内部那一堆能力孤岛——财务的智能报表、CRM 的客户洞察、知识库的语义检索,每个都是独立服务,想串给大模型调用,全靠手写胶水代码。

Model Context Protocol(MCP)就是冲着这件事来的。它定义了一套标准化协议,让 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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_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 Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_springai。它更适合持续性的编码场景,和本篇的多服务器编排可以配合使用。

这里要强调一点:TaoToken 在这里的角色是模型接入层,不是 MCP Server 本身。MCP Server 是你自己写的、暴露内部能力的服务;TaoToken 提供的是 LLM 的调用入口。两者职责不同,不要混淆。

配置好模型入口后,你就可以专注于 MCP 编排本身了。接下来看具体的依赖和配置。

3. 可复制配置:依赖、application.yml 与多服务器连接片段

这一节给出可以直接复制的配置。先看 Maven 依赖。Spring AI 的 MCP 集成分 Client 和 Server 两个维度,各自有对应的 starter。

<dependencies> <!-- MCP 客户端:连接外部 MCP 服务器 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> <!-- MCP 服务器:暴露本应用的工具能力 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency> </dependencies>

版本管理通过 BOM 引入,避免手动对齐版本号:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>2.0.0-M2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

Spring AI 的里程碑版本需要单独添加仓库:

<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>

接下来是application.yml。这是多服务器编排的核心,我把它拆成模型接入和 MCP 客户端两部分。模型接入指向 TaoToken:

spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o temperature: 0.7

MCP 客户端部分,同时连接本地 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 下跑 Stdio,npx需要用cmd.exe /c包装:

stdio: connections: local-fs: command: cmd.exe args: - "/c" - "npx.cmd" - "-y" - "@modelcontextprotocol/server-filesystem" - "D:\\data\\docs"

toolcallback.enabled: true是关键配置。它让所有从 MCP Server 发现的工具自动注册为 Spring AI 的ToolCallback,这样在ChatClient里就能直接调用,不用手动桥接。

如果你需要自定义客户端行为,比如针对某个慢服务单独设超时,可以实现McpSyncClientCustomizer:

@Component 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 暴露了太多工具,你只想放行一部分,可以实现McpToolFilter:

@Component public class SecurityToolFilter implements McpToolFilter { private static final Set<String> 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 List<OrderSummary> 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__,这是默认前缀生成器自动加的,跨服务器命名冲突就这样解决了。

最后验证调用链路。把工具桥接进ChatClient:

@Configuration 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/api,Spring 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是否为 true;annotation-scanner.enabled是否为 true;MCP 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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_springai,可以用来快速验证某个模型对工具调用的支持情况。

如果你在搭的是长期运行的编码 Agent,MCP 编排会涉及大量工具调用和上下文管理,这时候 Coding Plan 更合适:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_springai。它针对持续性编码场景做了优化,和本篇的多服务器编排可以配合。

接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_springai,里面有完整的参数说明和示例。控制台在https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_springai,可以查看调用量和余额。

最后给一个实操建议:先把 MCP 编排跑通,用本地 Stdio 服务验证工具发现和调用链路,确认没问题后再接入远程 SSE 服务。模型入口先用一个稳定的端点,等编排稳定了再考虑多模型切换。这样排查问题时变量最少,定位最快。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 16:49:06

Android U盘路径获取:基于StorageManager与反射的OTG文件读写实践

简介&#xff1a;面向Android开发者的U盘读写工程示例&#xff0c;聚焦OTG外接存储场景&#xff0c;解决常规API无法直接获取U盘路径的问题&#xff0c;内容涵盖外部存储权限声明、运行时权限请求&#xff0c;以及通过反射读取系统媒体扫描类的私有存储卷映射字段、遍历卷信息定…

作者头像 李华
网站建设 2026/10/16 16:45:55

古典密码C实现:棋盘密码与凯撒密码轻量级嵌入式方案

简介&#xff1a;本资源是一份面向密码学初学者与C语言编程入门者的古典加密算法实践代码包&#xff0c;聚焦棋盘密码&#xff08;Playfair&#xff09;与凯撒密码两大基础替换/移位加密机制的完整C实现&#xff0c;助力理解加密原理、掌握字符处理逻辑及算法编程思维。压缩包为…

作者头像 李华
网站建设 2026/10/16 16:45:19

SmartAgent:基于用户认知链的具身化智能体架构

1. 这不是又一个“智能体”概念炒作&#xff0c;而是用户意图建模的实质性突破最近在几个技术社区和内部项目复盘会上&#xff0c;反复看到SmartAgent这个词被提起&#xff0c;但多数讨论停留在“它用了Chain-of-Thought”这种表面归因。我带团队落地过3个面向终端用户的个性化…

作者头像 李华
网站建设 2026/10/16 16:43:17

C语言文件读取避坑指南:从FILE指针到fgets的正确用法

“文件明明就在那儿&#xff0c;为什么程序就是说不存在&#xff1f;”这个问题我大概被问过不下几十次。C语言文件读取是每个写过C的人都会碰的环节&#xff0c;但偏偏这个环节里埋了一堆“看着正常、跑起来就翻车”的坑。今天我想把这些年踩过的、替别人排过的C语言文件读取问…

作者头像 李华
网站建设 2026/10/16 16:42:51

Linux服务器磁盘扩容实战:云硬盘分区与文件系统在线扩展指南

1. 服务器磁盘扩容&#xff1a;从分区到文件系统的一次完整实践服务器磁盘满了&#xff0c;这事做过运维的朋友都懂&#xff0c;日志一停、数据库一挂、磁盘告警邮件堆满收件箱的时候&#xff0c;谁都顾不上优雅。前阵子我负责的一台业务服务器就遇到了这个情况&#xff0c;刚好…

作者头像 李华
网站建设 2026/10/16 16:42:30

GEO发稿平台是什么?AI搜索时代品牌必须具备的5项核心能力

GEO发稿平台是什么&#xff1f;AI搜索时代品牌必须具备的5项核心能力导语&#xff1a;GEO发稿平台是2026年兴起的新型服务&#xff0c;核心是让品牌内容被豆包、DeepSeek等大模型引用2026年企业品牌传播环境发生了两个关键变化。第一&#xff0c;用户获取信息的入口从搜索引擎转…

作者头像 李华