这是「华为云码道实战」系列的第九篇。前八篇覆盖了成长、观点、安全、性能、部署、数据、技术债、需求。这一篇回到架构设计——当 LLM Agent 要调用你的微服务时,REST API 不够用了,你需要 MCP。
我用 Spring Boot 3.4 + Spring AI 1.0.0-M5 + Spring Cloud 2024.0 造了一个真实可编译的项目骨架,展示如何用 MCP Gateway 模式把 SpringCloud 微服务能力暴露给 Agent。
一、问题:Agent 不会调你的 REST API
你有一套 SpringCloud 微服务:订单服务、库存服务、用户服务,通过 Nacos 发现、Feign 调用、Gateway 聚合。前端调用一切正常。
现在来了个 LLM Agent——可能是 Claude、可能是 CodeArts、可能是你自己的业务 Agent。你想让它"帮用户查订单、改库存、看积分"。问题来了:
Agent 不知道你有哪些接口、每个接口要传什么参数、返回值怎么解读。
你可能会说:"给它一份 OpenAPI 文档不就行了?" 不够。OpenAPI 描述的是 HTTP 语义,但 Agent 需要的是工具语义——"这个能力做什么、什么时候该用它、参数的业务含义是什么"。而且 OpenAPI 没有标准化的调用协议,每个 Agent 框架的对接方式都不一样。
这就是 MCP(Model Context Protocol) 要解决的问题。它是 Anthropic 在 2024 年底提出的开放协议,定义了 LLM 与外部工具/数据源之间的标准通信方式——JSON-RPC over SSE,核心是三类原语:
| 原语 | 用途 | 类比 |
|---|---|---|
| Tools | 可调用的函数 | REST POST 接口 |
| Resources | 可读取的数据源 | REST GET 接口 / 文件 |
| Prompts | 预定义的提示模板 | Prompt 模板库 |
MCP 的价值在于标准化:你实现一次 MCP Server,任何支持 MCP 的 Agent(Claude Desktop、Cursor、CodeArts 等)都能直接用你的工具,不用每个 Agent 单独对接。
二、架构:MCP Gateway 模式
核心思路:不改动现有微服务,加一层 MCP Gateway 作为 Agent 适配层。
MCP 协议 (JSON-RPC over SSE)
Agent / LLM ──────────────────────────────────► MCP Gateway
(Claude/CodeArts/...) │
│ OpenFeign
┌────────────┼────────────┐
▼ ▼ ▼
Order Service Inventory Svc User Svc
(SpringCloud) (SpringCloud) (SpringCloud)
│ │ │
└────────────┴────────────┘
│
Nacos / Eureka
MCP Gateway 的职责:
- 协议翻译:把 MCP 的
tools/call请求翻译成底层微服务的 REST 调用 - 能力声明:把微服务接口包装成带业务语义的
@Tool,Agent 能自动理解 - 认证适配:外层校验 Agent 的 API Key,内层走服务间 mTLS
- 粒度聚合:不按微服务粒度暴露,按业务域聚合(一个 Tool 类 = 一个业务域)
为什么不直接在每个微服务里集成 MCP?因为微服务是按技术拆分的,但 Agent 需要的是按业务能力聚合的接口。"创建订单"要调订单服务 + 库存服务 + 支付服务——这个编排逻辑放在 MCP Gateway 里,而不是让 Agent 自己编排三个服务。
三、真实代码:22 个文件,759 行
我造了一个完整的项目骨架(springcloud-mcp-demo/),结构如下:
springcloud-mcp-demo/
├── pom.xml # 父 POM (SpringCloud 2024.0 + Spring AI 1.0.0-M5)
├── mcp-gateway/ # MCP Server(Agent 适配层)
│ ├── pom.xml
│ └── src/main/java/com/example/mcp/gateway/
│ ├── McpGatewayApplication.java
│ ├── config/
│ │ ├── McpServerConfig.java # MCP Server 配置 + Tool 注册
│ │ └── SecurityConfig.java # API Key 认证
│ ├── tools/
│ │ ├── OrderTools.java # 订单工具(5 个 @Tool)
│ │ ├── InventoryTools.java # 库存工具(3 个 @Tool)
│ │ └── UserTools.java # 用户工具(3 个 @Tool)
│ ├── client/
│ │ ├── OrderServiceClient.java # Feign 调用订单服务
│ │ ├── InventoryServiceClient.java
│ │ └── UserServiceClient.java
│ └── model/
│ ├── Order.java
│ └── ApiResponse.java
├── order-service/ # 普通微服务(被 Gateway 调用)
│ └── src/main/java/com/example/order/
│ ├── controller/OrderController.java
│ └── service/OrderService.java
└── docs/architecture.md
3.1 MCP Server 配置:注册工具
McpServerConfig.java 是核心——它把每个 Tool 类注册为 MCP ToolCallbackProvider:
@Configuration
public class McpServerConfig {
@Bean
public ToolCallbackProvider orderToolProvider(OrderTools orderTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(orderTools)
.build();
}
// ... inventoryToolProvider, userToolProvider 同理
}
MethodToolCallbackProvider 会扫描对象上所有 @Tool 注解的方法,自动生成 MCP 工具声明(含参数 schema)。Agent 连接后拿到的 tools/list 响应,就是从这里来的。
3.2 工具声明:带业务语义的 @Tool
这是整个设计最关键的部分——工具的 description 不是技术文档,是业务说明:
@Component
public class OrderTools {
@Tool(description = "根据订单ID查询订单详情,返回订单号、用户ID、总金额、状态和创建时间。"
+ "状态含义:0=待支付, 1=已支付, 2=已发货, 3=已完成, 4=已取消")
public Order getOrderById(
@ToolParam(description = "订单ID,正整数") Long orderId
) {
return orderClient.getOrderById(orderId);
}
@Tool(description = "创建订单。需要指定用户ID、商品列表(商品ID+数量)、收货地址和支付方式。"
+ "返回创建后的订单信息。")
public Order createOrder(
@ToolParam(description = "用户ID") Long userId,
@ToolParam(description = "商品列表,JSON数组字符串,每项含 productId 和 qty") String itemsJson,
@ToolParam(description = "收货地址") String address,
@ToolParam(description = "支付方式:alipay/wechat/bank") String paymentType
) {
return orderClient.createOrder(userId, itemsJson, address, paymentType);
}
}
注意三个设计决策:
-
description 里写了状态码含义(
0=待支付, 1=已支付...)。这不是技术细节,是 Agent 理解返回值的关键——没有这个,Agent 拿到status: 2不知道是"已发货"还是"已取消"。 -
参数用业务语言描述。不写"String: The payment type",写"支付方式:alipay/wechat/bank"——Agent 看到这个就知道该填什么值,不用猜。
-
工具粒度是业务操作,不是 CRUD。暴露的是"取消订单"(带取消原因),不是"PUT /orders/{id}"。Agent 调用
cancelOrder时会自动带上 reason,而直接调 PUT 接口它可能不知道要传 reason。
3.3 服务调用:OpenFeign 声明式
MCP Gateway 通过 OpenFeign 调用底层微服务,和普通 SpringCloud 调用完全一样:
@FeignClient(name = "order-service", path = "/api/v1/orders")
public interface OrderServiceClient {
@GetMapping("/{orderId}")
Order getOrderById(@PathVariable Long orderId);
@PostMapping
Order createOrder(@RequestParam Long userId,
@RequestParam String itemsJson,
@RequestParam String address,
@RequestParam String paymentType);
@PostMapping("/{orderId}/cancel")
Order cancelOrder(@PathVariable Long orderId, @RequestParam String reason);
}
Feign + Nacos 服务发现,MCP Gateway 不需要硬编码服务地址。底层服务扩缩容、换实例,Gateway 无感知。
3.4 认证:双层防护
@Configuration
public class SecurityConfig {
@Value("${mcp.api-key}")
private String mcpApiKey;
@Bean
public HandlerFilterFunction<ServerResponse, ServerResponse> mcpAuthFilter() {
return (request, next) -> {
String apiKey = request.headers().firstHeader("X-MCP-API-Key");
if (apiKey == null || !apiKey.equals(mcpApiKey)) {
return ServerResponse.status(401).build();
}
return next.handle(request);
};
}
}
认证分两层: - 外层:Agent → MCP Gateway,校验 X-MCP-API-Key(每个 Agent 一个 Key,可轮换) - 内层:MCP Gateway → 微服务,走服务间 mTLS 或共享 JWT(SpringCloud 原生能力)
3.5 配置:一行开启 MCP Server
spring:
ai:
mcp:
server:
name: springcloud-mcp-gateway
version: 1.0.0
sse-message-endpoint: /mcp/message
Spring AI 的 MCP Server Starter 自动配置了 SSE 端点和 JSON-RPC 处理。启动后 Agent 连 http://gateway:8080/mcp/message 就能发现全部 11 个工具。
四、关键设计决策
4.1 工具粒度:粗还是细?
| 方案 | 优点 | 缺点 |
|---|---|---|
| 细粒度(1 工具 = 1 API) | 实现简单,1:1 映射 | Agent 要自己编排,容易出错 |
| 粗粒度(1 工具 = 1 业务操作) | Agent 直接用,编排逻辑在 Gateway | Gateway 有业务逻辑 |
我选粗粒度。"创建订单"是一个工具,内部编排了库存扣减 + 订单创建 + 支付发起——这个编排不该让 Agent 做,因为 Agent 可能漏掉某一步,或者执行顺序不对。
4.2 同步 vs 异步
- 查询类(getOrderById, getStock):同步返回,WebMVC 足够
- 写入类(createOrder, cancelOrder):同步返回结果,Agent 需要立即知道成功与否
- 长任务(批量导出、报表生成):返回 jobId + 单独的
getTaskStatus工具轮询 - 流式数据(日志、监控):用 MCP Resources(
resources/subscribe),不是 Tools
4.3 错误处理:让 Agent 能 self-heal
工具返回的错误信息要对 Agent 友好——不是抛个 500,而是结构化的错误码 + 可操作的建议:
{
"error": "STOCK_INSUFFICIENT",
"message": "商品 1001 库存不足",
"current_stock": 3,
"requested_qty": 5,
"suggestion": "可将数量调整为 3,或选择其他商品"
}
Agent 看到 suggestion 可以自动调整参数重试,而不是把错误抛给用户。
4.4 版本管理
MCP 协议自带版本字段(server.version)。工具变更策略:
- 新增工具/参数:兼容变更,不影响已有 Agent
- 删除工具/改参数语义:破坏性变更,需 bump version + 保留旧版本并行运行
五、MCP Gateway vs API Gateway vs BFF
这三个概念容易混淆,区分一下:
| API Gateway | BFF | MCP Gateway | |
|---|---|---|---|
| 面向 | 前端/外部 | 特定前端(Web/App) | LLM Agent |
| 协议 | HTTP/REST | HTTP/REST/GQL | MCP (JSON-RPC/SSE) |
| 关注点 | 限流/路由/鉴权 | 视图聚合/适配 | 能力声明/工具语义 |
| 粒度 | 接口级 | 视图级 | 业务操作级 |
MCP Gateway 不是替代 API Gateway,而是并列的新适配层。同一个微服务集群,可以同时有 API Gateway(给前端用)和 MCP Gateway(给 Agent 用),它们共享底层服务。
六、部署与运维
# K8s 部署要点
spec:
containers:
- name: mcp-gateway
image: registry/mcp-gateway:1.0.0
ports:
- containerPort: 8080
env:
- name: MCP_API_KEY
valueFrom:
secretKeyRef: { name: mcp-secrets, key: api-key }
- name: NACOS_ADDR
value: nacos.default.svc:8848
livenessProbe:
httpGet: { path: /actuator/health, port: 8080 }
运维要点: - SSE 长连接:K8s Service 要配 sessionAffinity,或用 Client-side LB - 超时:MCP 工具调用默认超时 60s,长任务工具要单独配 - 限流:按 API Key 限流(每个 Agent 配额不同),不是按 IP - 监控:重点看 tools/call 的 QPS、延迟、错误率,按工具名维度
七、结语
MCP 协议的出现,标志着 LLM Agent 从"聊天玩具"走向"生产力工具"的最后一公里——标准化地连接外部系统。
对 SpringCloud 用户来说,MCP Gateway 是一个低成本的增量改造:不改微服务、不改服务发现、不改现有 Gateway,加一个适配层就能让整个微服务集群"被 Agent 理解"。Spring AI 的 MCP Starter 把这个适配层的实现成本降到了——一个 @Tool 注解 + 一个 Feign Client。
系列九篇,从"用工具写代码"到"给代码装上 Agent 能听懂的接口": 成长 → 观点 → 安全 → 性能 → 部署 → 数据 → 技术债 → 需求 → MCP 架构。 越往后越是"代码之上"的设计思考,但这些设计决定了代码能不能被 AI 时代复用。下一份见。
本文完整项目骨架见 /workspace/springcloud-mcp-demo/(22 文件,759 行,Spring Boot 3.4 + Spring AI 1.0.0-M5 + Spring Cloud 2024.0)。
评论(0)