ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring AI 2.0 MCP Server 实战:把 Java 服务改造成 AI 工具

Spring AI 2.0 MCP Server 实战:把 Java 服务改造成 AI 工具 上一篇文章里我们讲了怎么用 Spring AI 2.0 的 MCP Client 去消费外部工具——AI 应用连上别人的 MCP Server 就能调用几百个现成工具。但有个问题没解决你自己的订单服务、库存服务、售后系统怎么变成 MCP Server 让别人或别的 AI 应用来调用总不能每次有新工具需求都让 AI 团队来你这边写适配代码吧。这篇文章解决的就是这个事用 Spring AI 2.0 提供的spring-ai-starter-mcp-server把手里的普通 Java 服务改造成一个标准 MCP Server。改造完任何 MCP Client不管是 Claude Desktop、Cursor 还是你自己的 Spring AI 应用都能自动发现并调用你的工具。全文代码基于 Spring Boot 4.1.0 Spring AI 2.0.0可以直接照着跑。一、这个问题到底是什么把 Java 服务变成 MCP Server核心目标只有一个让服务的能力通过标准协议暴露出去调用方不需要知道你内部是怎么实现的。先想清楚一个场景。假设你们公司有个订单服务里面有一个方法查订单详情、一个方法查物流轨迹。以前AI 应用想用这两个能力得写 Tool 注解把方法包装起来然后注册进 ChatClient。这听起来不麻烦但有两个隐患第一AI 应用和订单服务被耦合在一起订单服务改接口AI 应用要跟着改第二如果 Claude Desktop、Cursor 这类外部 AI 工具也想用你的订单能力你根本没地方给它注册 Tool——那是 Spring AI 应用内部的东西。MCP Server 就是来解决这个问题的。你的订单服务自己启动一个 MCP Server把查订单查物流声明成标准工具。任何 MCP Client 连上来都能自动发现这两个工具、自动调用。你不需要关心调用方是 Spring AI 还是别的什么调用方也不需要关心你的服务是 Java 写的还是别的什么写的。打个比方。以前的做法是你把工具直接焊死在 AI 应用里——像手机电池焊死了谁想换都难。MCP Server 的做法是你把工具做成标准接口插在墙上——像 USB 充电口任何设备拿根线插上就能用。对 Java 开发者来说这件事在过去并不轻松MCP 协议基于 JSON-RPC 2.0要自己实现协议、处理传输层、管理会话……但现在 Spring AI 2.0 把这一切封装好了。你只需要做三件事引入spring-ai-starter-mcp-server依赖写一个普通的类方法上加Tool注解配置一下暴露方式SSE 还是 STDIO完事。剩下的协议细节Spring AI 全包了。二、底层原理到底怎么回事要理解 Spring AI 的 MCP Server 是怎么工作的先得理解 MCP 协议里两个角色的关系。MCP 协议把参与方分成两边MCP Server服务端提供工具的人和MCP Client客户端使用工具的人。通信基于 JSON-RPC 2.0也就是双方通过交换 JSON 格式的请求和响应来协作。一次完整的调用流程分三步初始化InitializeClient 连上 Server双方交换协议版本和能力信息。这一步相当于握手——“你是哪个版本的协议你支持哪些能力”工具发现Tools/ListClient 问 Server“你有哪些工具” Server 返回工具清单每个工具带名字、描述、参数 schema参数长什么样。工具调用Tools/CallClient 按 schema 传参调用某个工具Server 执行后把结果返回。注意MCP 协议本身不规定传输层用什么。目前主流有两种STDIO通过标准输入输出通信Server 和 Client 在同一个进程里常用于本地工具和HTTP SSEServer 是一个独立 HTTP 服务Client 通过网络访问常用于远程服务。这篇文章我们用 SSE 模式——订单服务部署成独立 HTTP 服务这才是企业里最常见的形态。Spring AI 2.0 在底层做了这些事它用Tool注解解析你的方法自动生成符合 MCP 规范的工具定义名字、描述、JSON Schema 参数它内置了 MCP 协议的状态机处理握手、工具列表、调用请求这些协议消息它把工具调用的结果自动序列化成 JSON 返回给 Client。你写的业务代码Spring AI 一行都不用改原样暴露出去。这里有个关键设计值得说一下Spring AI 的 MCP Server 支持 WebMvc 和 WebFlux 两种 HTTP 栈。WebMvc 是传统的 Servlet 模型同步阻塞写起来简单WebFlux 是响应式模型异步非阻塞适合高并发长连接场景。SSEServer-Sent Events是单向推送正好配合 WebFlux 的流式能力。选哪个取决于你的服务架构——如果你的服务本来就是 WebMvc 的绝大多数 Spring Boot 服务都是直接用 WebMvc starter 就行Spring AI 会自动把你的 MCP 接口挂到现有应用上。再补一个概念工具描述和参数 schema 的质量直接决定 AI 能不能正确调用你的工具。MCP Server 返回的工具定义里description是给大模型看的properties是参数说明。模型读不懂你的 Java 代码它只能读到这些文本。所以 description 写得越清楚模型选对工具、传对参数的概率越高。这是后面实战部分会反复强调的点。最后说下版本。Spring AI 2.0.0 是 2026 年 6 月发布的 GA 版本对应的 Spring Boot 4.1.0。MCP 相关的 starter 有两个spring-ai-starter-mcp-server服务端和spring-ai-starter-mcp-client客户端当前最新 GA 都是 2.0.0。注意老版本1.0.x里 MCP Server 的 starter 叫spring-ai-mcp-server-webmvc-spring-boot-starter那个版本还停在 1.0.0-M6里程碑版本不要用。用 2.0.0 就对了。三、实战手把手写代码3.1 项目骨架和依赖新建一个 Spring Boot 项目Java 21pom.xml如下?xml version1.0 encodingUTF-8?projectxmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion4.1.0/versionrelativePath//parentgroupIdcom.baiyunge/groupIdartifactIdorder-mcp-server/artifactIdversion1.0.0/versionnameorder-mcp-server/namedescription订单服务 MCP Server 实战/descriptionpropertiesjava.version21/java.versionspring-ai.version2.0.0/spring-ai.version/propertiesdependencyManagementdependenciesdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-bom/artifactIdversion${spring-ai.version}/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagementdependencies!-- MCP Server 核心自动把 Tool 方法暴露成 MCP 工具 --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-mcp-server/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project这段配置里两个关键点。第一spring-ai-starter-mcp-server没有写版本号因为它由spring-ai-bom2.0.0统一管理这就是引入 BOM 的意义——所有 Spring AI 组件版本一致不会出现 A 组件 2.0.0、B 组件 1.1.8 这种混乱。第二这个 starter 默认带的是 WebMvc 版本spring-ai-mcp-server-webmvc如果你的服务用的是响应式栈需要额外排除并引入 WebFlux 版本后面 3.4 节会说。3.2 写业务工具类一个类就是一个工具包先写一个简单的订单查询工具。这个类不需要继承任何东西就是一个普通 Spring 组件packagecom.baiyunge.order;importorg.springframework.ai.tool.annotation.Tool;importorg.springframework.ai.tool.annotation.ToolParam;importorg.springframework.stereotype.Component;importjava.time.LocalDateTime;importjava.util.List;importjava.util.Map;/** * 订单查询工具。 * 类上的 Component 让 Spring 管理它 * 方法上的 Tool 让 Spring AI 把它暴露成 MCP 工具。 */ComponentpublicclassOrderTools{/** * 模拟的订单数据。真实项目里这里应该是注入 Service 查数据库。 */privatestaticfinalMapString,MapString,ObjectORDER_DBMap.of(A1001,Map.of(orderId,A1001,customer,张三,amount,299.0,status,已发货,createdAt,2026-08-01T10:30:00),A1002,Map.of(orderId,A1002,customer,李四,amount,89.5,status,待付款,createdAt,2026-08-10T14:00:00));/** * 根据订单号查询订单详情。 * * param orderId 订单号例如 A1001 * return 订单详情 */Tool(description根据订单号查询订单的详细信息包括客户、金额、状态和创建时间)publicMapString,ObjectqueryOrder(ToolParam(description订单号例如 A1001)StringorderId){MapString,ObjectorderORDER_DB.get(orderId);if(ordernull){returnMap.of(error,订单不存在: orderId);}returnorder;}/** * 查询所有订单。 * * return 订单列表 */Tool(description查询系统里全部订单返回订单列表)publicListMapString,ObjectlistOrders(){returnList.copyOf(ORDER_DB.values());}/** * 更新订单状态模拟发货、退款等操作。 * * param orderId 订单号 * param newStatus 新状态可选值待付款、已付款、已发货、已完成、已取消 * return 更新结果 */Tool(description更新订单状态用于发货、退款、取消订单等操作)publicMapString,ObjectupdateOrderStatus(ToolParam(description订单号)StringorderId,ToolParam(description新状态可选值待付款、已付款、已发货、已完成、已取消)StringnewStatus){MapString,ObjectorderORDER_DB.get(orderId);if(ordernull){returnMap.of(error,订单不存在: orderId);}ORDER_DB.put(orderId,Map.of(orderId,orderId,customer,order.get(customer),amount,order.get(amount),status,newStatus,createdAt,order.get(createdAt),updatedAt,LocalDateTime.now().toString()));returnORDER_DB.get(orderId);}}这段代码在干什么Tool注解告诉 Spring AI这个方法要暴露成 MCP 工具description是这个工具给大模型看的说明ToolParam的description是每个参数给大模型看的说明。方法返回值无所谓什么类型Spring AI 会序列化成 JSON。注意updateOrderStatus这种有副作用的操作description 里把状态的可选值写全了模型才知道怎么传参——这是工具能被正确调用的关键。3.3 启动类packagecom.baiyunge.order;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplicationpublicclassOrderMcpServerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(OrderMcpServerApplication.class,args);}}启动类没有任何特殊之处就是一个标准 Spring Boot 应用。启动后Spring AI 会自动扫描带Tool注解的 Bean把OrderTools里的三个方法注册成 MCP 工具并挂到 HTTP 接口上。3.4 配置文件application.ymlserver:port:8082spring:application:name:order-mcp-serverai:mcp:server:# 暴露 MCP 接口的路径前缀base-path:/mcp配置就三行。server.port设成 8082避免和本地其他服务冲突。spring.ai.mcp.server.base-path是 MCP 接口的挂载路径默认是/mcp这里显式写出来方便你记住——待会儿验证时要访问http://localhost:8082/mcp。如果你用的是 WebFlux响应式项目直接把 starter 换成 WebFlux 专用版即可版本同样由 BOM 管理不用写版本号dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-mcp-server-webflux/artifactId/dependency注意Spring AI 2.0 提供了三个服务端 starter——spring-ai-starter-mcp-server默认 WebMvc、spring-ai-starter-mcp-server-webmvc、spring-ai-starter-mcp-server-webflux当前最新 GA 都是 2.0.0按你的 HTTP 栈选一个即可。绝大多数业务服务是 WebMvc用默认 starter 就行这段只是给响应式项目留个方案。3.5 启动并验证用 curl 模拟 MCP Clientmvn spring-boot:run启动后MCP 协议的握手需要两步初始化 工具列表curl 验证如下# 第一步初始化握手返回协议版本和服务能力curl-s-XPOST http://localhost:8082/mcp\-HContent-Type: application/json\-HAccept: application/json, text/event-stream\-d{ jsonrpc: 2.0, id: 1, method: initialize, params: { protocolVersion: 2025-06-18, capabilities: {}, clientInfo: {name: curl-test, version: 1.0} } }初始化会返回一个Mcp-Session-Id头第二步工具发现要带上它# 第二步列出工具这里会返回你写的三个 Tool 方法SESSION_ID$(curl-s-D--o/dev/null-XPOST http://localhost:8082/mcp\-HContent-Type: application/json\-d{jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2025-06-18,capabilities:{},clientInfo:{name:curl-test,version:1.0}}}\|grep-imcp-session-id|tr-d\r|awk{print $2})curl-s-XPOST http://localhost:8082/mcp\-HContent-Type: application/json\-HMcp-Session-Id:$SESSION_ID\-d{jsonrpc:2.0,id:2,method:tools/list,params:{}}返回的 JSON 里tools数组应该有三个工具queryOrder、listOrders、updateOrderStatus每个都带 description 和参数 schema。看到这三个工具说明你的服务已经成功变成 MCP Server 了——Spring AI 自动完成了协议解析、工具注册、schema 生成这些活。第三步调用工具curl-s-XPOST http://localhost:8082/mcp\-HContent-Type: application/json\-HMcp-Session-Id:$SESSION_ID\-d{jsonrpc:2.0,id:3,method:tools/call,params:{name:queryOrder,arguments:{orderId:A1001}}}返回结果里content数组会带着订单 JSON。到这里一个完整的 MCP Server 就落地了。四、踩坑经验和最佳实践坑一老版本的 MCP starter 名字完全不一样。网上大量教程让你引spring-ai-mcp-server-webmvc-spring-boot-starter这个坐标在 Maven Central 上确实存在但最新只有 1.0.0-M6里程碑版本还没 GA而且那是 Spring AI 1.x 时代的产物。2.0.0 统一成了spring-ai-starter-mcp-server写 POM 时务必核对坐标和版本别让旧教程带偏。坑二description 写不清楚模型就乱传参。这是工具调用成功率的第一杀手。updateOrderStatus的newStatus参数如果你只写新状态三个字模型可能传已发、“Shipping”、3这种乱七八糟的值把可选值待付款、已付款、已发货、已完成、已取消写全模型才能规范传参。同理参数有格式要求比如订单号是字母开头数字也写进 description。规则description 里写清是什么、有哪些可选值、格式长什么样。坑三工具数量太多会撑爆上下文。MCP Client 每次对话都会拉取全部工具列表给模型。如果你的 Server 暴露了 50 个工具每个工具的 schema 平均 500 token光工具定义就吃掉 2.5 万 token。对策把工具按域拆分到多个 MCP Server订单一个、售后一个或者用Tool的name属性统一命名空间如order_query、order_update方便客户端按需加载。坑四有副作用的工具要加确认机制。updateOrderStatus这种写操作模型一旦误调用就是线上事故。生产环境的做法写操作工具内部先返回待确认结果让用户确认后再真正执行或者把写操作放在独立的高权限 MCP Server 里权限控制做在传输层比如加 token 鉴权。坑五SSE 模式要处理会话。SSE 长连接下MCP 协议要求 Client 带着 Session-Id 才能调用工具上面 curl 验证里你已经看到了。真实项目里如果客户端调用偶发失败先查 Session-Id 有没有正确透传。最佳实践一工具类保持薄。Tool方法里只做参数校验和调用 Service业务逻辑全放 Service 层。这样工具层只是翻译官把 MCP 协议翻译成业务调用职责单一也好测试。最佳实践二返回结构统一。所有工具返回Map或统一的结果对象如{code:0,data:...}模型解析起来更稳定。别一个工具返回 Map、一个返回实体类、一个返回 String模型会懵。最佳实践三用 MCP Inspector 调试。Spring AI 官方配套的 MCP Inspector一个可视化调试工具可以连你的 Server直接看工具列表、手动调工具、查看原始协议消息。排查工具没被发现参数 schema 不对这类问题比用 curl 高效得多。五、性能对比和技术选型WebMvc vs WebFlux两个 starter 暴露的协议完全一样区别在底层 HTTP 模型。WebMvc 同步阻塞实现简单、生态兼容性最好QPS 需求在几千以内的业务服务够用WebFlux 异步非阻塞单连接内存占用低适合高并发、长连接SSE 流式响应场景。选型建议现有服务是 WebMvc 就用默认 starter除非你的 MCP 工具本身就是流式/高并发的才考虑 WebFlux。MCP Server vs 手写 Tool 注册进 ChatClient这是两种不同量级的方案。手写 Tool 适合这个工具只有我这个 AI 应用用的私有场景零额外成本MCP Server 适合工具要被多个 AI 应用/外部工具复用的场景一次改造、处处可用。从 8 月 5 日那篇客户端文章的角度看Spring AI 应用自己也可以是 MCP Client——你自己的服务做成 MCP Server自己应用用 MCP Client 连外部工具也能连一套方案通吃内部外部。实测数据参考社区公开压测数据非本机测试SSE 模式下 MCP Server 单实例能稳定支撑每秒 1000 次工具调用瓶颈通常不在协议层而在业务代码本身协议层单次工具调用的额外开销在 1-5 毫秒量级相比大模型推理的秒级延迟可以忽略不计。所以性能上不用纠结把精力花在工具质量和描述质量上收益大得多。六、总结把 Java 服务改造成 MCP Server本质上就是三件事引入spring-ai-starter-mcp-server2.0.0、在普通方法上加Tool注解、配置base-path后启动。剩下的协议握手、工具发现、参数 schema 生成、结果序列化Spring AI 全部自动完成。回顾全文的关键结论MCP 协议基于 JSON-RPC 2.0通信分初始化、工具发现、工具调用三步传输层支持 STDIO 和 HTTPSSE 两种企业远程服务用 SSE。spring-ai-starter-mcp-server是 Spring AI 2.0 的 MCP 服务端 starter默认 WebMvcWebFlux 项目换成spring-ai-starter-mcp-server-webflux。旧版spring-ai-mcp-server-webmvc-spring-boot-starter停留在 1.0.0-M6里程碑版本不要用。工具描述质量决定模型调用准确率description 要写清是什么、可选值、格式。工具数量多时按域拆分 Server 或统一命名空间避免撑爆上下文。写操作工具必须加确认/鉴权机制防止模型误调用造成线上事故。下一步你可以做的把这个订单 MCP Server 跑起来用 MCP Inspector 连上去看看工具长什么样然后照 8 月 5 日那篇文章写一个 Spring AI MCP Client 连你自己的 Server完成自己服务自己调的闭环。手动摘要150字内本文用 Spring Boot 4.1.0 Spring AI 2.0.0 实战讲解如何把 Java 服务改造成 MCP Server。通过spring-ai-starter-mcp-server依赖和Tool注解订单查询、状态更新等普通方法即可自动暴露为标准 MCP 工具任何 MCP Client 都能发现和调用。文章包含完整可运行代码、curl 协议验证、五个真实踩坑经验以及 WebMvc/WebFlux 选型建议适合想给 AI 应用提供标准工具的 Java 开发者。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进