
Java Spring AI MCP:在 Spring 里把 AI 接上你的业务系统
🔥 写在前面:MCP 教程十篇有九篇是 Node/TypeScript。但国内大量系统是 Java 栈,强行用 Node 写 Server 再和 Spring 对接,运维和协作成本都不低。好消息:Spring AI 已经原生支持 MCP。这篇文章带你用 Java 写一个生产级 MCP Server,把已有的业务 Service 直接暴露给 AI。
💡 我是做一线 Java + AI 工程落地的,MCP 和信创都是真刀真枪踩过坑的。本专栏会持续更新 MCP / Spring AI / 信创实战,关注我,新篇第一时间看,少走弯路。
一、先说结论
- Spring AI 的
spring-ai-mcp模块 已经把传输层、协议层、原语层都包好了,你只需要写@Tool标注的方法。 - 对 Java 团队的最大价值:不用离开 Spring 生态 ,直接把现有
@Service变成 AI 可调用的 Tool,复用事务、鉴权、连接池。 - 两种玩法:SDK Server 模式 (内嵌 MCP Server,stdio/SSE)和Spring Boot Starter 模式(注解驱动,最省事)。
- 但别指望零成本:MCP 的「工具描述」「参数 Schema」仍要你认真写,否则 AI 不会调(见第七坑)。
二、为什么 Java 团队该直接用 Spring AI MCP
传统做法是「Node 写 MCP Server → 调 Java 后端 HTTP 接口」。问题:
- 两套技术栈、两套部署、两套监控;
- Node Server 成了「翻译层」,既脆弱又多余;
- 鉴权 / 事务 / 日志要和 Java 侧对齐,重复劳动。
Spring AI MCP 直接把这层翻译去掉:

text
AI Client → MCP(stdio/SSE) → Spring Boot 应用
├─ @Tool 方法(原 @Service 方法)
├─ Spring 事务 / 鉴权 / 连接池(复用)
└─ 你的业务 DAO
一句话:你的 Spring Bean 就是 MCP Server,不用另起炉灶。
实际落地时有两种 Server 模式可选:内嵌式 (MCP Server 跑在你的 Spring Boot 进程里,通过 stdio 或 SSE 对外)和独立式 (打成单独 jar 由客户端拉起)。内嵌式最省事、和主应用共享事务/连接池;独立式隔离性更好,适合把 MCP 能力单独部署、横向扩容。我的默认选择是内嵌 SSE 模式------既能让远程客户端连,又不引入额外进程,运维面最小。要注意:SSE 模式走 HTTP,必须叠一层 Spring Security(见第七坑 6),否则公网直接被白嫖。
三、最快上手:Starter + 注解
核心依赖(Maven):
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
写一个 Tool 只需一个注解:
java
@Service
public class OrderService {
@Tool(name = "query_order",
description = "当用户想按订单号查询订单状态时调用,参数 orderId 为字符串")
public String queryOrder(@ToolParam("订单号") String orderId) {
return orderRepository.findStatus(orderId); // 复用已有 DAO + 事务
}
}
关键点:
@Tool的description和@ToolParam的描述会原样发给 AI。写得含糊,AI 就不调(第七坑 2 同款问题)。
对应的 application.yml 声明 Server 模式与传输:
yaml
# application.yml:声明 Server 模式与暴露的传输
spring:
ai:
mcp:
server:
enabled: true
name: order-mcp
version: 1.0.0
type: SYNC # SYNC(WebMVC)或 ASYNC(WebFlux)
sse-endpoint: /mcp/sse # SSE 模式对外暴露的路径
带 DTO 参数的写操作(参数 Schema 由 DTO 字段描述自动生成):
java
// 复杂参数用 record + @ToolParam,Schema 由字段描述生成,AI 不会传错类型
public record OrderCmd(@ToolParam("订单号") String orderId,
@ToolParam("新状态") String status) {}
@Service
public class OrderService {
@Tool(name = "update_order",
description = "当用户要修改订单状态时调用,参数 orderId 为订单号,status 为目标状态")
public String updateOrder(OrderCmd cmd) {
return orderRepository.updateStatus(cmd.orderId(), cmd.status());
}
}
经验:读操作(query)和写操作(update)建议拆成两个 Tool,方便后续做权限隔离(只读 Server / 读写 Server 分离,见第 4 篇)。
四、一次调用在 Spring 里怎么流转
AI 说「查一下订单 A123」,完整链路是:

- MCP 传输层收到
tools/call(参数orderId=A123); - Spring AI 的 Dispatcher 路由到
OrderService.queryOrder; - 方法内走正常的 Spring 事务 + 鉴权拦截器(和你的其他接口一致);
- 结果序列化回 MCP 协议帧,返回给 AI。
你写的业务代码完全不知道自己在被 AI 调用------这就是 Spring AI MCP 最优雅的地方。
五、手写 vs 框架:到底省了多少
我对比了「手写 Spring MVC 接口 + 自己封装 MCP 协议」和「直接用 Spring AI Starter」:

- 手写:每个 Tool 约 40 行(协议解析 + 路由 + Schema 生成 + 错误处理),10 个 Tool = 400 行胶水代码;
- Starter:每个 Tool 约 5 行(一个
@Tool方法),10 个 Tool = 50 行; - 维护:协议升级时,手写版要改胶水代码,Starter 只升级依赖。
框架不仅是省代码,更关键是协议升级时不用重写,风险更低。
验收标准:你可以自己复现
按这 4 步,能确认你的 Spring MCP Server 真的跑通:
- 起项目 :
mvn spring-boot:run,确认日志出现MCP Server started且监听配置的端口 / stdio。 - 连 Client:用支持 MCP 的客户端(如 Cline)配置该 Server 的启动命令,重启客户端。
- 看工具列表 :在客户端工具列表里能看到
query_order(你定义的 name),且描述正确。 - 真调一次 :让 AI「查订单 A123」,观察:① 客户端发出
tools/call;② 你的 Spring 应用日志打印了queryOrder被调用;③ AI 拿到结果并自然回复。四步全过 = 达标。
六、实测:复用 Spring 生态的真实收益
- 事务一致性:Tool 内抛异常,Spring 事务自动回滚,和 REST 接口行为一致------不用额外处理。
- 鉴权复用 :已有的
@PreAuthorize/ 拦截器对 Tool 调用同样生效,AI 越权调用被挡。 - 监控复用:Tool 调用自动进 Micrometer / Actuator,和现有监控面板打通。
这三点用「Node 翻译层」方案基本都要重写一遍,Spring AI MCP 直接白嫖。
七、我踩过的 6 个坑
坑 1:Starter 选错,Server 起不来
现象:No qualifying bean 或端口被占。根因:WebMVC 和 WebFlux 的 Starter 混用。解决 :纯 Spring MVC 项目用 webmvc 版,Reactive 用 webflux 版,别混。预防:按项目响应式类型选 Starter。
坑 2:Tool 描述太短,AI 不调用
现象:方法注册了,AI 从不调。根因:description 只有「查询订单」四字。解决 :写成「用户按订单号查状态时调用,参数 orderId 为字符串」。预防:每个 Tool 描述包含触发场景 + 参数含义。
坑 3:参数类型没标注,Schema 生成错
现象:AI 传来的参数是字符串,方法要 Long,直接类型转换报错。根因:@ToolParam 没指明类型,Schema 推断错。解决 :明确类型,复杂对象用 DTO + 字段描述。预防:所有参数显式声明类型。
坑 4:长耗时 Tool 阻塞 MCP 线程
现象:查大报表的 Tool 一跑,其他 Tool 全卡。根因:MCP 处理在请求线程,同步阻塞。解决 :耗时操作丢到 @Async 或返回进度句柄。预防:超 2s 的 Tool 默认异步化。
坑 5:返回内容太大撑爆上下文
现象:AI 返回后「失忆」或超 token。根因:Tool 返回了整张 10 万行表。解决 :Server 端分页 / 截断,只回摘要 + 数量。预防:所有返回加 size 上限。
坑 6:SSE 模式忘了鉴权
现象:公网 Spring MCP 被白嫖。根因:SSE 走 HTTP 没加 Spring Security。解决 :加 Spring Security + Bearer Token,复用现有登录体系。预防:SSE 模式默认上 Security。
八、适合谁 / 不适合谁
✅ 适合
- 已有 Spring Boot 业务系统,想让 AI 调用内部能力;
- 团队是 Java 栈,不想引入 Node 运维;
- 需要事务 / 鉴权 / 监控和现有系统一致。
⚠️ 暂不适合
- 超轻量工具、无 Spring 依赖:用 Node SDK 更轻;
- 客户端要求极致低延迟的边缘场景。
九、总结
Spring AI MCP 让 Java 团队「零翻译层」地把业务系统暴露给 AI,最大红利是复用 Spring 的事务、鉴权、监控 。只要认真写好 @Tool 的描述和参数 Schema,剩下的交给框架。下一篇我们谈更实战的:多个 MCP Server 怎么编排。
📢 下篇预告
第 4 篇《多个 MCP Server 怎么编排:数据库 / Redis / Git / 飞书一把梭》------一个客户端接 4 个 Server 时,能力冲突、命名空间、权限边界怎么管?实测给你一套编排规范。
💬 聊聊 + 关注
你的 Spring 项目里,最想先暴露给 AI 的是哪个 Service?订单、用户、还是报表?评论区聊聊,我可以挑一个在下篇做完整示例。
👉 如果这篇帮你用 Java 落地了 MCP Server,点个「关注」------专栏后续还有:多 Server 编排避坑、可交互应用生成、MCP 安全攻防实战、用 MCP 接私有大模型。关注后新篇直接推给你,不迷路。
附:依赖选型与速查表
Gradle 等价依赖(不用 Maven 的团队)
gradle
// build.gradle:WebMVC 项目用 webmvc,Reactive 项目换成 webflux
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc'
}
表 1:Starter 选型
| 项目类型 | Starter | 传输 | 适用 |
|---|---|---|---|
| 纯 Spring MVC | spring-ai-starter-mcp-server-webmvc |
stdio / SSE | 绝大多数后台 |
| Reactive / WebFlux | spring-ai-starter-mcp-server-webflux |
SSE | 响应式栈 |
| 仅本地子进程 | spring-ai-starter-mcp-server |
stdio | 桌面客户端拉起 |
表 2:手写 vs Starter
| 维度 | 手写封装 | Spring AI Starter |
|---|---|---|
| 单 Tool 代码量 | ~40 行 | ~5 行 |
| 10 Tool 总量 | ~400 行胶水 | ~50 行 |
| 协议升级 | 改胶水代码 | 只升依赖 |
| 事务/鉴权 | 自己接 | 复用 Spring |
表 3:复用 Spring 生态能力
| 能力 | 是否自动复用 | 说明 |
|---|---|---|
| 事务(@Transactional) | ✅ | Tool 内异常自动回滚 |
| 鉴权(@PreAuthorize) | ✅ | AI 越权调用被拦 |
| 监控(Micrometer) | ✅ | Tool 调用进 Actuator |
| 日志(SLF4J) | ✅ | 和现有日志体系一致 |
表 4:常用注解对照
| 注解 | 作用 | 注意 |
|---|---|---|
@Tool |
标记方法为 Tool | description 必写触发场景 |
@ToolParam |
标记参数 + 描述 | 复杂对象用 DTO |
@ToolCallback |
注册回调 | 动态注册用 |
record 作参数 |
自动生成 Schema | 字段加 @ToolParam |
@Async |
异步 Tool | 超 2s 操作用它 |
表 5:6 个坑 → 对策
| 坑 | 根因 | 对策 |
|---|---|---|
| Starter 选错 | MVC/Flux 混用 | 按响应式类型选 |
| 描述太短 AI 不调 | description 含糊 | 写触发场景+参数 |
| 参数类型错 | 未标类型 | 显式声明/DTO |
| 长耗时阻塞 | 同步阻塞线程 | @Async 或进度句柄 |
| 返回过大 | 全量返回 | 分页/截断 |
| SSE 忘鉴权 | 无 Security | 加 Bearer Token |