夜雨聆风学习资料网

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(orderNouserId);    }}

② @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.classargs);    }}

启动后,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(orderNouserId);}// 改造后:加注解,变成 MCP Tool(原接口保持不变)@McpTool(description="查询订单详情")publicOrderVOgetOrder(@McpToolParam(description="订单号"@PathVariableStringorderNo,@McpToolParam(description="用户ID"@RequestHeaderStringuserId) {returnorderService.getOrder(orderNouserId);}

一个接口,两种消费方式(HTTP + MCP),共用同一套 Service 和校验逻辑。

权限、限流、审计怎么保留?

这是企业最关心的三个问题。答案是:用 Advisor 链,一行不动就带过去了

java

@ConfigurationpublicclassMcpSecurityConfig {@BeanpublicMcpServerFeatures.SyncToolSpecificationsecuredTools(OrderMcpToolstoolsAuthServiceauthRateLimiterlimiterAuditLogaudit) {// 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 与版本号以官方仓库为准。

本文由公众号原创 · 转载请注明出处

相关学习资料