Spring AI接入MCP协议:一行代码让Agent调用外部工具
发布时间:2026/10/14 10:41:25 锦皓数字建站

最近在搞一个 AI Agent 项目时碰到一个很现实的问题模型本身能力再强不接外部工具它就只是个嘴炮选手。我问它帮我读一下 /data 目录下最新的文件它只能回一句我无法访问您的文件系统。后来我把 MCP 协议接进 Spring AI加一个 starter 依赖、写几行配置模型就能调用文件系统、数据库、天气服务这些外部工具了。几百个工具一次接入听起来很夸张但这套协议干的就是这件事。这篇文章从协议原理讲到 Spring AI 的落地方式再给出一套能直接跑的示例、配置解析和我在实际项目中踩过的坑适合已经写过一点 Spring AI 基础代码、又想让 Agent 真正动手干活的朋友。1. MCP协议到底在解决什么问题1.1 没有MCP之前每接一个工具都要重新造轮子大模型的功能调用Function Calling / Tool Calling已经不新鲜了。但问题是每个工具提供方都有自己的接口风格有的给 REST 接口有的给 SDK有的直接让你传自定义的 JSON Schema。A 服务的天气接口参数叫 cityB 服务的数据库工具要求你先把 SQL 校验逻辑写在回调里C 服务的文件工具又要求你实现一套私有的事件接口。作为应用方每接一个工具就要写一套适配代码工具一多代码里全是胶水层和特殊判断。MCP 想解决的就是这个问题定义一套统一的协议让工具提供方按协议暴露能力让应用方按协议消费能力。可以类比成 USB 接口——设备厂商只要做 USB 接口电脑就不需要给每个外设定制一个专用插槽。MCP 之于 AI 应用扮演的就是这个统一插槽的角色。这里插一句个人理解MCP 里的工具不是传统意义上你写的工具类而是模型可以主动调用的一种任务能力。什么时候调用、传什么参数由模型自己决定应用层只需要把工具列表暴露给模型剩下的事交给模型编排。这个思维转变很关键否则你很容易把 MCP 理解成又一种 RPC 框架然后走偏。1.2 MCP的核心抽象工具、资源和提示词MCP 协议里有三个核心原语把这几个东西搞明白整篇文章后续都顺了。工具Tool模型可以主动调用的外部功能比如读取文件查询数据库发送 HTTP 请求。每个工具都包含名字、描述和一份 JSON Schema 参数定义。模型根据名字和描述决定要不要调用根据 Schema 生成参数。资源Resource可以提供给模型参考的静态数据比如文档片段、配置文件内容、图片元信息。资源通常由应用主动指定或用户选择而不是模型主动发现。提示词Prompt预定义好的提示模板可以组合工具和上下文比如帮我总结这个文件就是一个模板把模板里要填充的槽位定义清楚即可。从实战角度看工具是我们日常开发中最常打交道的原语资源次之提示词单独用的场景其实不多。Spring AI 的 MCP 集成也主要围绕工具展开。把 MCP 理解成一个中间人就对了MCP 服务器Server负责提供工具、执行调用MCP 客户端Client负责连服务器、发请求、拿结果你的 Spring AI 应用则是宿主Host宿主持有客户端客户端连接服务器。一条完整的调用链路长这样模型决定调用工具 → 应用把对话上下文和工具列表发给模型 → 模型返回工具调用指令 → 应用把指令交给 MCP 客户端 → 客户端按协议向服务器发起调用 → 服务器执行并返回结果 → 应用把结果回传给模型 → 模型根据结果生成最终回复。1.3 为什么这个协议能通用MCP 的通用性来自两点。第一传输层标准化支持 stdio 和 Streamable HTTP 两种方式。本地进程间通信用 stdio远程服务用 HTTP应用方不需要关系服务器在哪个进程、哪台机器。第二能力描述层直接用 JSON Schema大模型天然看得懂不需要额外训练就能理解工具用法。还有一个容易被忽略的点MCP 是语言无关的。不管服务器是用 Node.js、Python 还是 Java 写的只要实现了协议就能被任何宿主调用。这意味着你完全可以在 Spring AI 里接入一个 Python 生态的工具服务器也可以反过来把 Java 写的能力做成 MCP 服务器暴露给其他语言的 AI 应用用。这种跨语言互操作能力是自研协议很难短期内做到的。2. Spring AI里MCP是怎么落地的2.1 Spring AI的Tool Calling机制讲 MCP 集成前得先搞清楚 Spring AI 本身的工具调用机制。Spring AI 定义了一个ToolCallback接口凡是实现了这个接口的 Bean会被自动注册进模型调用时的工具列表。你平时写的Tool注解方法本质上也会被包装成ToolCallback。MCP 集成走的也是这条路MCP 客户端从服务器拉到的外部工具会被转换成 Spring AI 能识别的ToolCallback再注册到同一个工具注册表。站在模型的角度工具是本地 Java 方法还是 MCP 外部工具没有本质区别看到的都是名字、描述、参数 Schema 和可调用的回调。这个设计带来的一个直接好处是混用很自然。你可以在同一个 ChatClient 里同时暴露本地Tool方法和远程 MCP 工具模型自己根据描述去判断该用哪个。我实际用下来模型在工具描述清晰的情况下选对的概率远比你想象的高。2.2 自动装配帮你干了哪些事Spring AI 官方提供了 mcp-client 和 mcp-server 两个方向的 starter。你引入spring-ai-starter-mcp-client后Spring Boot 的自动装配会帮你做这些事读取配置文件中spring.ai.mcp.client.connections下声明的所有 MCP 服务器连接。根据每个连接的传输类型stdio 或 http创建对应类型的 MCP 客户端。启动时连接服务器、拉取工具列表。同步客户端在启动时完成这一步异步客户端在模型首次请求时才建立连接。把工具列表封装成ToolCallback注册进 Spring AI 的工具注册体系。管理客户端生命周期应用关闭时自动优雅断开连接。也就是说常规情况下你不需要手动创建 MCP 客户端对象只要写配置剩下的交给自动装配。一行代码接入几百个工具的实际含义就在这里核心工作就是加依赖和写配置业务代码几乎没增加。2.3 同步客户端与异步客户端的选择Spring AI 的 MCP 客户端支持SYNC和ASYNC两种连接类型分别对应阻塞式调用和响应式调用。我的选择原则很简单传统 Web MVC 项目接口基本都是同步的用SYNC最省心排错也直接。用了 WebFlux或者调用链路天然响应式用ASYNC避免阻塞事件循环线程。本地联调、跑 demo优先SYNC启动时就能验证连接是否真的通。配置里通过type字段指定。提醒一句如果注册了多个连接别把类型混得太乱。响应式和阻塞式混用技术上没事但调试的时候要多承担一层心智负担没必要。3. 一行代码接入几百个外部工具实操全程3.1 依赖引入真正的那一行代码先在 pom.xml 里加 MCP 客户端 starter这就是整个接入方案的入口dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency注意这个 starter 只负责 MCP 客户端的管理你还需要一个模型功能的 starter。这里以你实际使用的模型服务商为准引入对应的spring-ai-starter-model-xxx。两个依赖缺一不可模型 starter 提供聊天入口MCP starter 提供工具来源。这里必须提醒Spring AI 版本迭代很快MCP 相关包名和自动配置类在不同版本间有调整。建议直接用一个较新的稳定版本对照官方文档该版本的内容来配置。如果你照着老博客抄大概率遇到类找不到或者配置项不识别那不是你写错了是版本变了别浪费时间硬解。3.2 配置文件里声明MCP服务器依赖加好之后在 application.yml 里声明要连接的 MCP 服务器。先看一个同时接文件系统和数据库的例子spring: ai: mcp: client: connections: filesystem: type: SYNC transport: stdio stdio: command: npx args: [-y, mcp-server-filesystem, /tmp/data] database: type: SYNC transport: http http: url: http://localhost:8081/mcp headers: Authorization: Bearer your-token每段就是一个独立的 MCP 服务器连接。connections下的键名filesystem、database是连接名用来区分不同服务器也会作为 MCP 握手中的客户端标识。如果走 stdio 传输Spring AI 会在本机启动一个子进程通过标准输入输出和它通信。上面示例里的npx命令会临时拉取并运行一个 MCP 服务器程序这要求应用机器上有 Node.js 环境。本地开发用这套方案非常顺滑装好 Node、配好命令Spring Boot 启动后自动拉起子进程。如果走 http 传输要求远端已经有一个跑起来的 MCP 服务器直接填 URL 即可。生产环境我更推荐 http因为不需要在应用机器上跑子进程认证也更好做部署更干净。3.3 让模型开始使用工具配置齐了之后业务侧几乎不用写调用工具的代码。新建一个带 ChatClient 的服务类正常发消息就行Service public class AiAssistant { private final ChatClient chatClient; public AiAssistant(ChatClient.Builder builder) { this.chatClient builder.build(); } public String ask(String question) { return this.chatClient.prompt(question).call().content(); } }当模型判断需要读文件时它会返回一个工具调用请求Spring AI 自动找到对应的ToolCallback通过 MCP 客户端转发给服务器拿回结果后再让模型继续回答。从编码角度看确实只有一行 prompt 调用。但一行代码不等于不需要理解原理。实际项目里你往往要控制工具注册的时机和范围。比如某个场景不想让模型看到文件系统工具就不能单纯依赖全局自动注册而是手动控制工具暴露范围。Spring AI 支持在构建 ChatClient 时用.defaultTools()或.tools()指定工具集粒度可以做得很细。我建议把这个手段用好而不是把所有工具一股脑全注册进去。3.4 验证工具调用是否生效接入后怎么确认工具真的被调用了我的习惯是三步走。第一步看启动日志。MCP 客户端连接成功时一般会打印连接信息和工具数量。如果配置了文件系统服务器启动日志里能看到类似 Connected to MCP server 的输出以及注册的工具列表。第二步问一个只有调用工具才能回答的问题。比如接入了 /tmp/data 目录你就问/tmp/data 目录下有哪些文件如果模型直接回答我无法访问说明工具没注册上或者描述没被模型理解如果模型列出了文件名说明链路通了。第三步开 DEBUG 日志。把org.springframework.ai.mcp和org.springframework.ai.tool的日志级别调到 DEBUGlogging: level: org.springframework.ai.mcp: DEBUG org.springframework.ai.tool: DEBUG这时能看到模型发起的工具调用请求和服务器返回的原始结果。我遇到过一种情况模型确实返回了工具调用指令但执行结果没正确回传模型最后生成了我调用了工具但没拿到结果这种话。这种情况靠第二步发现不了必须开 DEBUG 日志看完整链路工具返回结果有没有完整拼进后续请求一目了然。4. 生产环境下的配置与调优4.1 超时、重试与连接管理MCP 工具调用是跨网络或跨进程的超时配置直接决定用户体验。你肯定不想看到模型好不容易生成了工具调用结果卡在连接上几十秒没反应。Spring AI 的 MCP 客户端里几个关键参数我列一下参数作用建议值request-timeout单次请求的等待时间10s ~ 30sconnect-timeout建立连接的超时5s ~ 10sread-timeout读取响应的超时10s ~ 30ssse-timeoutSSE 流式响应的超时按实际场景调整配置示例spring: ai: mcp: client: connections: filesystem: type: SYNC request-timeout: 20s transport: stdio stdio: command: npx args: [-y, mcp-server-filesystem, /tmp/data]一个经验之谈stdio 传输下子进程冷启动可能比 http 传输慢不少尤其是npx要现场拉包时首次调用超时概率偏高。生产环境能走 http 就走 http。如果实在要用 stdio建议在部署脚本里提前把 MCP 服务器依赖装好避免运行期下载依赖。重试方面MCP 客户端本身没有像 HTTP 客户端那样默认做丰富的重试我建议在消息路由层做兜底或者在调用外层设计简单重试策略。但重试必须谨慎工具调用不一定是幂等的发送订单创建请求这种工具重试一次很可能就多一条订单。重试前先确认工具语义是否幂等不幂等就别自动重试。4.2 多服务器接入与工具名冲突接几百个工具之后最先遇到的是工具名冲突。MCP 协议里工具名在同一个服务器内唯一但跨服务器并不保证。比如文件系统服务器有个read_file你自建的工具也叫read_file都注册进 Spring AI 之后模型看到两个同名工具行为就不可预测了。解决思路有两个方向。第一在工具命名层面统一规划加前缀区分比如fs_read_file、db_query让模型能看出区别。第二在 Spring AI 侧手动控制注册范围只暴露当前任务真正需要的工具子集用前面说的.tools()选择即可。工具数量太多还有一个隐性成本上下文窗口。每个工具的描述和参数 Schema 都会作为上下文的一部分发给模型几百个工具的元数据叠加可能占掉好几千 token。如果你的模型上下文窗口本身不大工具列表反而挤占业务上下文空间。我的建议很明确生产环境别把全部工具一次性暴露。按业务域拆分不同 Agent 挂不同工具组比如数据处理 Agent挂文件系统和数据库工具信息查询 Agent挂搜索和天气工具。既控制 token 消耗又减少模型选错工具的概率。4.3 安全与权限控制MCP 把让模型调用工具这件事变得太容易了安全就成了第一优先级。我见过有人直接把文件系统 MCP 服务器配上生产目录模型随口一句请求就能读写服务器文件。这个能力确实炫酷但也意味着任何能向这个 Agent 提问的人都可能间接操作你的服务器。几个必须做的基本动作MCP 服务器的访问要最小化授权只给 Agent 开它真正需要的目录、数据表或 API 权限。http 传输的连接必须认证比如在配置里加 Authorization 头服务器端校验身份。不能不加区分地信任工具返回值。工具结果会被模型加工成回答如果某个工具返回了不该出现的敏感内容模型很可能原文复述出来输出侧该做的过滤不能省。针对高危操作类工具删除、更新、转账等要么别让模型直接调用要么在工具实现层强制二次确认。别指望模型有判断能力它的判断本质是概率输出。多说一句MCP 协议本身不管鉴权鉴权是你和 MCP 服务器之间自己的事。Spring AI 只负责按协议通信不会替你承担任何安全决策。5. 踩坑记录与排查技巧速查5.1 最常见的三类问题把实际项目中遇到的 MCP 集成问题归成三类按出现频率排序。第一类启动失败连不上服务器。表现是应用启动报错日志里有连接超时或握手失败。stdio 场景下大概率是命令本身有问题——Node 没装、npx不存在、包名写错、路径不对。http 场景下大概率是 URL 写错或认证失败。排查方法很笨但有效先在命令行手动跑一次 MCP 服务器命令确认服务器本身能起来、能完成协议握手再去查应用配置。第二类应用启动成功但模型始终不调用工具。这种最让人头大因为错误不明显。原因通常是工具描述写得不清不楚模型不知道这工具能干啥或者参数 Schema 太复杂模型生成的参数老是不满足校验调用反复失败。解决办法是优化工具描述把触发场景写明确比如当用户询问天气时使用此工具参数尽量用简单类型避免层层嵌套。第三类调用链路通了但结果不对。比如文件读出来了模型却总结得七零八落数据库查到了模型却说没查到。这类大多是上下文传参问题工具执行结果没有被完整回传或者结果太长被截断。开 DEBUG 日志看全链路重点看工具返回结果是否完整进入下一轮模型请求。5.2 调试三板斧我调 MCP 集成问题固定是三板斧。第一板斧看启动日志。MCP 客户端启动时打印的信息量很大连接的服务器列表、工具注册数量、有没有跳过某个工具一眼可见。第二板斧开 DEBUG 日志。重点看org.springframework.ai.mcp和org.springframework.ai.tool两个包。前者能看到协议层收发内容后者能看到工具注册和调用前后的数据流转。第三板斧绕过 Spring AI用独立工具直接连 MCP 服务器手动构造 JSON-RPC 的tools/list和tools/call请求。这能帮你快速确认问题在服务器端还是 Spring AI 集成层。一次tools/list就能看到服务器真正暴露了哪些工具、描述是什么、Schema 长什么样很多模型不调用工具的问题在这里一看就明白了。5.3 一个印象深刻的案例做某个内部数据系统时我接了一个数据库 MCP 服务器。启动日志里工具注册成功模型也正常返回了调用指令但执行结果总不对。查了很久最后发现是数据查询工具的参数 Schema 里类型定义得比较宽模型生成参数时带了多余的引号服务器执行时把参数原样拼进查询逻辑结果全错。问题不在 MCP 协议而在工具实现方对参数没做类型校验和清洗。这个案例给我的教训是MCP 工具的参数传递不是模型输出什么服务器就得到什么中间是有损耗的。模型生成的参数可能多空格、多引号、类型不匹配工具实现侧必须做防御式校验不能假设输入永远是规范 JSON。这和写普通 Web 后端时不能信任前端传参是一个道理。6. 一些扩展玩法和经验6.1 自己写一个 MCP 服务器除了接入现成的 MCP 服务器你完全可以把项目里已有的 Java 能力暴露成 MCP 服务器给其他 AI 应用用。Spring AI 提供了spring-ai-starter-mcp-server配合Tool注解可以直接把 Service 方法变成 MCP 工具。这个方向很适合团队内部能力复用A 组维护业务系统把几个核心操作封装成 MCP 服务器B 组做 AI 应用不需要改一行业务代码通过 MCP 就能让模型使用 A 组的服务。比写一堆 HTTP 接口再手动封装成工具省事得多。6.2 从工具调用走向Agent编排MCP 解决的是工具接入真正的 Agent 编排逻辑先查数据库、再写文件、最后做总结依然需要你自己设计。Spring AI 的 ChatClient 链式调用、工具组合配合 MCP 外部工具可以搭出比较复杂的自动化流程。举个简单的例子一个周报生成 Agent可以让模型先调用文件系统工具读取本周日志再调用数据库工具统计任务完成数最后调用文档生成工具输出周报。整个链路里模型是调度中枢MCP 工具承担每一环的操作你只需要写好提示词和工具组合。6.3 我对MCP的几点判断MCP 非常适合模型需要跨系统操作的场景尤其是系统数量多、工具类型杂的情况。但如果你的场景只是调用两三个固定 API老老实实写Tool方法就够了没必要为了架构而架构。协议版本要盯紧。MCP 还在快速演进Spring AI 每发一个版本MCP 模块的配置格式、类名都可能调整。升级依赖前先看官方的版本变更说明别盲目升到最新。不要迷信工具越多越好。我见过一个演示项目接入二十个 MCP 服务器启动确实没问题模型却经常选错工具。工具一多描述之间的语义重叠也增多模型在模糊场景下就容易瞎猜。宁可工具少而精也要让每个工具的描述边界清晰。最后再分享一个小技巧如果 MCP 工具很多可以在提示词里给模型加一句优先使用最具体的工具不要凭空猜测参数不确定时先调用tools/list查看最新工具定义。虽然 MCP 本身会把工具定义发给模型但在多服务器场景下这句提示确实能让模型少犯低级错误实测下来对调用准确率提升很明显。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。