本文基于 Spring AI 2.0.1 (截至 2026-08-25 的当前稳定版)与 MCP Java SDK 2.0.x (对应 2025-11-25 版 MCP 规范)编写,API 与配置均核对自官方参考文档。
开篇:2026 年,把 Spring 服务暴露给 AI 的标准答案
2026 年,"让大模型调用我的业务系统"已经从一个技术选型题变成了标准操作题------答案就是 MCP(Model Context Protocol)。Anthropic 在 2024 年底开源的这个协议,如今已经是 AI 工具调用领域的事实标准:Claude、Cursor、各类 Agent 框架都通过它接入外部工具,Linux 基金会旗下的 AAIF(Agentic AI Foundation)负责治理,各大语言都有官方 SDK。
对 Java 开发者来说,这个故事尤其顺:你不需要新起一个服务,把现有的 Spring Boot 服务加一个注解,它就成了一个 MCP Server------任何 MCP 客户端(Claude、Cursor、你自己写的 Agent)都能直接调用里面的工具。
这篇文章是一个完整的"十分钟"实操:从建项目、写第一个 @McpTool、配置 Streamable HTTP 传输,到用 MCP Inspector 和 Cursor 验证调用,最后讲清楚无状态模式的切换和企业级落地要补什么课。
先对齐版本现状(重要)
写 MCP 相关的文章,第一件事是交代版本坐标,因为这里的版本差最容易踩坑:
| 组件 | 当前版本(2026-08-25) | 对应 MCP 规范 |
|---|---|---|
| MCP 规范 | 2026-07-28(2026-07-28 正式发布) | --- |
| MCP Java SDK | 2.0.1(2026-08-19 发布) | 2025-11-25 |
| Spring AI | 2.0.1(当前稳定版) | 基于 MCP Java SDK 2.0.x,即 2025-11-25 |
| LangChain4j | 1.19.0(2026-08-14 发布) | MCP 客户端已支持 2026-07-28 |
也就是说:规范已经跑到 2026-07-28,但 Java 服务端生态(MCP Java SDK / Spring AI)还在 2025-11-25。这不是坏事------2025-11-25 规范本身已包含 Streamable HTTP 传输和无状态变体,足够支撑生产级 MCP Server;而 Spring AI 2.0 又把注解和传输模块全部收编进了自己的名下,开发体验是目前 Java 生态里最顺的。新规范的支持进度与迁移建议,下一篇踩坑记里详细展开。
一、五分钟原理:MCP 到底定义了什么
不背协议细节,只记三件事。
1. 服务端三原语:Tools / Resources / Prompts
一个 MCP Server 能向客户端暴露三种东西:
- Tools(工具):模型可以主动调用的函数------"查一下北京明天的天气";
- Resources(资源):服务端持有的数据,用 URI 标识------"当前部署的配置文件内容";
- Prompts(提示模板):预置的提示词模板------"代码审查"这种带参数的标准化提示。
90% 的场景只需要 Tools,本文的实战也以它为主,但三种原语在 Spring AI 里都是注解级别的支持(@McpTool / @McpResource / @McpPrompt),后面都会见到。
2. 传输层:STDIO 与 Streamable HTTP
MCP 客户端和服务端之间怎么通信?两个选项:
- STDIO:客户端把服务端作为本地子进程启动,走标准输入输出。适合本地开发场景(比如 Claude Desktop 跑你机器上的脚本);
- Streamable HTTP:服务端是一个 HTTP 端点,客户端 POST 请求过去,服务端可以用普通 HTTP 响应或 SSE 流返回。适合远程、多客户端、要上 K8s 的场景。
时间线上有个关键节点:旧的 HTTP+SSE 传输自 2025-03-26 规范引入 Streamable HTTP 时起就进入了弃用通道,2026-07-28 规范更是把它按功能生命周期政策正式归类为 Deprecated。今天新建的远程 MCP Server,Streamable HTTP 是唯一正确答案------这也是本文标题的后半句。
3. 一切都是 JSON-RPC
MCP 的消息格式是 JSON-RPC 2.0:tools/list 列出工具、tools/call 调用工具、resources/read 读资源......知道这一点,你就能看懂任何 MCP 调试工具里的报文。
最后看一眼"对面"长什么样。调用你 Server 的 MCP 客户端(无论 Inspector、Cursor 还是自研 Agent),在 MCP Java SDK 里是这么分层的:

MCP Java 客户端分层架构(改绘自 Spring AI / MCP Java SDK 官方架构图):工厂统一入口,同步 / 异步双 API 镜像,传输层可插拔。
记住 McpSyncClient / McpAsyncClient 这两个名字------功能完全镜像,只是一个阻塞、一个返回 Mono;后面用 SDK 写自研 Agent 客户端时,选型就在这一层。
二、十分钟实战
环境要求:JDK 17+(建议 21)、Node.js(最后用 MCP Inspector 验证时需要)。以下代码基于 Spring AI 2.0.1 核对。
第 1 分钟:建项目,加依赖
新建一个 Spring Boot 项目(Boot 4.x),只需要一个 starter:
xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
</parent>
<properties>
<spring-ai.version>2.0.1</spring-ai.version>
</properties>
<dependencies>
<!-- WebMVC 版(同步阻塞);响应式场景换 webflux 同名 starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
一个冷知识:@McpTool 这些注解在 Spring AI 1.0.x 时代来自社区库 org.springaicommunity:mcp-annotations,1.1 起官方接管 ------spring-ai-mcp-annotations 模块随 starter 自动引入(1.1.x 的 starter POM 已直接依赖它),2.0 起注解完全融入 Spring AI 且扫描默认开启(包名 org.springframework.ai.mcp.annotation.*);MCP 的 WebMVC/WebFlux 传输模块也在 2.0 被收编到 Spring AI 名下(groupId 从 io.modelcontextprotocol.sdk 改为 org.springframework.ai)。所以今天你只需要上面这一个依赖。
而这个 starter 背后,MCP Java SDK 的服务端是这么分层的:

MCP Java 服务端分层架构(改绘自 Spring AI / MCP Java SDK 官方架构图):你的 @McpTool 代码落在最上面的特性层,Spring AI starter 帮你组装工厂、会话与传输。
对上本文的语境:你写的 @McpTool / @McpResource / @McpPrompt 落在特性层 (Features);Spring AI 的自动配置替你组装了工厂、会话与传输层;第 10 分钟会讲到的 STATELESS 切换,动的是会话层------这个分层地图记住,后面踩坑时能快速定位问题出在哪一层。
第 2~4 分钟:写第一个 @McpTool
拿一个最常见的场景举例------把"查询订单"暴露给模型。核心就这么多:
java
import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.ai.mcp.annotation.McpToolParam;
import org.springframework.stereotype.Component;
@Component
public class OrderTools {
private final OrderService orderService;
OrderTools(OrderService orderService) {
this.orderService = orderService;
}
@McpTool(name = "get_order", description = "根据订单号查询订单状态与物流信息")
public OrderView getOrder(
@McpToolParam(description = "订单号,例如 SO-20260825-0001", required = true) String orderNo) {
return orderService.findByNo(orderNo);
}
}
写完收工。启动类不用动,配置不用写------annotation-scanner 默认开启,Spring AI 的自动配置会扫描所有带 MCP 注解的 Bean,把 get_order 注册成标准的 MCP 工具:方法参数生成 JSON Schema,description 会成为模型看到的工具说明,返回值序列化为工具结果。
几个写工具的要点:
描述就是提示词。 description 和 @McpToolParam 的 description 是模型决定"何时调用、怎么传参"的唯一依据,把它当成写给模型的 prompt 来打磨,工具的调用准确率会有肉眼可见的差别。
返回复杂对象时可以生成输出 Schema:
java
@McpTool(name = "get_order",
description = "根据订单号查询订单状态与物流信息",
generateOutputSchema = true) // 为非基本类型返回值自动生成 JSON 输出 schema
public OrderView getOrder(...) { ... }
给客户端的行为提示(hints): @McpTool.McpAnnotations 可以声明工具的只读性、幂等性等语义:
java
@McpTool(name = "get_order",
description = "根据订单号查询订单状态与物流信息",
annotations = @McpTool.McpAnnotations(
readOnlyHint = true, // 不修改环境
destructiveHint = false, // 非破坏性操作
idempotentHint = true)) // 重复调用无额外副作用
public OrderView getOrder(...) { ... }
这些 hints 会被客户端用于交互确认等场景(比如"这个工具会改数据,调用前要不要问用户")。
异常的语义有一条隐形分界线:
| 工具里抛出 | 结果 |
|---|---|
RuntimeException |
转成 isError=true 的工具结果,会传达给模型,模型可以推理原因并重试 |
声明的受检异常(checked)或 Error |
直接导致工具调用失败,不会传达给模型 |
换句话说:想让人工智能自己消化错误(参数不对、数据不存在),就抛 RuntimeException 携带可读信息;想让调用直接失败炸出去,就用受检异常。这个设计比看起来重要------工具报错"订单号格式不合法,应为 SO-YYYYMMDD-XXXX"传回给模型后,下一轮它通常能自己纠正。
第 5 分钟:Resources 和 Prompts(可选)
工具之外的两个原语,同样是注解级。比如把配置数据暴露成 Resource:
java
import org.springframework.ai.mcp.annotation.McpResource;
import org.springframework.stereotype.Component;
@Component
public class ConfigResources {
@McpResource(
uri = "config://{key}", // URI 模板,{key} 会映射到方法参数
name = "app-config",
description = "读取当前应用的配置项")
public String getConfig(String key) {
return configStore.get(key);
}
}
再比如一个带参数的提示模板:
java
import org.springframework.ai.mcp.annotation.McpArg;
import org.springframework.ai.mcp.annotation.McpPrompt;
@Component
public class ReviewPrompts {
@McpPrompt(name = "code-review", description = "生成代码审查提示词")
public String review(@McpArg(name = "language", description = "编程语言", required = true) String language) {
return "请以资深 %s 工程师视角审查以下代码,重点指出并发与资源泄漏问题".formatted(language);
}
}
多数团队第一批只上 Tools,Resources/Prompts 在客户端侧的支持也参差,按需选用。
第 6 分钟:配置传输与服务端信息
application.yml:
yaml
spring:
ai:
mcp:
server:
protocol: STREAMABLE # 有状态 Streamable HTTP(默认远程形态)
name: order-mcp-server # 服务端名称(客户端可见)
version: 1.0.0
instructions: "订单查询服务,支持按订单号查询状态与物流" # 给客户端的可选说明
streamable-http:
mcp-endpoint: /mcp # 端点路径,默认 /mcp
instructions 值得一写:它相当于服务端的"自我介绍",高级客户端(比如带规划能力的 Agent)会把它纳入上下文来决定怎么用你的工具。
然后启动:
bash
mvn spring-boot:run
服务起来后,http://localhost:8080/mcp 就是一个可用的 MCP 端点。第 2 分钟到现在,你还没有写过一行"协议代码"。
第 7 分钟:MCP Inspector 验证
不急着接 Claude,先用官方调试工具 MCP Inspector 自测:
bash
npx @modelcontextprotocol/inspector
浏览器会打开调试界面:
- Transport Type 选择 Streamable HTTP;
- URL 填
http://localhost:8080/mcp; - 点 Connect------左侧日志能看到完整的 initialize 握手报文;
- 进 Tools 标签页,List Tools 应该列出
get_order; - 填入订单号,Call Tool,查看返回的 JSON-RPC 结果。
任何"MCP Server 不工作"的问题,先过一遍 Inspector------它能让你看到原始报文,把"模型没调用我的工具"这类模糊问题,定位成"schema 描述不清"或"服务端 500"这类具体问题。
第 8~9 分钟:接入 Cursor / Claude
验证通过后,接真实的 AI 客户端。以 Cursor 为例,在项目或全局的 MCP 配置里加一个远程服务器(以 Cursor 官方文档的最新格式为准,形如):
json
{
"mcpServers": {
"order-service": {
"url": "http://localhost:8080/mcp"
}
}
}
之后在对话里问"帮我查一下订单 SO-20260825-0001 到哪了",模型会自主决定调用 get_order 并把结果整合进回答。Claude 侧则通过 MCP connector 机制接入远程 MCP 服务器(Claude Code 同样支持以 HTTP 方式挂载 MCP 服务端),具体配置以官方文档当前版本为准。
走到这里,一个能被主流 AI 客户端调用的 MCP Server 就上线了。回头看:一个 starter、几个注解、八行 YAML。
第 10 分钟:切换无状态模式
最后一步留给一个更工程化的问题:多副本部署。
上面跑起来的是有状态 模式:客户端 initialize 握手后,服务端会维护会话(协议层面体现为 Mcp-Session-Id)。这带来一个部署约束------多副本时要做会话亲和(sticky routing)或共享会话存储。如果你的工具是纯无状态的查询类服务,Spring AI 提供一个开关:
yaml
spring:
ai:
mcp:
server:
protocol: STATELESS # 无状态 Streamable HTTP
依赖不变,端点不变,服务端不再维护会话状态,随便挂到轮询负载均衡器后面水平扩展。
代价是什么?官方文档列得很清楚:
| 无状态模式的限制 | 说明 |
|---|---|
| 不支持反向消息请求 | elicitation(向用户征询)、sampling(请求模型补全)、ping 都不可用 |
| Tool Context 不适用 | 有状态模式里通过 ToolContext 传递的会话级上下文没了 |
| 客户端要求 | 必须使用 Streamable HTTP 客户端连接 |
对注解代码也有影响:无状态模式下,注解方法不能使用双向通信的上下文 (McpSyncRequestContext 里的 elicit() / sample() / roots() 都依赖反向请求),只能用轻量的 McpTransportContext(仅传输层信息)或干脆不带上下文参数。Spring AI 启动时会按这个规则过滤注解方法,不匹配的记警告日志------升级或切换模式后记得看一眼启动日志。
写有状态工具时,请求上下文是个好东西,比如长任务上报进度:
java
@McpTool(name = "export_report", description = "导出报表(可能耗时较长)")
public String exportReport(McpSyncRequestContext context,
@McpToolParam(description = "报表日期,格式 yyyy-MM-dd", required = true) String date) {
context.info("开始导出 " + date + " 报表"); // 日志通知(客户端可见)
context.progress(p -> p.progress(0.5).total(1.0).message("生成中")); // 进度通知
return reportService.export(date);
}
注意:McpSyncRequestContext 只在有状态模式可用------这也正是"协议无状态、应用可有状态"的边界所在。这个话题,以及 2026-07-28 新规范把整个协议彻底无状态化之后 Java 生态该怎么办,是下一篇文章的主角。
三、企业级落地还要补的课
"十分钟"能跑通 Demo,上生产还有三件事要考虑。
1. 安全:默认裸奔,谁来管认证授权?
上面的服务端点没有任何认证------生产环境必须补上"谁能调、以谁的身份调"。Java 生态目前的选项:
- 社区项目 mcp-security :提供 OAuth 2.0 资源服务器能力和 API-Key 基础支持,快速开始用
mcp-server-security-spring-boot。使用前注意对齐版本线:0.1.x 适配 Spring AI 2.0.x (v0.1.13 起声明适配 Spring Boot 4.1.0 / Spring AI 2.0.0 / MCP SDK 2.0.0,截至本文写作最新为 0.1.14),仍在 Spring AI 1.1.x 的项目用 0.0.6;以及它仅兼容 WebMVC 服务端、社区驱动无官方背书; - Spring AI 官方:参考文档中已有 MCP Security 页面,但截至本文写作标注为 WIP(开发中),值得关注跟进;
- 自建 :MCP over HTTP 本质就是 HTTP 端点,把
spring-security-oauth2-resource-server直接怼上去也是不少团队的选择------Filter 链拦/mcp,和普通 REST API 的做法一致。
顺带一提:2026-07-28 规范在授权侧有一批加固(iss 参数校验、凭据绑定 issuer 等),以及配套的 x-mcp-header 机制(工具参数自动转 HTTP 头,天然适合传租户 ID 做 API-Key 认证)------下一篇详细讲。
2. 观测:Micrometer / OTel
MCP Server 本质是 Spring Boot 应用,Micrometer + Actuator 的常规打法全部适用。值得单独规划的是工具级的观测 :哪个工具被调用最多、失败率多少、耗时分布------这些指标直接决定"哪些工具描述要优化、哪些工具该下线"。2026-07-28 规范还把 OpenTelemetry 的 trace context 传播(traceparent / tracestate / baggage 写进 _meta)写成了标准约定,客户端-服务端的全链路追踪有了协议级支持,这也是新规范值得跟进的理由之一。
3. 工具设计:不是越多越好
实践中的两个铁律:工具数量膨胀会降低模型选择准确率 (有研究表明工具超过一定数量后选择准确率显著下降,大工具集场景考虑检索式披露------这是另一个话题);命名空间要干净 (多团队共建一个 Server 时约定好 team_action 式命名,避免撞名)。
小结
把整条链路压缩成一张图:
css
Spring Bean + @McpTool/@McpResource/@McpPrompt
│ annotation-scanner 自动扫描注册
▼
Spring AI MCP Server(starter + YAML 配置)
│ protocol: STREAMABLE / STATELESS
▼
Streamable HTTP 端点 http://host:port/mcp
│
├── MCP Inspector(开发自测)
├── Cursor / Claude / Claude Code(AI 客户端)
└── 自研 Agent(MCP Java SDK / LangChain4j 客户端)
版本坐标再强调一次:本文的写法基于 Spring AI 2.0.1 + MCP Java SDK 2.0.x(2025-11-25 规范)。MCP 规范已在 2026-07-28 完成史上最大升级------移除握手、协议级会话彻底消失,Java 服务端生态尚未跟进、客户端侧 LangChain4j 已先行。