
1. 为什么要把 Spring Boot 服务塞进 AI 的“工具箱”第一次听到 MCP 这个词很多人会下意识觉得又是哪个厂商造的新概念。但如果你手上正好有一堆跑了好几年的 Spring Boot 服务里面封装着订单查询、库存校验、报表生成、权限判断这些实打实的业务能力那你大概率会冒出一个念头能不能让 AI 直接调用这些接口而不是每次都要人肉复制粘贴参数、再把结果贴回对话框这个念头就是 MCP 要解决的核心问题。MCP 全称 Model Context Protocol直译过来是“模型上下文协议”。你可以把它理解成 AI 应用和外部能力之间的一份“插座标准”。以前每个 AI 客户端想接你的服务都得单独写一套适配代码现在只要你的服务按 MCP 的规范暴露能力任何支持这个协议的 AI 应用都能即插即用。我拿一个真实场景来说明它的价值。假设你有一个 Spring Boot 写的内部工单系统提供“按工单号查详情”“按状态统计数量”“批量导出周报”三个接口。没有 MCP 之前运营同学想让 AI 帮忙分析工单只能自己登录系统、导出 Excel、再上传给 AI。有了 MCP 之后AI 应用可以直接调用这三个接口用户一句“帮我看看本周未关闭的工单里有没有超期的”AI 自己就去查了。这篇文章适合三类人看一是手里有 Spring Boot 项目、想让 AI 用起来的后端开发二是正在做 AI 应用、需要接外部业务系统的工程师三是对 MCP 好奇但还没动手试过的技术负责人。我会从协议原理讲到代码落地把“为什么这么设计”和“实际怎么跑通”都讲透中间踩过的坑也会一并交代。需要先说明一点MCP 本身还在快速演进不同 AI 客户端对它的支持程度不一样。我下面讲的是基于当前主流实现方式的通用做法具体到某个客户端可能会有细微差异但核心逻辑是通的。2. MCP 到底在解决什么从“人肉搬运”到“协议对接”2.1 没有 MCP 之前AI 接外部服务有多别扭在 MCP 出现之前让 AI 调用外部服务主要有几种土办法。第一种是“提示词里塞接口文档”把 API 的 URL、参数格式、返回示例全写进系统提示词让模型自己生成 HTTP 请求。这种做法的问题非常明显接口一多提示词就爆炸参数稍微复杂模型就编错而且没有任何类型校验错了也不知道错在哪。第二种是“函数调用”也就是 Function Calling。这个方案比纯提示词进步很多模型会按你定义的 JSON Schema 输出结构化参数。但它有个致命短板每个 AI 平台定义的函数格式都不一样。你在 A 平台写好的函数定义搬到 B 平台就得重写一遍。如果你的服务要被多个 AI 应用使用维护成本会成倍上升。第三种是“自己写中间层”针对每个 AI 客户端做适配。这本质上是在重复造轮子而且每接一个新客户端就要改一次代码。我见过一个团队为了接三个不同的 AI 工具硬是维护了三套适配逻辑后来接口一改三套全挂。MCP 的思路是把“能力描述”和“能力调用”标准化。你的 Spring Boot 服务只需要按 MCP 规范暴露一次所有支持该协议的客户端都能发现并调用这些能力。这就像 USB 接口统一了外设连接方式你不需要为每个电脑单独做一个鼠标接口。2.2 MCP 的三个核心角色Host、Client、Server理解 MCP 的关键是搞清楚它里面的三个角色我用一个生活化的类比来解释。Host 是“宿主”也就是用户直接交互的 AI 应用比如某个桌面 AI 助手、某个 IDE 里的编程助手。它相当于一个“总调度中心”负责和用户对话、决定什么时候需要调用外部能力。Client 是“客户端”它由 Host 创建和管理专门负责和某一个 Server 建立连接、发送请求、接收结果。你可以把它理解成 Host 派出去的“联络员”一个 Host 可以同时管理多个 Client每个 Client 对接一个 Server。Server 是“服务端”也就是我们这篇文章的主角——把 Spring Boot 服务包装成的 MCP 服务。它对外声明“我能做哪些事”并在被调用时执行真正的业务逻辑。Server 相当于一个“专业窗口”只负责自己那一摊事不关心是谁来调用。这三个角色的关系是Host 根据用户需求通过 Client 向 Server 发起调用Server 执行完把结果原路返回。整个过程中Host 不需要知道 Server 是用 Java 写的还是 Python 写的只需要知道它声明了哪些能力。2.3 为什么是 Spring Boot 服务最适合做 MCP Server有人可能会问MCP Server 用什么语言写不行为什么偏偏要拿 Spring Boot 服务来做这里有几个很实际的原因。第一绝大多数企业的核心业务逻辑都在 Spring Boot 里。订单、库存、审批、报表这些能力不是凭空冒出来的它们已经在 Java 服务里跑了很久经过了充分测试。把这些现成能力暴露成 MCP比用别的语言重写一遍要靠谱得多。第二Spring Boot 的生态成熟做 HTTP 服务、参数校验、异常处理、日志记录都有现成方案。MCP Server 本质上也是一个服务需要处理并发、需要做鉴权、需要记日志这些 Spring Boot 都帮你解决好了。第三Java 的强类型特性在定义工具参数时是优势。MCP 要求 Server 声明每个工具的参数结构Java 的类定义天然适合做这件事配合注解可以自动生成参数描述减少手写出错。第四团队维护成本低。如果团队本来就是 Java 技术栈用 Spring Boot 做 MCP Server 不需要引入新的语言和构建体系现有开发人员直接就能上手。3. 动手之前把 MCP 的核心概念翻译成 Java 能听懂的话3.1 Tools、Resources、Prompts 三件套分别对应什么MCP 协议里 Server 可以暴露三类东西Tools、Resources、Prompts。这三个词听起来抽象我用 Spring Boot 的视角给你翻译一下。Tools 是“可执行的操作”对应到 Spring Boot 里就是那些会改变状态或执行计算的接口比如“创建工单”“计算折扣”“发送通知”。AI 调用 Tool 时Server 会真正执行逻辑并返回结果。这是最常用的一类也是我们重点要实现的。Resources 是“可读取的数据”对应到 Spring Boot 里就是那些只读的查询接口比如“获取配置”“读取日志”“查询字典”。Resources 和 Tools 的区别在于Resources 是幂等的、只读的AI 可以放心地反复读取而不用担心副作用。Prompts 是“预定义的提示模板”对应到 Spring Boot 里可以理解成“封装好的操作流程”。比如“生成周报”这个 Prompt 可能内部会依次调用查询工单、统计数量、格式化输出三个 Tool。Prompts 让 AI 可以一键执行复杂流程而不需要用户一步步描述。对于大多数 Spring Boot 服务来说我建议先从 Tools 入手把最核心的几个业务能力暴露出来。Resources 适合暴露那些查询类接口Prompts 可以等前两者跑通后再考虑。3.2 传输方式怎么选stdio 还是 HTTPMCP 支持多种传输方式最常见的是 stdio 和 HTTP 两种。这个选择直接影响你的部署方式必须一开始就想清楚。stdio 方式是 Server 作为一个子进程运行通过标准输入输出和 Client 通信。它的优点是简单、无需网络配置、天然隔离缺点是 Server 必须和 Client 在同一台机器上而且一个 Server 进程通常只服务一个 Client。这种方式适合本地工具类的场景比如 IDE 里的编程助手调用本地脚本。HTTP 方式包括 SSE 和 Streamable HTTP是 Server 作为独立的网络服务运行Client 通过网络调用。它的优点是 Server 可以独立部署、多个 Client 共享、方便做鉴权和限流缺点是需要处理网络问题、需要配置端口和证书。对于 Spring Boot 服务来说HTTP 方式几乎是必然选择因为你的服务本来就是网络服务没必要退化成子进程。我实测下来的建议是如果你的 Spring Boot 服务已经是一个独立部署的 Web 服务直接用 HTTP 传输方式把它当成一个普通的 REST 服务来对待。stdio 方式更适合那些一次性的、本地的脚本工具不太适合企业级服务。3.3 工具描述写得好不好直接决定 AI 会不会用这是最容易被忽视、但实际影响最大的一点。MCP Server 暴露的每个 Tool 都需要一段描述告诉 AI 这个工具是干什么的、什么时候该用、参数怎么填。这段描述写得好不好直接决定 AI 能不能正确调用。我见过太多人把工具描述写成“查询工单”然后抱怨 AI 老是调错。问题就出在描述太模糊。好的工具描述应该包含三个要素这个工具做什么、什么场景下用、参数有什么约束。举个例子同样是查询工单差的描述是“查询工单信息”好的描述是“根据工单号查询单个工单的详细信息包括状态、负责人、创建时间。适用于用户提供了明确工单号的场景。工单号格式为字母加数字长度 10 位”。后者明确告诉 AI 了使用场景和参数格式调用准确率会高很多。参数描述同样重要。每个参数都要说明它的含义、格式、是否必填、取值范围。如果参数有枚举值一定要列出来。这些信息会直接影响 AI 生成参数的质量。4. 从零搭建Spring Boot 接入 MCP 的完整实操4.1 依赖选型与项目骨架目前 Java 生态里做 MCP Server 主要有两种路径一是用官方或社区提供的 MCP Java SDK二是自己按协议手写 JSON-RPC 处理。我建议优先用 SDK因为协议细节比较多手写容易漏掉边界情况。在pom.xml里引入 MCP 相关的依赖通常包括核心协议库和 Spring Boot 的集成模块。具体坐标会随版本变化你需要去对应的仓库确认最新版本。引入依赖后项目骨架大致是这样的一个配置类负责注册 MCP Server 和工具若干个工具类分别封装具体的业务能力一个启动类把服务跑起来。这里有个容易踩的坑MCP SDK 的版本和 Spring Boot 的版本要匹配。我遇到过 SDK 依赖了某个较新的 Jackson 版本而项目里锁定了旧版本结果启动时报序列化相关的错。解决办法是在dependencyManagement里统一 Jackson 版本或者用mvn dependency:tree排查冲突。项目结构我建议按职责分层config包放 MCP 配置tools包放各个工具实现service包放原有的业务逻辑。工具类只负责参数转换和结果封装真正的业务逻辑还是调用原有的 Service这样不会污染现有代码。4.2 把第一个业务接口包装成 Tool假设我们有一个现成的TicketService里面有个getTicketById方法。要把它包装成 MCP Tool核心工作是三件事声明工具元信息、定义参数结构、实现调用逻辑。声明工具元信息时需要给工具起一个清晰的名字建议用英文小写下划线比如get_ticket_detail写一段前面说的那种详细描述并指定参数结构。参数结构可以用一个 Java 类来定义字段上加注解说明含义和约束。实现调用逻辑时工具方法接收参数对象调用TicketService把返回结果转换成 MCP 能识别的格式。这里要注意异常处理如果业务抛异常要捕获后返回一个结构化的错误信息而不是让异常直接冒泡。AI 拿到清晰的错误信息才能决定下一步怎么做。我建议第一个工具选最简单的查询接口来试不要一上来就搞复杂的写操作。查询接口没有副作用调错了也不会造成数据问题适合用来验证整条链路是否通畅。4.3 参数校验与类型映射的细节MCP 的参数是通过 JSON 传递的而 Java 是强类型的中间的类型映射有很多细节要注意。数字类型要特别小心。JSON 里的数字可能是整数也可能是小数如果你的 Java 字段是int但 AI 传了个3.0反序列化就可能出问题。我的做法是能用Integer就不用int能用BigDecimal处理金额就不用double。对于明确是整数的参数在描述里写清楚“必须是整数”。日期时间类型也是重灾区。AI 生成的日期格式五花八门有2024-01-01的有2024/01/01的还有带时区的。稳妥的做法是在参数描述里明确规定格式比如“日期格式为 yyyy-MM-dd”然后在工具方法里做一次格式校验和转换不合法就返回明确的错误提示。枚举类型要列出所有可选值。如果某个参数只能是“待处理、处理中、已完成”三个值之一一定要在描述里写全否则 AI 可能编出一个“进行中”这种不存在的值。必填和选填要区分清楚。必填参数缺失时要返回“缺少必填参数 X”这样的明确提示让 AI 知道该补什么。选填参数要有合理的默认值并在描述里说明默认行为。4.4 本地跑通与调试的实用手法代码写完后怎么验证它真的能用我总结了一套调试流程。第一步是单元测试。不依赖任何 AI 客户端直接调用工具方法传入各种正常和异常参数看返回结果是否符合预期。这一步能排除掉大部分逻辑错误。第二步是用 MCP 官方的调试工具或支持 MCP 的客户端手动连接。连接成功后先看工具列表能不能正确拉取工具描述和参数结构显示是否正常。然后手动触发一次调用观察请求和响应的完整内容。第三步是看日志。MCP 的通信是基于 JSON-RPC 的把请求和响应的原始 JSON 打出来能快速定位是参数问题还是返回格式问题。我习惯在开发阶段把日志级别调到 DEBUG把每次调用的入参和出参都记下来。这里有个实用技巧准备一组“标准测试用例”包含正常调用、参数缺失、参数格式错误、业务异常等场景。每次改完代码都跑一遍这组用例能快速发现回归问题。5. 让 AI 真正会用工具描述与调用体验的打磨5.1 工具粒度太粗和太细都不行工具粒度是个需要反复权衡的问题。粒度太粗一个工具干太多事AI 很难准确填参数粒度太细工具数量爆炸AI 选择困难。我的经验法则是一个工具对应一个明确的业务动作。比如“查询工单”和“关闭工单”应该是两个工具而不是一个“工单操作”工具带个 action 参数。因为这两个动作的参数结构、使用场景、风险等级都不同混在一起会让 AI 困惑。但也不要细到把“查询工单状态”和“查询工单详情”拆成两个工具。如果它们返回的数据高度重叠合并成一个工具、用参数控制返回字段的详细程度会更合理。判断粒度是否合适有个简单的测试把工具列表给一个不熟悉系统的人看如果他能在不看文档的情况下说出每个工具什么时候用那粒度就差不多了。5.2 返回值设计给 AI 看的不是给人看的工具返回值的设计原则和普通 API 很不一样。普通 API 的返回值是给前端程序解析的而 MCP 工具的返回值是给 AI 理解并转述给用户的。这意味着返回值要“自解释”。我建议返回值包含三部分状态标识、核心数据、人类可读的摘要。状态标识让 AI 知道调用成功还是失败核心数据是结构化的业务数据方便 AI 做后续处理人类可读的摘要可以直接展示给用户减少 AI 的转述负担。举个例子查询工单的返回可以是这样状态是 success核心数据里包含工单号、状态、负责人、创建时间等字段摘要是一句“工单 TK12345 当前状态为处理中负责人是张三创建于 2024 年 1 月 1 日”。这样 AI 既拿到了结构化数据又有一句现成的话可以回复用户。错误返回同样要清晰。不要只返回“操作失败”而要说明失败原因和可能的解决办法比如“工单号 TK99999 不存在请确认工单号是否正确”。5.3 多工具协作时的上下文传递当 AI 需要连续调用多个工具完成一个任务时上下文传递就成了关键。比如用户说“把上周未关闭的工单都导出成报表”AI 可能需要先调用“查询工单列表”再调用“生成报表”最后调用“保存文件”。这里的问题是第一个工具的返回结果怎么传给第二个工具MCP 本身不负责维护这种跨工具的上下文需要靠 AI 自己从上一个结果里提取信息填到下一个调用里。所以工具返回的数据结构要尽量扁平、字段命名要清晰方便 AI 提取。我的做法是对于经常需要串联的工具在返回结果里额外放一些“便于后续调用”的字段。比如查询工单列表时除了返回工单详情还返回一个工单号数组这样 AI 调用批量操作工具时可以直接用这个数组。另外工具描述里可以提示常见的调用顺序。比如在“生成报表”工具的描述里写“通常需要先调用查询工单列表获取数据”帮助 AI 建立正确的调用链路。6. 上线前必须想清楚的安全与稳定性问题6.1 鉴权别让 MCP 成为后门把 Spring Boot 服务暴露成 MCP 之后它就成了一个对 AI 开放的能力入口。如果不做鉴权任何能连上这个服务的 AI 客户端都能调用你的业务接口这是非常危险的。鉴权的第一层是传输层鉴权。如果走 HTTP 传输至少要加 Token 校验每个 Client 连接时带上约定的 TokenServer 验证通过才允许调用。Token 要定期轮换不要硬编码在代码里。第二层是工具级鉴权。不是所有工具都对所有调用方开放。比如“删除工单”这种高危操作应该只对特定来源开放。可以在工具方法里检查调用方身份不满足条件就拒绝执行。第三层是参数级校验。即使调用方有权限也要校验参数是否合法。比如查询工单时要校验调用方是否有权限查看该工单不能因为对方知道工单号就放行。我见过一个反面案例某团队把内部管理接口直接暴露成 MCP没做任何鉴权结果被扫描到之后有人通过 AI 客户端批量拉取了敏感数据。这个教训很深刻鉴权这一步绝对不能省。6.2 限流与超时防止 AI 把服务打挂AI 调用工具的频率可能远超人类操作。一个复杂的任务AI 可能连续调用几十次工具。如果不做限流很容易把后端服务打挂。限流要分两个维度一是单次会话的调用频率二是全局的并发数。单次会话限流防止某个 AI 任务疯狂调用全局限流防止整体过载。具体阈值要根据你的服务承载能力来定可以先设一个保守值观察一段时间再调整。超时同样重要。AI 调用工具时如果后端处理时间过长Client 可能会一直等待。要给每个工具设置合理的超时时间超时后返回明确的错误让 AI 知道这次调用失败了可以决定重试还是放弃。另外要注意工具执行的幂等性。AI 可能会因为超时重试而重复调用同一个工具如果工具不是幂等的就会产生重复数据。对于写操作要么设计成幂等要么在返回结果里带上唯一标识让 AI 能判断是否重复。6.3 日志与审计出了问题能查MCP 调用必须有完整的日志记录。每次调用要记录谁调的调用方标识、调了什么工具名、传了什么参数、返回了什么结果、耗时多久、是否成功。这些日志有两个用途一是排查问题当 AI 行为异常时可以通过日志还原它到底调了什么二是审计对于涉及敏感数据的调用需要留痕以备追溯。日志里要注意脱敏。参数和返回值里可能包含手机号、身份证号、金额等敏感信息记录前要做脱敏处理。我一般会把敏感字段替换成掩码只保留格式信息。日志的存储和查询也要考虑。如果调用量大日志会很快堆积需要设置合理的保留周期和归档策略。查询接口要能按调用方、工具名、时间范围等维度快速检索。7. 踩过的坑与实战经验7.1 工具描述里的“隐形陷阱”我踩过最深的坑是工具描述里的歧义。有一次我写了个“查询用户信息”的工具描述里只说了“根据用户 ID 查询”结果 AI 经常把用户名当成用户 ID 传进来。后来我在描述里明确写了“参数必须是数字 ID不是用户名”问题才解决。还有一个坑是工具之间的描述太相似。我有两个工具一个叫“查询订单”一个叫“查询订单详情”描述也差不多AI 经常选错。后来我把它们合并成一个工具用参数控制返回详细程度反而更清晰。另一个经验是描述里要写“不适用场景”。比如某个工具只适用于查询已完成的订单那就在描述里写明“不适用于查询进行中的订单”避免 AI 在错误场景下调用。7.2 参数默认值引发的误调用给参数设默认值本来是为了方便但有时候会适得其反。我有个工具的参数“时间范围”默认值是“最近 7 天”结果 AI 在用户明确说“查上个月”的时候还是用了默认值因为它觉得不传参数更简单。后来我改成了对于关键参数不设默认值强制 AI 明确传入。如果 AI 没传就返回错误提示“请指定时间范围”。这样虽然多了一次交互但避免了错误结果。对于确实需要默认值的参数我会在描述里强调“如果不确定请先询问用户不要使用默认值”。这句话能显著减少 AI 的“自作主张”。7.3 返回数据过大导致的截断问题有一次我做了个“导出全量数据”的工具返回了几万条记录。结果 AI 客户端直接把返回截断了只拿到前一部分后续处理全乱了。这个坑的教训是MCP 工具的返回值要控制大小。如果数据量可能很大要么分页返回要么只返回摘要和获取详情的入口。我后来的做法是列表类工具默认只返回前 20 条并在返回里说明“共 X 条已返回前 20 条如需更多请使用分页参数”。对于确实需要返回大数据的场景可以返回一个临时文件路径或下载链接让 AI 告诉用户去下载而不是把数据全塞进返回值里。7.4 版本升级时的兼容性处理MCP 协议和 SDK 都在快速迭代升级时很容易出现兼容性问题。我遇到过一次 SDK 升级后工具注册方式变了原来的代码直接编译不过。应对这个问题的办法是把 MCP 相关的代码隔离在一个独立的模块里业务逻辑不要直接依赖 MCP 的类。这样升级时只需要改这个模块业务代码不受影响。同时要锁定依赖版本不要用动态版本号避免某天构建时突然拉到一个不兼容的新版本。升级前一定要在测试环境完整跑一遍所有工具确认行为没有变化。我习惯维护一份“工具行为基线”记录每个工具在标准输入下的输出升级后对比输出是否一致。8. 这套方案还能怎么扩展把 Spring Boot 服务变成 MCP Server 只是第一步。跑通之后你会发现很多可以继续深挖的方向。一个方向是工具的动态注册。现在工具是写死在代码里的每次加新工具都要改代码、重新部署。如果能把工具定义放到配置中心或数据库里就可以在不重启服务的情况下动态增减工具。这对于工具频繁变化的场景很有价值。另一个方向是多服务聚合。一个 AI 应用可能需要同时调用多个 Spring Boot 服务的能力如果每个服务都单独做一个 MCP ServerClient 要管理多个连接。可以考虑做一个“聚合网关”把多个后端服务的工具统一暴露在一个 MCP Server 里对 Client 来说只有一个连接。还有一个方向是调用链追踪。当 AI 连续调用多个工具时把整条调用链串起来记录每个环节的输入输出和耗时。这对于分析 AI 行为、优化工具设计都很有帮助。可以在每次调用时生成一个 trace ID贯穿整条链路。我个人在实际操作中的体会是MCP 的价值不在于技术本身有多复杂而在于它把“AI 调用外部能力”这件事标准化了。标准化带来的最大好处是复用——你为 A 应用做的 MCP ServerB 应用可以直接用不需要任何改造。这种复用性会随着你接入的 AI 应用越来越多而越来越明显。所以如果你手上有一堆 Spring Boot 服务早点把它们 MCP 化后面会省很多事。