ARTICLE · 1042883
把 Spring AI 的 @Tool 一键暴露成 MCP Server:我用它让 30 个老接口变成 工具
把 Spring AI 的 @Tool 一键暴露成 MCP Server:我用它让 30 个老接口变成 AI 工具
💡 提示
篇5 · 干货教程 · 字数:约 3600 字 | 阅读时长:10–12 分钟
上周团队里为了一个概念吵了一整周:Function Calling 和 MCP 到底什么关系?
有人说"MCP 就是 Function Calling 换了个名字",有人说"MCP 是新一代 Function Calling"。
吵到最后,我把两者的关系画成了一张图,会议室瞬间安静了。
这篇文章,我把那 10 张图画成 1 张讲清楚,然后带你从一个真实的 30 接口老系统出发,用 Spring AI 2.0 把它在半小时内改造成 AI 可直接调用的 MCP 工具集——包括权限、限流、审计怎么原样保留。
一、先把概念理清:Function Calling ≠ MCP
这是最容易搞混的一对概念。一句话说清:
概念本质类比
Function Calling模型的能力一个人"会说中文"
MCP工具与客户端的传输协议"USB-C 接口"标准
Function Calling 解决的是:让 LLM 能"决定调用某个函数",并输出结构化参数。
MCP 解决的是:让"工具"能被任意支持 MCP 的客户端(Claude Desktop、Cursor、你自己的 Agent)发现和调用——不用为每个客户端写一套对接。
关键认知:一个 @Tool 方法 = 两件事
java
@Tool(description="查询订单状态")publicStringqueryOrder(StringorderNo) { ... }
加上 MCP 的支持后,这一个方法同时是:
—本地工具:你当前的 Spring 应用内,LLM 可以直接调(Function Calling)
—MCP Server 端点:通过网络暴露出去,Claude Desktop、Cursor 等外部客户端也能调(MCP)
💡 提示
用一句话总结:Function Calling 是"让模型会调工具",MCP 是"让工具被全世界用到"。 前者是模型能力,后者是生态协议。
理解这一点,你就明白为什么 2026 年 MCP 会火——它是"AI 时代的 USB-C",一次实现,处处可用。
二、传输方式选型:STDIO vs SSE vs Streamable HTTP
MCP 支持三种传输方式,选错了会踩坑。一张表说清:
传输方式适用场景优点缺点
STDIO本地工具(同机进程)零网络开销、配置简单只能本机、不适合服务化
SSE旧版远程方案兼容早期客户端⚠️ 已被 MCP 规范标记为 deprecated
Streamable HTTP生产环境首选支持无状态、可水平扩展、走标准 HTTP需要 HTTP 基础设施
选型建议:
—本地开发 / 单机工具 → STDIO(比如文件系统、Git 操作)
—生产环境 / 服务化 / 多客户端共享 → Streamable HTTP
—除非有历史包袱,别再选 SSE(新规范里它是过渡方案)
三、从 0 到 1:写一个 MCP Server
依赖
xml
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
三种注解:Tool / Resource / Prompt
Spring AI 2.0 提供了三个注解,把 Spring Bean 直接暴露成 MCP 能力:
① @McpTool —— 暴露"可执行的动作"
java
@ComponentpublicclassOrderMcpTools {@McpTool(description="根据订单号查询订单状态,返回 待发货/已发货/已签收")publicStringqueryOrderStatus(@McpToolParam(description="订单号,例如 A12345") StringorderNo,@McpToolParam(description="用户ID,用于权限校验") StringuserId) {// 复用已有的 Service,权限校验在里面returnorderService.getStatus(orderNo, userId); }}
② @McpResource —— 暴露"可读取的数据"
java
@McpResource(uri="order://{orderNo}/detail",name="订单详情",description="读取指定订单的完整信息(JSON)")publicStringgetOrderDetail(StringorderNo) {returnjson(orderService.findDetail(orderNo));}
③ @McpPrompt —— 暴露"Prompt 模板"
java
@McpPrompt(name="order-complaint", description="生成订单投诉话术")publicStringcomplaintPrompt(@McpPromptParam(description="订单号") StringorderNo) {return""" 请以客服身份,针对订单 %s 生成一段专业、有同理心的投诉处理话术。 要求:先共情、再说明、最后给方案,不超过 150 字。 """.formatted(orderNo);}
自动生成的 JSON Schema
你不需要手写 JSON Schema——Spring AI 会根据方法签名自动生成:
json
{"name": "queryOrderStatus","description": "根据订单号查询订单状态,返回 待发货/已发货/已签收","inputSchema": {"type": "object","properties": {"orderNo": { "type": "string", "description": "订单号,例如 A12345" },"userId": { "type": "string", "description": "用户ID,用于权限校验" } },"required": ["orderNo", "userId"] }}
💡 提示
这是 MCP 最爽的一点:@McpToolParam 的 description 会自动变成 LLM 判断"要不要调"的依据。写清楚描述 = 工具好用的一半。
启动
yaml
spring:ai:mcp:server:name: order-mcp-serverversion: 1.0.0protocol: STREAMABLE_HTTP # 生产推荐
java
@SpringBootApplicationpublicclassMcpServerApplication {publicstaticvoidmain(String[]args) {SpringApplication.run(McpServerApplication.class, args); }}
启动后,MCP 端点默认在 /mcp(Streamable HTTP),任何 MCP 客户端都能连。
四、实战:30 个老接口,半小时改造
真实场景:一个运营后台系统,有 30 个 REST 接口(查询订单、改地址、发优惠券、退款…)。
以前要让 AI 调用它们,得给每个接口写一遍 Function Calling 的包装。现在:
改造前 vs 改造后
项目改造前改造后
代码量每个接口写一个 Function 包装(~30 行)加 2 个注解(~2 行)
暴露范围只能本应用内 LLM 调用任何 MCP 客户端都能调
维护接口变了要改两处改一处
总工时预计 2 天半小时
改造姿势
java
// 改造前:一个普通的 MVC Controller@GetMapping("/order/{orderNo}")publicOrderVOgetOrder(@PathVariableStringorderNo, @RequestHeaderStringuserId) {returnorderService.getOrder(orderNo, userId);}// 改造后:加注解,变成 MCP Tool(原接口保持不变)@McpTool(description="查询订单详情")publicOrderVOgetOrder(@McpToolParam(description="订单号") @PathVariableStringorderNo,@McpToolParam(description="用户ID") @RequestHeaderStringuserId) {returnorderService.getOrder(orderNo, userId);}
一个接口,两种消费方式(HTTP + MCP),共用同一套 Service 和校验逻辑。
权限、限流、审计怎么保留?
这是企业最关心的三个问题。答案是:用 Advisor 链,一行不动就带过去了。
java
@ConfigurationpublicclassMcpSecurityConfig {@BeanpublicMcpServerFeatures.SyncToolSpecificationsecuredTools(OrderMcpToolstools, AuthServiceauth, RateLimiterlimiter, AuditLogaudit) {// 1. 权限:校验 userId 是否有权访问该订单// 2. 限流:每用户每分钟 N 次// 3. 审计:记录谁在什么时候调用了什么工具、参数、结果 ... }}
关键原则:MCP 只是"多了一个调用入口",业务逻辑、权限、审计一行都不用改——因为它们都在 Service 层,被 HTTP 和 MCP 共享。
💡 提示
反过来说:如果你发现改造时要复制一遍权限逻辑,说明你的分层有问题,趁这个机会修掉。
分布式注册:Nacos MCP Registry
如果 MCP Server 有多个实例,可以用 Spring AI Alibaba 的 Nacos MCP Registry 统一注册与发现:
yaml
spring:ai:alibaba:mcp:nacos:server-addr: ${NACOS_ADDR}service-name: order-mcp-server
好处:客户端只需要知道 Nacos 地址,不用维护一堆 MCP Server 的 URL;还自带负载均衡。
存量 Spring Cloud / Dubbo 应用甚至能零代码改造发布为 MCP 服务。
五、客户端怎么接
方式 1:Spring AI MCP Client(自己写 Agent)
xml
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId></dependency>
yaml
spring:ai:mcp:client:streamable-http:connections:order-server:url: http://localhost:8080/mcp
之后模型就能直接调用远端 MCP Server 的工具了:
java
Stringanswer=chatClient.prompt() .user("帮我查一下订单 A12345 的状态") .call().content();// 模型自动调用 order-mcp-server 的 queryOrderStatus
方式 2:Claude Desktop / Cursor 直连
在 Claude Desktop 的配置文件里加一段即可(Streamable HTTP):
json
{"mcpServers": {"order-server": {"url": "http://your-host:8080/mcp" } }}
重启后,你在 Claude 里说"查一下订单 A12345",它就会调用你写的工具——不用写一行客户端代码。
这就是 MCP 的威力:你实现一次,所有支持 MCP 的 AI 客户端都能用。
六、避坑清单
#坑后果解法
1还在用 SSE未来不兼容换 Streamable HTTP
2@McpToolParam 描述为空LLM 不会调用描述写清楚用途和格式
3工具方法无权限校验越权风险复用 Service 层校验,参数带 userId
4工具无幂等性重复调用出问题危险操作加二次确认或幂等键
5暴露了删除/转账类工具安全事故只暴露读操作,写操作必须人工确认
6没做限流被刷爆Advisor 链加限流
7无审计日志出事查不到记录工具调用全链路
8工具粒度太细LLM 选择困难按业务动作聚合(一个工具做一件事)
第 5 条最重要:永远不要把危险操作直接暴露成 MCP Tool。MCP 是"人人可调"的,一个 deleteOrder 工具暴露出去,等于给全世界的 AI 开了后门。
七、完整代码仓库
本文的 MCP Server 示例代码已开源:
💡 提示
https://github.com/java-ai-in-action/mcp-in-action 含:@McpTool / @McpResource / @McpPrompt 三种注解示例、Streamable HTTP 配置、权限+限流+审计 Advisor、Claude Desktop 接入配置
下一篇预告:篇6《我用 Spring AI Alibaba + Nacos 搭了个企业级 Multi-Agent,老板看完沉默了》——我会用 4 个 Agent(Planner / Executor / Reviewer / Summarizer)重构一个客服系统,讲清楚多 Agent 怎么协同、上下文怎么管理、生产级稳定性怎么做。
👇 觉得有用就关注我,下一篇讲企业级 Multi-Agent 实战。
💡 提示
本文为「Java AI 实战派」系列第 5 篇,共 10 篇,不定时更新。 上一篇:篇4 ·《RAG 准确率从 32% 干到 89%:重写切片、混合检索与 Rerank 的全流程》 下一篇:篇6 ·《我用 Spring AI Alibaba + Nacos 搭了个企业级 Multi-Agent,老板看完沉默了》
💡 提示
版本说明:本文基于 Spring AI 2.0 GA 与 MCP 1.0 规范编写,具体 API 与版本号以官方仓库为准。
本文由公众号原创 · 转载请注明出处