Spring AI 集成 MCP 全攻略:从 Server 到 Client 的工程实践
接手过一个内部数据分析助手最开始就是给 Spring AI 挂几个Tool方法让模型能查表、能算指标。功能上线后需求越来越多今天要接文件解析服务明天要接设计稿标注工具后天模型又得操作浏览器做页面巡检每次加能力都要改代码重新发布逼得我开始认真研究 Spring AI 里的 MCPModel Context Protocol。这篇文章就把我在 Spring AI 中操作 MCP 的完整过程写下来包括协议选型、依赖配置、Server 和 Client 两种角色怎么落地以及从官网示例能跑通到生产环境稳定用过程中踩过的坑。如果你正准备用 Spring AI 开发 Agent、做工具编排或者想把现有服务改造成标准 MCP Server 供 AI 调用这篇应该能帮你节约至少一个通宵。MCP 说白了就是把工具这件事标准化。在 Spring AI 出现之前每个 AI 应用接入外部工具基本是各自为战函数调用Function Calling是模型厂商定义的格式你接一个工具就要写一套转换逻辑MCP 则把AI 应用Host和工具提供方Server之间的交互固定成一套协议。你要是熟悉 USB-C 接口的思路就好理解了以前每个设备一个充电头现在一个接口通吃。MCP 就是 AI 世界里的那个统一接口Spring AI 对它的支持不是实验性插件而是在 ToolCalling 层做了原生适配这一点我在后面的实操里会反复提到。1. 想清楚再动手Spring AI 里 MCP 的定位与分工1.1 MCP 协议到底做了什么为什么 Spring AI 要内置MCP 的核心是两件事。第一把工具的描述、参数 Schema、调用结果用统一 JSON 结构表达模型侧不用关心工具背后是 Java 方法、Python 脚本还是一个远程 HTTP 接口第二定义了 Client也就是你的 Spring 应用和 Server 之间的传输方式——STDIO本地子进程、SSE服务端推送事件、Streamable HTTP可流式 HTTPSpring AI 1.0 版本后的默认选择新项目优先用它。Spring AI 从 1.0 M 系列开始就紧跟协议迭代把它抽象成 ToolCallingManager 的一部分。这意味着 MCP Server 上暴露的工具在你的应用里表现得跟本地Tool注解方法几乎一样模型能通过工具描述自动决定是否调用参数由框架按 JSON Schema 绑定返回值自动回传给模型。你不需要手写任何协议细节这是 Spring AI 和其他裸用 MCP SDK的方案最大的区别。1.2 MCP 和 Function Calling、RAG 的区别别混着上很多新手把 MCP 和 RAG 放在一起比较其实这是两个不同维度的东西。RAG 解决的是模型没有的知识怎么查进来本质是检索增强它让模型在生成前先拿一段上下文MCP 解决的是模型怎么操作外部系统本质是行动能力让模型去执行一次查询、写一条记录、触发一个构建任务。两者可以共存于同一个 AgentRAG 负责喂背景资料MCP 负责调用下游工具各自分工。Function Calling 和 MCP 的关系更微妙一点。Function Calling 是模型厂商定义的模型如何描述和请求调用本地函数的约定你可以理解为模型侧的输入输出规范MCP 是应用与工具提供方之间的通信协议。在 Spring AI 里你完全可以只写Tool方法让模型调用这属于单应用内闭环但当工具方是独立部署的服务、进程、甚至第三方提供的工具时MCP 的价值才真正体现出来——它让工具注册、发现、调用变成了运行时行为而不是编译期写死。1.3 分清两种角色你是 Server 还是 Client这是我在项目里见过最多的认知混淆。Spring AI 的 MCP 支持分两条线MCP Server把你的 Spring Boot 应用变成工具提供方通过标准端点把Tool方法暴露出去其他 AI 应用Cursor、Claude Desktop甚至另一个 Spring AI 应用都能连接过来调用。MCP Client你的 Spring AI 应用作为消费者连接外部 MCP Server把对方的工具注册到模型可调用列表里。一个项目里经常两种角色并存。比如我那个数据分析助手对内部暴露了一组数据查询工具Server 角色同时又去连接了设计稿标注工具的 MCP ServerClient 角色让模型能看图取标注值。先想清楚当前需求是哪条线再去配依赖不然很容易在配置文件里绕晕。2. 环境准备与依赖引入版本和依赖比想象中更挑2.1 版本选型Spring Boot、Spring AI、MCP 协议版本要联动操作 MCP 第一个坑永远是版本。MCP 协议本身还在快速迭代Spring AI 对协议版本也有最低要求。我目前的推荐组合是Spring Boot 3.3.x 或 3.4.x Spring AI 1.0.0 GA 及以上对应 MCP Java SDK 1.0.x。如果你还在用 Spring AI 0.8.x 之前的版本直接上 MCP 会遇到大量 API 不兼容尤其是ToolCallingManager相关的配置类名、Bean 装配方式都变了网上老教程照抄会报一堆ClassNotFoundException。另外要注意 Spring AI 官方提供了 BOMBill of Materials来统一管理相关依赖版本。强烈建议引入spring-ai-bom这样spring-ai-starter-mcp-server、spring-ai-starter-mcp-client这些模块的版本不用手动去对齐我早期就是没加 BOM结果 model starter 和 mcp starter 走了两个不同小版本MCP Client 连不上 Server排查了一下午才发现是这个问题。2.2 Maven 依赖一个最小可跑通的组合我拿一个接智谱 AI 模型的工程举例因为不少人搜spring ai maven 智谱ai version就是想找这套组合。注意智谱 AI 的 starter 坐标现在是spring-ai-starter-model-zhipuai如果你用的是阿里系那套spring-ai-alibaba生态则换成spring-ai-starter-alibaba对应 dashscope 模型结构类似。最小依赖如下dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-zhipuai/artifactId /dependency !-- 如果要做 MCP Server -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId /dependency !-- 如果要做 MCP Client -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency /dependencies如果你用的是 WebFlux 响应式栈则要额外确认 mcp server 的 webflux 模块是否被自动装配。Spring AI 的 mcp server starter 默认会按你项目里的 Web 框架选择适配器但如果你混用了 MVC 和 WebFlux 的依赖会出现端点映射冲突这点放到第 5 节排查里细说。2.3 配置文件先把开关和端点搞清楚不管你是 Server 还是 ClientSpring AI 都要求显式开启 MCP 支持默认不是全开的。一个典型的application.yml长这样spring: ai: model: zhipuai: api-key: ${ZHIPU_API_KEY} chat: options: model: glm-4-plus mcp: server: enabled: true name: demo-mcp-server version: 1.0.0 type: STREAMABLE_HTTP client: enabled: true # 连接外部 server 的配置见第 4 节这里有个细节spring.ai.mcp.server.type有STREAMABLE_HTTP、HTTP、SSE、STDIO四种。WebMVC 项目在 Spring AI 1.0 里默认是SSE但新协议更推荐STREAMABLE_HTTP因为它在响应式传输和兼容性上更好而且很多第三方客户端只认STREAMABLE_HTTP。我测试下来如果只是内部工具SSE也能跑但接 Cursor 这类外部宿主时STREAMABLE_HTTP的成功率明显高。配完依赖和开关后启动应用观察日志里是否有 MCP 端点信息。Spring AI 的 MCP Server 会自动暴露/mcp路径不同传输方式路径略有差异能看到MCP server started之类的日志就说明 Server 角色基本就绪。3. 实操把 Spring Boot 服务改造成 MCP Server3.1 用 Tool 把方法暴露成 MCP 工具Spring AI 里最爽的一点就是你在本地写 Function Calling 时用的Tool注解在 MCP Server 模式下直接复用。我先写一个查内部订单数据的工具Component public class OrderTools { Tool(description 根据订单号查询订单状态与金额仅支持内部订单系统) public OrderInfo queryOrder(String orderId) { // 实际逻辑查数据库或者调内部接口 return orderService.findByOrderId(orderId); } }就这么简单。启动后Spring AI 会自动扫描容器里的Tool方法通过McpToolUtils把方法签名转换成 JSON Schema 描述注册到 MCP Server 的工具列表里。我最开始还担心要手写ToolParam去标注每个参数后来发现只有需要细化参数描述时才用得上Tool(description 查询指定时间范围内的销售汇总) public SalesSummary querySales( ToolParam(description 开始日期格式 yyyy-MM-dd) String startDate, ToolParam(description 结束日期格式 yyyy-MM-dd) String endDate) { // ... }ToolParam的 description 不是可选项模型靠它理解参数含义。我踩过的一个很典型的坑某个时间参数不写 description模型经常把yyyy-MM-dd和timestamp两种格式混着传后来补上描述并加了示例值调用准确率明显上升。3.2 Server 的传输配置与端点暴露细节如果你的应用里同时跑着业务接口和 MCP 端点要特别关注请求路径和过滤器。Spring AI 的 MCP Server 端点默认注册在/mcpSTREAMABLE_HTTP模式下协议比较复杂包含初始化握手、工具列表拉取、工具调用等多个子端点但都被框架封装好了你在代码里不需要单独处理。真正需要留意的是鉴权。MCP Server 端点在默认配置下是裸奔的谁拿到地址就能拉取你的工具列表并调用。内部工具还好如果暴露到公网必须加一层鉴权。我用的是在网关层拦截/mcp路径做 token 校验或者给端点配置 Spring SecurityBean public SecurityFilterChain mcpSecurityFilterChain(HttpSecurity http) throws Exception { http .securityMatcher(/mcp/**) .authorizeHttpRequests(auth - auth .requestMatchers(/mcp/health).permitAll() .anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()); return http.build(); }这里有个实际经验MCP 握手阶段会有一些预检请求和状态查询如果鉴权过滤器太严格会把正常的初始化流程挡在外面导致客户端日志显示连接成功但工具列表永远是空的。我给/mcp路径单独配了 Security 规则不跟正常业务接口混用才算稳定下来。3.3 本地验证 Server用支持 MCP 的客户端连一次写完 Server 后别急着接业务先做连通性验证。最省事的办法是用支持 MCP 配置的 AI 客户端工具比如 Cursor 里打开 MCP 设置或者用 Spring AI 自带的 MCP Client 调试连一下http://localhost:8080/mcp看工具列表里有没有queryOrder和querySales。我习惯再加一步先用 curl 探活。注意STREAMABLE_HTTP端点不是简单 GET 一下就能确认的正确的探活方式是发送一个 MCP 协议的初始化 JSON-RPC 请求。本地快速验证可以用社区常用的 MCP Inspector 工具把 transport 选为 Streamable HTTP填入端点地址就能看到完整的工具调用过程——这个方法在我后来排查参数绑定问题时帮了大忙。如果只想确认 Server 进程活着看日志里的启动标志就足够不需要过深纠结协议细节。我也试过用普通 HTTP 请求模拟发现STREAMABLE_HTTP的响应格式是流式的直接拿 POST 工具请求可能要处理 SSE 分帧内容不如直接用 MCP Inspector 顺手。这块工具选型的经验是能用现成 MCP 调试器就别自己写客户端省下来的时间拿去处理真正的业务逻辑更有价值。4. 实操Spring AI 作为 MCP Client 接入外部 Server4.1 在配置里声明要连接的 MCP Server 列表Client 角色的核心配置是spring.ai.mcp.client下的 server 列表。Spring AI 支持同时连接多个 MCP Server每个 server 有名字、传输类型、地址或命令。我用一个实际例子说明接入社区里很常见的本地文件 MCP Server和通过 npx 启动的 Node 类 Server。spring: ai: mcp: client: enabled: true servers: file-server: type: STREAMABLE_HTTP url: http://localhost:9000/mcp playwright-server: type: STDIO command: npx args: [-y, playwright/mcplatest]STDIO类型的 server 实际上是让 Spring AI 在本地拉起一个子进程通过标准输入输出来通信。这里我最开始犯过一个错误以为command直接写npx playwright/mcplatest就行后来发现框架是把 command 和 args 拆开传的必须按上面这种格式配否则子进程起不来日志里全是Cannot run program npx之类的报错。多个 server 的名字file-server、playwright-server会被用作工具命名的前缀。比如playwright-server里的浏览器启动工具映射到模型那边就是playwright-server_browser_start这种格式方便模型区分同名工具来自哪个 server。如果你的多个 server 有同名工具这个前缀机制能避免冲突但也会让工具名很长模型偶尔会因为名字太长而忽略工具实践中尽量给 server 起短名。4.2 在代码里创建 ToolCallingManager 并绑定 ChatClient配好 yml 后Spring AI 的自动配置会准备好 MCP Client 相关的 Bean但你还需要一步手动操作把 MCP 工具注入到 ToolCallingManager 里再和 ChatClient 绑起来。这里的关键类是McpToolUtils——它负责从 MCP Client 拉取工具列表并转换成 Spring AI 的工具定义。Service public class McpAgentService { private final ChatClient chatClient; public McpAgentService(ToolCallingManager toolCallingManager, ListMcpClient mcpClients) { // 从所有已配置的 MCP Client 拉取工具注册到 ToolCallingManager ListToolCallback toolCallbacks McpToolUtils.getToolCallbacksFromMcpClients( mcpClients, toolCallingManager, 300); this.chatClient ChatClient.builder(new ZhipuAiChatModel(...)) .defaultTools(toolCallbacks.toArray(new ToolCallback[0])) .build(); } public String chat(String userMessage) { return chatClient.prompt(userMessage) .call() .content(); } }我每次接入新 server 都会先打印一下toolCallbacks的数量肉眼确认工具真的被注册进来了。因为踩过一次配置了 server 但工具列表为空的坑原因是 yml 里spring.ai.mcp.client.enabled忘开自动配置没创建 Client Bean代码里拿到的mcpClients是空列表工具自然就没了。如果你用的是ChatClient的流式接口在 Spring AI 1.0 里有ReactiveMcpClient和响应式 API 可用但核心绑定思路一致。客户端角色不需要关心 MCP 协议细节所有工具调用都会被框架转成模型侧的 function call再走 MCP 通道执行。4.3 真实场景连接设计稿标注工具、浏览器自动化工具这部分说点实战体验。很多团队会连蓝湖Lanhu的 MCP、Figma 的 MCP 来让模型直接读设计稿标注值或者连 Playwright 的 MCP 让模型做页面巡检。以 Figma MCP 为例官方或社区的 MCP Server 通常会提供一个图标的访问入口给模型过程是模型调用读取节点 → Server 返回 JSON 结构 → 模型再调用取具体图层标注。这类外接 Server 最典型的问题不是连不上而是模型不知道工具的调用成本。比如浏览器自动化工具一次页面截图或一次点击可能耗时几秒甚至十几秒模型如果在一个任务里频繁调用整个 Agent 的响应时间会很难看。我在实际项目里做了一层工具护栏通过Tool包装一次 MCP 调用在包装层加限流和超时控制或者干脆在 prompt 里明确告诉模型浏览器操作成本高优先用本地数据工具。这种编排思路比单纯堆 MCP Server 更实用。还有一点外接 Server 的稳定性你控制不了像npx启动的临时 Server首次拉取依赖可能要几十秒Spring AI 的调用超时默认值不一定够。我建议在 yml 或代码里把 MCP 调用的超时调大一点具体参数因版本略有差异一般都可以在McpClient的McpClientProperties里找到requestTimeout之类的选项。超时太小STDIO类型的 server 第一次冷启动时非常容易超时失败。4.4 同步与异步、阻塞与响应式新手容易忽略的线程问题Spring AI 的 MCP Client 有同步和响应式两套 API。默认的同步McpClient底层是阻塞调用如果你的工具里还要嵌套调另一个同步服务线程很容易被占满。我在一个高并发场景里就吃过亏Agent 服务挂在 Web 请求线程里模型一次会话连调三个 MCP 工具每个工具内部又同步等下游接口结果压测时线程池直接打满。我的做法是对外接口保持响应式或异步MCP 调用走响应式 API如果团队对响应式不熟至少把Tool包装方法里加线程池隔离避免阻塞默认的业务线程。Spring AI 官方文档里对同步/异步 MCP Client 的说明比较分散实际操作时记住一个原则能响应式就响应式不能就隔离线程池千万别直接在 Netty 事件循环线程里调同步 MCP。5. 常见问题与排查技巧实录5.1 工具列表为空或连接成功但调用报错这类问题占了我排障时间的一半以上。排查顺序固定是先确认 MCP Server 进程活着并打印工具列表 → 再确认 Client 端的 Bean 被创建 → 再确认 ToolCallingManager 拿到了工具。经验做法是加一条启动日志在ApplicationRunner里打印所有已注册的 ToolCallback 名称。如果工具列表是 0大概率是spring.ai.mcp.client.enabled没开或者 yml 里 server 配置没被加载——常见原因是使用了spring.config.import方式导入外部配置但 MCP server 配置没有跟着被加载。如果工具列表有值但调用时报错多半是协议版本不匹配看服务端日志里有没有Unsupported protocol version之类的关键字然后统一升级 Spring AI 版本和 MCP SDK 版本。5.2 版本冲突与依赖乱象Spring AI 的生态更新快第三方依赖很容易打架。我遇到过一个很隐蔽的问题项目里本来有 fastjson 相关依赖MCP SDK 通过 Jackson 做序列化在某个特定版本组合下JSON Schema 生成时把ToolParam的 description 丢了模型看不到参数说明工具调用质量直线下降。后来把 Jackson 统一到 Spring Boot 管理的版本问题消失。不要迷信加依赖越多越好。MCP Server starter 和 MCP Client starter 只有在两种角色都需要时才同时引入只做 Client 就别带 server starter避免自动配置互相干扰。我可以确定地说至少两次线上事故都是因为多余依赖触发自动配置导致/mcp端点被意外暴露。5.3STDIO子进程启动失败与路径问题Windows 和 Linux 上踩的坑还不一样。Windows 上npx实际上是npx.cmd框架直接用npx启动会报找不到程序需要把 command 配成npx.cmd而且对 args 的解析规则跟 Linux 有差异。Linux 服务器上常见的是 Node 版本太低MCP Server 要求 Node 18子进程一启动就崩溃但 Spring 应用日志里只显示一行子进程非零退出非常难定位。我的建议是在本地先用命令行手动执行一遍npx -y playwright/mcplatest确认能正常启动再交给框架。如果命令本身能跑但框架拉起失败加一个启动配置把 MCP Client 的子进程输出重定向到日志文件这样能看到子进程的真实报错。5.4 数据格式与参数绑定问题MCP 工具的参数绑定依赖 JSON Schema跟 Java 类型的映射偶尔会出问题。最容易踩的是日期类型、枚举类型和大数类型。日期参数建议在ToolParam描述里写清楚格式并配合DateTimeFormat或自定义 converter枚举参数建议 description 里列出允许值模型经常会把枚举值大小写传错导致服务端反序列化失败。返回值的序列化也有讲究。MCP 工具返回值最终要被模型重新理解如果你返回一个特别复杂的嵌套对象模型不一定能正确提取关键字段。我后来统一让工具方法返回简单 DTO 或 JSON 字符串并且在Tooldescription 里写明返回结构模型的理解准确度提升明显。5.5 常见问题速查表现象最可能原因处理建议工具列表为空MCP Client 未启用 / server 未加载打印 ToolCallback 列表检查spring.ai.mcp.client.enabled连接报 Unsupported protocol协议版本不匹配统一升级 spring-ai-bom 与 mcp sdk 版本npx 起不来Windows 下命令名差异 / Node 版本低command 改为npx.cmd手动验证子进程可启动调用超时工具冷启动慢 / 超时配置过小调大 requestTimeout必要时预热工具参数频繁传错ToolParam 描述缺失补全描述、枚举值、格式示例线程池打满同步 MCP 占用请求线程隔离线程池或改用响应式 API工具名太长被忽略server 命名过长使用短名称并避免特殊字符6. 一个老实践者的建议操作 Spring AI 的 MCP 到现在我最深的感觉是MCP 本身不复杂复杂的是工程化落地。如果你只是演示用官方示例的配置就够了但如果要进生产一定要在工具调用的超时、鉴权、日志、线程模型这几个维度做加固。MCP 给你的是统一接入能力不是稳定运行能力后者还是得靠自己的架构兜底。我个人在实际操作中的体会是小步验证比大规划更靠谱。先选一个小工具走通全链路把 Server/Client 的角色边界、版本选型、日志排查手段都验证清楚再逐步扩大接入范围。这样既能看到 MCP 带来的明显收益——新增工具不用改代码、不用发版只要配置一个 server 地址——也能避免一步到位时排障无从下手。最后分享一个小技巧给 MCP 相关配置单独开一个 profile比如application-mcp.yml里面只放 server 列表和开关。这样排查问题时可以快速开关某个 server 做隔离测试生产环境想临时下线某个工具改一行配置重启就完事不用碰业务代码。Spring AI 的 MCP 生态还在快速发展配置项和 API 在不同版本之间会有调整但先想清角色、再配依赖、最后用日志验证这套思路无论版本怎么变都不会过时。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →