一个 @McpTool 注解,让 Claude 和 Cursor 直接调用你的 Spring 服务

本文基于 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-annotations1.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@McpToolParamdescription 是模型决定"何时调用、怎么传参"的唯一依据,把它当成写给模型的 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

浏览器会打开调试界面:

  1. Transport Type 选择 Streamable HTTP
  2. URL 填 http://localhost:8080/mcp
  3. 点 Connect------左侧日志能看到完整的 initialize 握手报文;
  4. 进 Tools 标签页,List Tools 应该列出 get_order
  5. 填入订单号,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 已先行。

参考资料

相关推荐
全栈弄潮儿2 小时前
用 AI 做代码优化:哪些建议值得采纳
aigc·openai·ai编程
薛定猫AI3 小时前
【技术干货】Claude Code多模型代理与反馈闭环:Python实现可验证的AI编程工作流
开发语言·python·ai编程
必须会一定会3 小时前
AI 编程隐私保护清单:API Key、代码上传、Agent 权限与 Git 历史排查
人工智能·git·ai编程
plainGeekDev4 小时前
外层六构件:把 Loop 放大成系统
ai编程·claude
火云牌神4 小时前
长连接与流式推送:规范 SSE / WebSocket 实现,替换无效轮询
websocket·网络协议·架构·ai编程·流式推送
码农飞哥5 小时前
RAG 翻车实测 + LangGraph Agent 实时抓取修复
人工智能·爬虫·langchain·ai编程·亮数据
9i编程5 小时前
9. AI编写的SKILL,坑我一一试过,这次我自己改写:逐行Code Review登录代码:username改名account、伪删除双键唯一,4个设计坑一次
人工智能·openai·ai编程
CodeStats5 小时前
【Java进程通信】Java进程通信系统完全指南:从ProcessBuilder底层原理到多语言实战
java·前端·python·进程·ai编程·processbuilder
sigterM先生6 小时前
Ollama + Go 搭建 AI 告警分析管道
后端·ai编程