开发实践 / Spring AI 实战

Spring AI 2.0 MCP:一个 @McpTool,把订单服务开放给 Agent

用 @McpTool 把 Java 订单服务开放给 Agent,再通过 MCP Client 发现与调用工具。

大家好,我是一言,继续更新 Spring AI 2.0 系列。

前面写 Tool Calling 时,我们把 @Tool 方法直接注册给了当前应用的 ChatClient。

实际项目里,订单、库存和会员往往分属不同服务。客服 Agent、运营助手和其他 AI 客户端都想查订单,总不能每个项目都复制一份 Java 工具类。

MCP 可以把这个能力变成标准服务。客户端先发现工具,然后按照 JSON Schema 组装参数并调用。

Spring AI 2.0 里,现有 Spring Service 加上 @McpTool,再引入 MCP Server Starter,就可以走通这条链路。

先看运行结果#

我用 Java 21、Spring Boot 4.0.8 和 Spring AI 2.0.1 写了一个订单 MCP Server。

服务启动后,Spring AI 找到了一个工具:

text
Registered tools: 1
Tomcat started on port 8081

MCP Client 调用 tools/list,拿到的核心内容是:

json
{
  "name": "queryOrder",
  "description": "Query shipping status, carrier and tracking number by order id",
  "inputSchema": {
    "type": "object",
    "properties": {
      "orderId": {
        "type": "string",
        "description": "Order id, for example A1024"
      }
    },
    "required": ["orderId"]
  }
}

客户端继续执行:

text
queryOrder({"orderId":"A1024"})

服务端返回:

json
{
  "orderId": "A1024",
  "status": "SHIPPED",
  "carrier": "SF Express",
  "trackingNo": "SF1234567890"
}

这次验证覆盖了 MCP 初始化、工具发现和工具执行。后面的 ChatClient 代码也已通过编译。真实模型能否在复杂问题中选对工具,还会受模型能力和 description 质量影响。

先把 MCP Server 开起来#

服务端引入 WebMVC Starter:

xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

然后配置服务名、端口和传输协议:

properties
server.port=8081

spring.ai.mcp.server.name=order-mcp-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.protocol=STREAMABLE

STREAMABLE 使用 Streamable HTTP,默认 MCP 端点路径是 /mcp。本文的初始化、工具查询和调用都通过 HTTP POST 完成。Spring AI 2.0 已将 SSE 标记为弃用,新的 HTTP 服务可以直接使用 Streamable HTTP。

给现有 Service 加一个注解#

订单查询的业务代码仍然放在 OrderService。MCP 这层只负责对外描述和转发:

java
import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.ai.mcp.annotation.McpToolParam;

@Service
public class OrderMcpTools {

    private final OrderService orderService;

    public OrderMcpTools(OrderService orderService) {
        this.orderService = orderService;
    }

    @McpTool(
        name = "queryOrder",
        description = "Query shipping status, carrier and tracking number by order id",
        annotations = @McpTool.McpAnnotations(
            readOnlyHint = true,
            destructiveHint = false
        )
    )
    public OrderSnapshot queryOrder(
            @McpToolParam(
                description = "Order id, for example A1024",
                required = true
            )
            String orderId) {
        return orderService.query(orderId);
    }
}

Starter 会扫描 Spring Bean 上的 @McpTool,把方法名、描述和参数转成 MCP Tool Definition。OrderSnapshot 会被序列化后放进工具结果。

这里还有个容易忽略的点:destructiveHint 默认是 true。queryOrder 只读不改数据,所以显式标记 readOnlyHint=true 和 destructiveHint=false。这些值会随 tools/list 返回给客户端,它们属于工具行为提示,权限检查仍然要在服务端执行。

required = true 也会进入 JSON Schema。我把 orderId 删掉后再调用,服务端没有进入 Java 方法,直接返回:

text
Tool (queryOrder) input validation failed:
JSON schema validation errors: 未找到所需属性“orderId”
isError=true

Spring AI 2.0 的 MCP Server 会默认按照 Schema 校验工具参数。金额范围、用户权限和订单状态仍然要由业务层检查。

Agent 应用怎么接#

另一个 Spring AI 应用引入 MCP Client Starter:

xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>

配置订单服务地址:

properties
spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.streamable-http.connections.order-server.url=http://localhost:8081

客户端启动时会建立 MCP 连接,并把发现到的工具封装成 SyncMcpToolCallbackProvider。

这个 Provider 需要显式交给 ChatClient:

java
@Bean
ChatClient orderChatClient(
        ChatClient.Builder builder,
        SyncMcpToolCallbackProvider mcpTools) {

    return builder
            .defaultTools(mcpTools)
            .build();
}

之后的调用和本地 Tool Calling 一样:

java
String answer = chatClient.prompt()
        .system("You are an order assistant. Call the order tool when order data is needed.")
        .user("帮我查订单 A1024 到哪了")
        .call()
        .content();

这条链路里,模型看到的仍然是工具名、description 和 JSON Schema。当它返回 queryOrder 调用请求时,Spring AI 会通过 MCP Client 请求订单服务,再把结果交回 ToolCallingAdvisor。

Spring AI 2.0 MCP 调用链:Agent 先发现 queryOrder,再通过 tools/call 调用订单服务
Spring AI 2.0 MCP 调用链:Agent 先发现 queryOrder,再通过 tools/call 调用订单服务

@Tool 和 @McpTool 怎么选#

常见用法 @Tool @McpTool
执行位置 当前 Java 应用 MCP Server
注册方式 生成 Spring AI ToolCallback 生成 MCP Tool 并通过协议暴露
调用范围 当前 ChatClient 已连接的 MCP Client
适合场景 当前项目的小工具 订单、库存等可复用能力

客户端拿到 MCP Tool 后,Spring AI 会把它转成统一的 ToolCallback。ToolCallingAdvisor 处理工具循环时,本地工具和 MCP 工具可以放在同一次请求中。

Spring AI 2.0 这次改了什么#

Spring AI 1.1 已经提供过注解式 MCP 集成,当时的注解模块来自 Spring AI Community。

2.0 的几个变化和这篇代码直接相关:

  1. MCP 注解并入 Spring AI,包名统一到 org.springframework.ai.mcp.annotation.*;
  2. MCP Java SDK 升级到 2.0.0,服务端默认开启工具输入 Schema 校验;
  3. WebMVC 和 WebFlux 传输模块进入 Spring AI 项目;
  4. SSE 已弃用,Streamable HTTP 成为新项目的主要选择。

从 1.x 升级时,旧的 org.springaicommunity.mcp.annotation.* import 要换成新包名,直接依赖的 org.springaicommunity:mcp-annotations 也要移除。

上线前先加鉴权#

Spring AI 的 HTTP MCP Starter 默认会暴露一个没有身份认证的 JSON-RPC 端点。只要能访问该地址,客户端就可以列出并调用已注册的工具。

本地开发可以先绑定在 localhost。部署到内网或公网前,要在 /mcp 前面加上 Spring Security、OAuth 2.0 或统一网关鉴权。

@McpTool 也要当成对外 API 看待。查询、更新、删除和付款类工具,需要分开做权限、幂等和审计。

最后#

这篇先跑通了一条最小链路:

text
Spring Service
   ↓ @McpTool
MCP Server
   ↓ tools/list + tools/call
MCP Client
   ↓ ToolCallbackProvider
ChatClient / Agent

一个订单查询方法从应用内部走到 MCP 协议层以后,同一份能力就可以交给多个 Agent 客户端使用。

下一步可以继续看两个问题:多个 MCP Server 中的工具重名怎么处理,以及用户身份怎么从 Agent 传到 MCP Tool。

参考资料#

  • Spring AI 2.0.1:Getting Started with MCP
  • Spring AI 2.0.1:MCP Server Boot Starter
  • Spring AI 2.0.1:MCP Server Annotations
  • Spring AI 2.0.1:MCP Client Boot Starter
  • Spring AI 2.0.1:Tool Calling 与 MCP
  • Spring AI 2.0:Upgrade Notes
  • Spring AI 2.0.0 GA 发布说明

公众号同名

微信搜索「雷一言说」

关注公众号,继续看我的文章。

手机阅读时,可以保存二维码后在微信中识别。

雷一言说微信公众号二维码
扫码关注公众号