企业级 MCP 落地:网关、鉴权与多租户
你是公司基础平台的负责人。公司里有几十个内部系统——订单、用户、支付、风控、BI、客服,各业务线的 Agent 团队都想把这些能力接进去。三个月后你发现:同一套”查订单”逻辑被 5 个团队各写了一遍;数据库密码和第三方 AK 散落在每个团队的 Agent 配置里;一个实习生写的 Agent 把生产库查崩了,没人说得清是谁干的;合规来要访问日志,你交不出来。
上一篇《MCP 全面解析》讲的是”怎么接一个工具”——那是单机视角:一个 Host 连一个 Server,协议怎么握手、消息怎么流转。但企业落地时,真正的重头戏不是协议,而是一堆工具 + 一堆团队怎么被统一管起来。
这里有一个当下很真实的争议:MCP 管了”工具调用”,但谁管”传输与治理”? MCP 规范本身只定义了”工具怎么描述、怎么发现、怎么调用”,至于注册发现、路由、鉴权、限流熔断、多租户隔离,规范一个字都没写——于是各家都在造自己的轮子,标准还没收敛。这就是”工具中台 / 聚合网关”要补的那一层。
这篇文章会用与之前 Dubbo、ZooKeeper、Apollo 系列相同的深度剖析风格,借 Apollo/网关的分布式经验,回答三个问题:
- 为什么:单机 MCP 在企业里会撞上什么墙,网关要解决什么。
- 怎么做:注册发现、路由、OAuth 2.1 鉴权、限流熔断、多租户隔离,五大能力如何落地。
- 代码:用 Spring AI + Spring Cloud Gateway + Spring Security + Resilience4j 给出关键实现代码。
每个治理能力,我都会先用一个真实企业的坑开场(匿名化,但都是互联网公司每天都在发生的事),再讲解法。
一、为什么单机 MCP 撑不起企业场景
1.1 从 N×M 到 N×M×T
上一篇里我们讲过 MCP 解决的是 N 个工具 × M 个模型的 N×M 集成困境:每个工具实现一次 MCP,任何客户端都能接。这个结论在单机完全成立。
但企业场景多了一个维度:T(Tenant,团队/租户)。真实世界里,公司内部的系统不是 3 个,是几十上百个;用这些能力的团队也不是 1 个,是每个业务线都有。
单机: N 个工具 × M 个模型 → 解决"协议互通"
企业: N 个内部系统 × M 个业务线 Agent × 每个团队各自直连 → 失控
单机视角里,你是”一个开发者接一个文件系统工具”;企业视角里,你是”平台方给十几个团队开放几十个内部系统”。前者是 USB-C 插头,后者是整个大楼的配电网。
1.2 单机直连的五个真实坑
如果放任每个团队自己写 MCP Server、自己直连后端,你会收获下面五类问题——它们不是假设,是互联网公司上 Agent 项目时几乎必踩的坑:
| # | 坑 | 具体表现 |
|---|---|---|
| 1 | 凭据失控 | DB 密码、第三方 AK/SK、内部接口 token 散落在各团队 Agent 配置里,无法统一轮换、无法吊销 |
| 2 | 权限失控 | 模型能调的边界没人统一收口,”最小权限”无从谈起;一个只该读数据的 Agent 拿到了写权限 |
| 3 | 流量失控 | Agent 会”死循环”——模型误判反复调同一个工具,瞬间把下游服务打挂;且一个租户的失控流量会拖垮所有人 |
| 4 | 审计缺失 | 谁在什么时候调了什么工具、传了什么参数、改了哪些数据,没有记录,合规/排障两头都交不了差 |
| 5 | 重复建设 | 同一套系统能力被不同团队重复包装,工具清单碎片化、命名冲突、口径不一致(”查订单”有五个版本) |
1.3 缺的不是协议,是控制面
这五类问题的共同点:MCP 协议解决不了它们,因为它们根本不在协议层。MCP 定义的是”工具怎么描述、怎么调用”,而这些问题属于”治理”——谁来注册、谁能调、调多少、调了怎么查。
这不陌生。回看我们熟悉的世界:
| 领域 | “协议”管什么 | “治理”管什么 |
|---|---|---|
| RPC | Dubbo 定义服务怎么调 | 注册中心(ZooKeeper/Nacos)管服务发现,网关管鉴权限流 |
| 微服务 | HTTP/REST 定义接口怎么调 | Service Mesh 管流量、可观测、安全 |
| 配置 | Apollo 定义配置怎么读 | Admin Service/Meta Server 管发布、发现、高可用 |
| AI 工具 | MCP 定义工具怎么调 | ?——目前是空白,各家造轮子 |
结论一句话:MCP 生态现在是一堆 Server 裸奔,缺一层统一的控制面与数据面。补上这层的东西,业内叫它 MCP 网关,或更直白一点——工具中台。
二、MCP 网关的整体架构
2.1 控制面与数据面分离
一个能落地的网关,第一件事是分清两条链路,别把它们糊在一起:
| 链路 | 干什么 | 频率 | 类比 Apollo |
|---|---|---|---|
| 数据面 | 转发请求、执行鉴权、限流熔断、租户路由、审计 | 每个请求必经,高频 | Config Service(配置读取) |
| 控制面 | 工具注册、元数据管理、路由规则、配额策略、权限配置 | 低频管理操作 | Portal + Admin Service(配置发布) |
这个划分是 Apollo 给我们的最直接经验:读的链路和管的链路要分开,数据面必须无状态、可水平扩展,控制面才能做审批、版本、灰度这些”慢操作”。MCP 在 2026-07-28 版本走向无状态化,恰好让数据面网关可以像普通 HTTP 服务一样随便扩容——这是做网关的天时。
2.2 分层架构
┌────────────────────────────┐
│ 业务 Agent / Host(多租户) │
│ 租户A 租户B 租户C … │
└────────────┬───────────────┘
Streamable HTTP + OAuth 2.1
┌────────────▼───────────────┐
│ MCP 网关(数据面) │
│ ① 鉴权 → ② 路由 → ③ 限流熔断 │
│ → ④ 租户隔离 → ⑤ 审计 │
└────────────┬───────────────┘
内网转发(mTLS / 内网)
┌────────────────┬──────────────┴──────┬────────────────┐
▼ ▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌─────────┐ ┌──────────┐
│ MCP Server│ │ MCP Server│ │ 原生 API │ │ 数据库/ │
│ (订单) │ │ (用户) │ │(消息/缓存)│ │ 第三方 SaaS│
└─────────┘ └───────────┘ └─────────┘ └──────────┘
▲
┌─────────────┴──────────────┐
│ 控制面:工具注册中心 / 路由规则 │
│ / 租户策略 / 配额管理 │
└─────────────────────────────┘
两个要点:
- 后端形态可以异构。网关后面接的不一定都是标准 MCP Server,也可以是还没改造的原生 API、数据库、第三方 SaaS——网关负责把”标准 MCP 请求”翻译/转发到”各种真实后端”。这让存量系统不用一上来就全面 MCP 化,落地阻力小很多。
- 网关是唯一的策略执行点。租户、权限、配额这些策略只在网关落地一次,后端 Server 不需要各自实现一遍治理逻辑——这正是”中台”的价值:把治理从 N 个后端里抽出来,收敛到一处。
2.3 网关要补的五大能力
后面五章逐一展开,先给一张总览:
| 能力 | 回答的问题 | 一句话定位 |
|---|---|---|
| 注册发现 | 有哪些工具?在哪?还活着吗? | 工具从”硬编码”到”动态可发现” |
| 路由 | 这个调用该打到哪个后端实例? | 把”调哪个工具”翻译成”调哪个实例” |
| OAuth 2.1 鉴权 | 你是谁?你能调这个工具吗? | 从”有 token”到”最小权限” |
| 限流熔断 | 调用量失控、下游挂了怎么办? | 保护下游,也保护模型自己 |
| 多租户隔离 | 租户之间会不会互相看见/互相影响? | 企业落地的生死线 |
2.4 业界实证:控制面/数据面分离已被验证
这套”数据面 + 控制面”不是纸上谈兵,阿里系开源方案落地的就是同一张图:Higress 当 MCP Proxy(数据面),Nacos 当 MCP Registry(控制面)。Higress 负责流量转发与治理,Nacos 负责注册后端服务、维护 MCP 工具元数据(见《深度解析 Higress + Nacos 在 MCP Server 部署中的高可用、热更新与鉴权方案》)。
三、注册发现:工具从”硬编码”到”动态发现”
真实案例:某出行平台的 60+ 工具”没人管”。 平台部把订单、车辆、计价、地图等能力都包装成了 MCP Server,但没有统一登记。新 Agent 团队接入时,要一个个去 wiki 翻、去群里问”订单工具的内网地址是什么、参数格式谁定的”;更糟的是,某个工具下线了没人通知,一堆 Agent 还在傻傻地调,报错刷屏。
3.1 注册的粒度:不只注册 Server,要注册到”工具”
网关的注册中心,注册的最小粒度不是 Server,而是 Tool。原因很直接:后面所有的治理动作——路由、鉴权、限流——最终都落在单个工具上。
Server 级: name=order-server, url=http://order.internal:8081, 协议版本, 健康状态
Tool 级: name=query_order, server=order-server,
inputSchema={...}, 注解=[readOnlyHint],
租户白名单={A,B}, 限流=100 QPS, scope=order:read
一个 MCP Server 会暴露多个工具,但不同工具的权限要求、限流阈值、危险程度完全不同——”查询订单”和”删除订单”绝不能享受同一套策略。所以注册表必须以工具为一行。
3.2 借 Apollo 的经验
Apollo 的注册发现我们拆过:Meta Server 封装 Eureka,Config/Admin Service 注册 + 心跳,客户端通过一个 HTTP 接口拿节点清单,永远不关心背后是谁。这套思想平移过来,MCP 网关需要:
| Apollo 经验 | 映射到 MCP 网关 |
|---|---|
| Meta Server 封装服务发现细节 | 网关对 Host 只暴露一个稳定入口,后端如何变化对调用方透明 |
| 服务实例注册 + 心跳保活 | 后端 MCP Server 注册 + 健康检查,心跳失败自动摘除 |
| 客户端拿到的是一份”节点清单” | 网关维护一份”工具→实例清单”的注册表,缓存并定期刷新 |
| 配置变更推送(ReleaseMessage) | 工具上下线时,通过 MCP 的 toolsListChanged 订阅主动推给网关刷新 |
更关键的是 2026-07-28 无状态化红利:MCP 去掉会话、每个请求自描述之后,后端 Server 天然无状态、可水平扩展,注册发现不再被”会话粘滞”绑架——这在工程上省了无数麻烦。
3.3 实现要点
一个最小可用的工具注册中心,核心是一张缓存表 + 一个发现动作:
// 工具注册表条目:工具 → 后端实例 → 治理元数据
public record ToolRegistration(
String toolName, // 全局唯一的工具名,如 query_order
String serverName, // 归属的后端 Server
String baseUrl, // 后端 Streamable HTTP 地址
Map<String, Object> inputSchema, // 入参 JSON Schema
boolean readOnly, // 由工具注解 readOnlyHint 推导
Set<String> tenantAllowlist, // 租户白名单
int rateLimitQps, // 工具级限流阈值
String scope // 工具级授权 scope,如 order:read
) {}
发现动作:网关作为 MCP 客户端去拉后端的 tools/list,把结果灌进注册表缓存:
@Service
public class ToolRegistryService {
private final Cache<String, ToolRegistration> registry =
Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(5))
.build();
// 用 Spring AI 的 MCP 客户端发现某个后端的工具清单,写入注册表
public void discover(String serverName, String baseUrl, McpAsyncClient client) {
List<Tool> tools = client.listTools().block().tools();
for (Tool t : tools) {
registry.put(t.name(), new ToolRegistration(
t.name(), serverName, baseUrl,
t.inputSchema() == null ? Map.of() : t.inputSchema().toMap(),
Boolean.TRUE.equals(t.annotations() == null ? null
: t.annotations().readOnlyHint()),
Set.of(), 0, serverName + ":" + t.name()));
}
}
public ToolRegistration lookup(String toolName) {
ToolRegistration r = registry.getIfPresent(toolName);
if (r == null) {
throw new ToolNotFoundException("未注册的工具: " + toolName);
}
return r;
}
}
McpAsyncClient.listTools()是 Spring AI MCP 客户端最常用的一个方法:它替你把”连上后端 →tools/list→ 解析 Tool 列表”全做了。注册发现这个高频动作,一行调用就拿到后端能力清单。生产里这套逻辑会挂一个定时任务周期性重跑,配合toolsListChanged订阅做增量刷新。
3.4 业界实证:零代码扩展 Tool
Higress + Nacos 方案给出的承诺非常直白:”使用配置即可实现零代码扩展 Tool,新应用的注册、应用下面工具的扩展、工具 prompt 更新验证都能通过服务集成的可视化控制台,更新发布配置快速完成,接入方式极其简单”(出处)。注册发现的价值,就是把”接一个工具”从写代码降级成改配置。
四、路由:把”调哪个工具”变成”调到哪个实例”
真实案例:某电商大促的工具版本灰度。 订单查询工具要升一个大版本,入参 schema 变了(order_id 拆成 order_id + biz_type),老 Agent 团队还没改造完。如果一刀切切换,老团队全挂;必须按租户、按流量比例,把一部分流量灰度到新版本。
4.1 路由的输入与维度
路由要回答的问题是:请求进来了,打到哪个后端实例。它的输入不只有工具名,还有四个维度:
| 维度 | 例子 | 用途 |
|---|---|---|
| 工具名 | query_order |
最基本的归属路由:这个工具属于哪个后端 |
| 租户 | tenant_id=A |
租户分流:A 租户走专有集群、VIP 租户走高配实例 |
| 版本/灰度标签 | v2、gray |
灰度发布:按比例把流量切到新版本 Server |
| 机房/亲和 | idc=华东1 |
就近路由:优先同机房,降低跨机房延迟(Apollo 同机房优先的思路) |
4.2 网关是”策略点”,不是”转发器”
这里要纠正一个常见的理解偏差:MCP 网关不是简单反向代理,它是策略点。一个请求穿过网关时,鉴权、限流、熔断、审计全部挂在路由链上——先过策略,再决定放行到哪。
Host ──▶ 网关
│ ① 鉴权(token → 租户 → 权限)
│ ② 路由(工具名 + 租户 + 版本 → 目标实例)
│ ③ 限流(租户级 + 工具级)
│ ④ 熔断(下游健康?)
│ ⑤ 审计(记录 + 脱敏)
▼
转发到后端 Server
这五步构成一条过滤器链(Filter Chain),路由是其中一环,不是全部。下面用 Spring Cloud Gateway 落地。
4.3 实现要点
网关用 Spring Cloud Gateway 做数据面。路由规则可以直接写配置,按工具归属把请求分到不同后端:
spring:
cloud:
gateway:
routes:
- id: order-tools
uri: http://localhost:8081 # 订单工具后端
predicates:
- Path=/mcp/order/**
- id: user-tools
uri: http://localhost:8082 # 用户工具后端
predicates:
- Path=/mcp/user/**
工具级、租户级的细粒度路由(比如灰度)则写进一个 GlobalFilter,从注册表里查目标:
@Component
public class ToolRouteFilter implements GlobalFilter, Ordered {
private final ToolRegistryService registry;
// 从请求里解出工具名 + 租户,决定目标后端;结果写进 exchange attribute 供后续转发
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String tool = extractToolName(exchange); // 从 JSON-RPC body 里取 tools/call 的 name
String tenant = extractTenant(exchange); // 从 JWT claim 里取 tenant_id
ToolRegistration reg = registry.lookup(tool);
if (!reg.tenantAllowlist().isEmpty() && !reg.tenantAllowlist().contains(tenant)) {
return unauthorized(exchange, "租户无权访问工具 " + tool);
}
// 记录路由决策,供后续过滤器使用(灰度标签、目标实例等)
exchange.getAttributes().put("gw.tool", tool);
exchange.getAttributes().put("gw.backend", reg.baseUrl());
return chain.filter(exchange);
}
@Override
public int getOrder() { return 0; }
}
实际生产里,灰度会做成”配置中心下发路由表 + 网关热加载”:路由规则(谁、多少比例、切到哪个版本)放在 Apollo/Nacos 里,网关监听变更,无需重启。这也是 Apollo “配置发布 → 客户端热更新”那条链路在 MCP 网关上的复刻。
4.4 业界实证:接入点路径即路由
携程的网关里,路由的真实形态是”接入点路径”:为每个接入方(消费者)分配不同的接入点路径,一个路径关联多个模型路由,一个模型路由再关联多个后端服务做负载均衡(见《携程旅游的 AI 网关实践》)。这和本文”工具名 + 租户 + 版本 → 目标实例”的路由模型是同一个东西的工程化落地。
五、OAuth 2.1 鉴权:从”有 token”到”最小权限”
真实案例(一):实习生差点删库。 某公司给内测 Agent 配了个”全量工具包”,凭据(DB 读写账号)直接写在 Agent 的 MCP 配置文件里。实习生把这套配置拷去跑自己的实验,结果拿到了生产库的读写权限,一个误操作差点出事。
真实案例(二):只读 Agent 误调退款。 一个”客服查询助手”本应只能读订单,但因为鉴权只做到了”能不能连上 Server”,没做到”能不能调某个工具”,模型在某次对话里把”申请退款”这个工具也调了。
5.1 MCP 的鉴权现状与争议
MCP 规范在 2025-06-18 引入 OAuth 2.1,要求 Streamable HTTP Server 支持它。但规范解决的只是认证(Authentication)——”客户端是不是它声称的那个客户端、token 合不合法”,它不解决授权(Authorization)——”这个租户能不能调这个工具、能做读还是写”。授权是业务策略,必须由网关来收口。
这正好回答了开头那句”谁管传输与治理”的争议:认证是 MCP 规范给的标准,授权是网关给的治理,两者缺一不可。
5.2 认证层:OAuth 2.1 授权码 + 资源服务器校验
业务 Agent/Host 网关(Resource Server)
│ │
│── ① 授权码流程 / 客户端凭证 → 授权服务器拿 access_token
│<─ access_token(JWT,含 iss/aud/exp/scope/tenant_id)
│ │
│── ② 带 Authorization: Bearer <token> ──→│
│ │── ③ 校验签名/iss/aud/exp/scope
│<─ ④ 校验通过,进入路由 ────────────────│
几个校验点一个都不能少:
| 校验项 | 为什么 | 对应漏洞 |
|---|---|---|
| 签名/过期 | token 是否由信任的授权服务器签发、是否过期 | 伪造 token、过期 token |
iss(签发方) |
防钓鱼:token 必须来自你配置的那台授权服务器 | 用别人家的授权服务器伪造签发方 |
aud(受众) |
token 是不是发给”这个网关”的 | 拿发给别的服务的 token 冒充 |
scope |
token 携带了哪些权限声明 | 权限越界 |
5.3 授权层:工具级 scope + 危险工具默认拒绝
认证过了只是”门开了”,授权才决定”你能碰哪些工具”。这里的核心思想是把 scope 细化到工具级,并借 MCP 的工具注解做安全兜底:
粗粒度:tenant_id=A 能访问 order-server(Server 级)
细粒度:tenant_id=A 对 query_order 有 order:read,对 cancel_order 无权限(Tool 级)
兜底: cancel_order 声明了 destructiveHint → 即便有权限,也要人工确认
工具注解(readOnlyHint / idempotentHint / destructiveHint)是 MCP 协议送给网关的一份”安全元数据”——网关用它做默认拒绝:凡是 destructiveHint 的工具,一律要求额外的破坏性 scope + 人工确认。
5.4 实现要点
网关作为 OAuth 2.1 Resource Server,用 Spring Security 校验 JWT:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:9000 # OIDC 发现定位授权服务器
工具级授权写成过滤器:从 JWT 的 scope 里查”这个工具需要的最小 scope 是否被授予”:
@Component
public class ToolAuthorizationFilter implements GlobalFilter, Ordered {
private final ToolRegistryService registry;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Authentication auth = exchange.getPrincipal()
.cast(Authentication.class)
.block();
String tool = (String) exchange.getAttributes().get("gw.tool");
ToolRegistration reg = registry.lookup(tool);
boolean granted = auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(a -> a.equals("SCOPE_" + reg.scope()));
if (!granted) {
return forbidden(exchange, "缺少工具 scope: " + reg.scope());
}
// 破坏性工具:要求额外的人工确认标记(Host 侧拦截)
if (!reg.readOnly()) {
exchange.getAttributes().put("gw.requiresConfirmation", true);
}
return chain.filter(exchange);
}
@Override
public int getOrder() { return 1; }
}
这就是案例二的正解:
query_order和cancel_order是两个不同 scope,只读 Agent 的 token 只带order:read,cancel_order需要的order:cancel它没有,网关直接 403——模型连调它的机会都没有。
5.5 业界实证:Token 关联消费者 + 审批
携程的鉴权落地:访问方用 Bearer Token 认证,每个 Token 关联一个”消费者”,消费者能访问哪些服务需要申请和审批;后端服务的凭证统一存在网关里,消费者无感(出处)。有意思的是 Higress+Nacos 方案对”多 Provider 鉴权”的处理——推荐把鉴权信息透传给服务提供方自己完成,而不是把 AK 存网关,安全层面更稳妥(出处)。这两种都是真实存在的取舍:集中托管 vs 端到端透传,没有标准答案。
六、限流熔断:保护下游,也保护模型
真实案例:客服 Agent 把定价服务打挂。 某公司的智能客服 Agent,在一次模型幻觉里认定”重算价格”能回答用户问题,于是开始反复调用这个工具。由于没有限流,几十个并发把下游定价服务打挂了——而这套定价服务同时还在给真实订单出价。另一个租户的爬虫 Agent 也把共享的搜索服务打到熔断,殃及了其他所有租户。
6.1 为什么 MCP 网关必须有限流熔断
这里有个区别于传统 API 网关的关键点:传统限流防的是”人”,MCP 限流还要防”模型”。人是理智的(至少不会故意狂点),但模型会幻觉、会死循环、会在一轮 Agent Loop 里反复调同一个工具。一个失控的 Agent,就是一台天然的 DDoS 发射器。
所以 MCP 网关的限流,本质是给模型套一层”调用预算”——这是”治理”对”智能”的最后一道保险。
6.2 限流:租户级 + 工具级双层配额
| 层级 | 目的 | 例子 |
|---|---|---|
| 租户级 | 防止一个租户吃光所有资源,保证公平 | 每个租户全局 500 QPS / 并发 50 |
| 工具级 | 保护慢/贵/危险的下游 | query_order 限 100 QPS;run_sql 限 5 QPS;send_sms 按租户限 1000/天 |
配额放配置中心(Apollo/Nacos),网关热加载——这样运营可以在大促前临时调高订单工具的配额,而不需要发版。
6.3 熔断与降级
限流挡的是”量”,熔断挡的是”下游已经不行了”。当下游 MCP Server 响应变慢、超时、错误率上升,网关应该快速失败,而不是让调用方(和模型)干等:
正常: Host → 网关 → 后端(200ms 返回)
劣化: 后端错误率 > 50% / 超时率飙升
熔断: Host → 网关 → 立即返回"工具暂不可用"(不碰后端,避免雪崩)
降级: 读类工具可返回缓存兜底结果;写类工具明确失败
对 Agent 而言,”快速失败”比”长时间卡住”好得多——模型拿到一个明确的错误信息,可以调整策略;而卡住只会让 Agent 无限重试、雪上加霜。
6.4 实现要点
用 Resilience4j 的注解把限流、熔断、兜底串起来:
@Service
public class ToolInvocationGuard {
// 工具级限流:query_order 每秒 100 次
@RateLimiter(name = "tool-query_order", fallbackMethod = "rateLimitedFallback")
// 熔断:后端错误率/慢调用率超过阈值时打开
@CircuitBreaker(name = "backend-order", fallbackMethod = "circuitOpenFallback")
public Mono<String> invokeOrderTool(Mono<String> call) {
return call; // 实际调用后端 MCP Server 的逻辑
}
public Mono<String> rateLimitedFallback(String tool, Throwable t) {
return Mono.just("{\"error\": \"工具 " + tool + " 触发限流,请稍后重试\"}");
}
public Mono<String> circuitOpenFallback(String tool, Throwable t) {
return Mono.just("{\"error\": \"工具 " + tool + " 暂不可用(下游熔断)\"}");
}
}
resilience4j:
ratelimiter:
instances:
tool-query_order:
limit-for-period: 100
limit-refresh-period: 1s
timeout-duration: 0
circuitbreaker:
instances:
backend-order:
sliding-window-size: 20
failure-rate-threshold: 50 # 错误率 50% 打开熔断
wait-duration-in-open-state: 10s
这两层不要二选一,要叠着用:限流管”我主动控制你最多打多少”,熔断管”下游坏了我不再雪上加霜”。这也是我们在 Dubbo 治理里最熟的那套组合拳,只是保护对象从”服务”换成了”模型调用的工具”。
6.5 业界实证:TPM/QPM/并发三档限流 + 降级
携程的限流不是拍脑袋的一个 QPS,而是三档:Token per Minute(TPM)、Query per Minute(QPM)、并发请求数,消费者申请访问时就要填好阈值;实现上用 Redis 作中央计数器、Lua 脚本保证原子更新(出处)。降级也做实了:后端返回 4xx/5xx 时,网关自动把请求切到降级服务,而不是把错误原样抛给调用方。
七、多租户隔离:企业落地的生死线
真实案例(一):SaaS 平台越权。 某公司把 Agent 能力做成 SaaS 开放给外部客户,结果 A 客户通过构造 prompt 让模型”顺手查一下”别的租户数据,越权拿到了 B 客户的信息。
真实案例(二):财务 BU 调了 HR 工资工具。 某公司内部多个 BU 共用一个工具中台,因为工具清单对所有人一视同仁,财务 BU 的 Agent 竟然在列表里看到了 HR 的”查工资”工具,并且真的调成功了。
7.1 租户模型怎么定
租户(Tenant)是谁,取决于你的业务:内部多 BU,租户 = 业务线/团队;对外 SaaS,租户 = 客户。不管哪种,一个租户在网关里都由三元组定义:
租户 = { 命名空间(namespace) + 配额(quota) + 权限(permissions) }
- 命名空间:租户的身份标识,贯穿所有日志与策略。
- 配额:这个租户能吃多少资源(呼应上一章的双层限流)。
- 权限:这个租户能调哪些工具、每种工具什么 scope。
7.2 四层隔离
多租户隔离不是一个开关,是四层都要做,缺一层就漏:
| 层 | 隔离什么 | 怎么落 | 防的坑 |
|---|---|---|---|
| 流量隔离 | 一个租户别打爆别人 | 路由按租户分流 + 限流按租户独立 | 爬虫租户拖垮全员 |
| 能力隔离 | 一个租户别看到别人的工具 | tools/list 按租户过滤,看不见就调不了 |
财务 BU 看到 HR 工资工具 |
| 凭据隔离 | 一个租户别拿到别人的后端凭据 | 后端凭据由网关统一托管,按租户+工具绑定,Agent 拿不到明文 | 凭据满天飞、难轮换 |
| 数据隔离 | 一个租户别读到别人的数据 | 工具层强制 tenant_id 下推,跨租户访问直接拒绝 | A 客户越权读 B 客户数据 |
其中能力隔离最容易被忽视,但它是最前置的一道闸:很多越权根本不是”调的时候拦”,而是”清单里就不该出现”。MCP 的 tools/list 是模型能看到全部能力的窗口,给不同租户返回不同工具清单,就等于给模型配了不同的”视野”。
这里的隔离粒度,直接借鉴 Apollo 的 AppId + Cluster + Namespace 思想:Apollo 用这三个维度把配置空间切得清清楚楚,MCP 网关用 租户 + 工具组 + scope 把能力空间切得清清楚楚。
7.3 实现要点
租户上下文贯穿整条过滤链,从 token 里解出来、存进 exchange attribute:
public record TenantContext(String tenantId, Set<String> scopes) {
private static final String ATTR = "gw.tenant";
public static TenantContext from(ServerWebExchange exchange) {
Jwt jwt = (Jwt) exchange.getPrincipal().block();
String tenantId = jwt.getClaimAsString("tenant_id");
@SuppressWarnings("unchecked")
Set<String> scopes = new HashSet<>(jwt.getClaimAsStringList("scope"));
TenantContext ctx = new TenantContext(tenantId, scopes);
exchange.getAttributes().put(ATTR, ctx);
return ctx;
}
public static TenantContext current(ServerWebExchange exchange) {
return (TenantContext) exchange.getAttributes().get(ATTR);
}
}
能力隔离——给不同租户返回裁剪后的工具清单(聚合模式网关在 tools/list 响应里按租户过滤):
public List<Tool> visibleTools(String tenantId, List<Tool> allTools) {
return allTools.stream()
.filter(t -> {
ToolRegistration reg = registry.lookup(t.name());
return reg.tenantAllowlist().isEmpty()
|| reg.tenantAllowlist().contains(tenantId);
})
.toList();
}
凭据隔离——后端凭据托管在网关的密钥库(Vault/KMS),按”租户+工具”维度绑定,转发时才注入:
// 网关转发前,从密钥库按 (tenantId, tool) 取出后端凭据注入请求头,Agent 全程无感、无明文
public Map<String, String> backendCredential(String tenantId, String tool) {
String path = tenantId + "/" + tool; // Vault 路径:租户/工具 维度的隔离
return vaultClient.read(path); // 返回后端 token/账号,仅网关可见
}
7.4 业界实证:命名空间即租户隔离
多租户隔离最干净的工业实现,是直接复用注册中心的命名空间能力:Higress + Nacos 方案里,”利用 Nacos 的命名空间能力做到服务和工具集的隔离,给不同的用户提供不同的 MCP 工具集“(出处)。这就是本文 7.2 “能力隔离”的现成解法——租户 A 连看都看不到租户 B 的工具。
八、可观测与审计:治理的最后一块拼图
真实案例:金融合规要 180 天可追溯。 某金融科技公司要过等保/审计,要求”谁在什么时间调用了什么工具、传了什么参数、返回了什么结果”可追溯 180 天,且入参里的手机号、身份证号等敏感字段必须脱敏。团队发现——前面做得再好,没有审计日志,合规这一关就是过不去。
8.1 全链路可观测
可观测要回答”这次调用发生了什么”,三个信号缺一不可:
| 信号 | 内容 | 手段 |
|---|---|---|
| Trace | 租户 → 网关 → 后端 → 耗时的完整链路 | OpenTelemetry 埋点,traceId 贯穿 |
| Metrics | 各工具 QPS、错误率、P99 延迟、熔断状态 | Prometheus + Grafana |
| Logs | 每次调用的结构化日志(含 traceId 关联) | 网关统一打点,后端不需要各打各的 |
8.2 审计日志:append-only + 脱敏
审计日志和普通日志是两回事:审计是合规证据,必须 append-only、不可篡改、可回放。一条审计记录至少包含:
{
"timestamp": "2026-08-25T10:00:01.123Z",
"tenantId": "bu-finance",
"principal": "agent-42@bu-finance",
"tool": "query_order",
"arguments": "{\"order_id\": \"138****0012\"}", // 敏感字段已脱敏
"result": "{\"status\": \"ok\", \"rows\": 1}",
"traceId": "a1b2c3...",
"decision": "allowed", // allowed / denied / rate_limited / circuit_open
"latencyMs": 83
}
这里呼应一个我们在 DeepSeek Harness 架构里强调过的硬不变量——“模型可见 ⟺ 已记录(model-visible ⟺ logged)”。网关把它落地成:任何要放行给模型的结果,必然先落一条审计日志;反过来,任何能审计到的,都能回放出”模型当时看到了什么”。这一条同时解决了合规、排障、回放三件事。
8.3 一张治理矩阵收口
把全文五大能力 + 审计放进一张矩阵,作为网关的完整”职责清单”:
| 能力 | 防的问题 | 关键手段 | 借的分布式经验 |
|---|---|---|---|
| 注册发现 | 工具失控、死链 | Tool 级注册表 + 心跳 + toolsListChanged |
Apollo Meta Server/Eureka |
| 路由 | 打错实例、无法灰度 | 工具名+租户+版本多维度路由 | Dubbo 服务路由、网关灰度 |
| OAuth 2.1 鉴权 | 越权、凭据泄露 | JWT 校验 + 工具级 scope + destructiveHint 默认拒绝 |
微服务统一认证 |
| 限流熔断 | 模型打爆下游 | 租户级+工具级限流 + Resilience4j 熔断降级 | Dubbo 治理、Sentinel |
| 多租户隔离 | 跨租户越权 | 流量/能力/凭据/数据四层隔离 | Apollo AppId+Cluster+Namespace |
| 审计 | 合规无据 | append-only + 脱敏 + traceId | 微服务可观测体系 |
8.4 业界实证:日志进 ClickHouse、指标进 Grafana
携程的可观测链路很典型:请求日志落本地磁盘 → logrotate 滚动 → FileBeat 送 Kafka → LogStash 消费解析 → 写 ClickHouse → Kibana 查询;监控则走网关暴露的 Prometheus 接口 → Grafana(出处)。这套东西复用公司现有监控链,不需要为 MCP 另起炉灶。
九、总结与展望
9.1 核心观点
回到开头那句争议:MCP 管了”工具调用”,但谁管”传输与治理”? 这篇文章的答案是——
单机看 MCP,企业看 MCP 网关。MCP 是”插座”,网关是”配电箱 + 电表 + 门禁”。
MCP 协议解决了”工具怎么统一接入”,而注册发现、路由、鉴权、限流熔断、多租户隔离、审计,这些治理能力协议一个字都没定义,必须由网关这一层补上。把治理收敛到网关一处,后端 Server 才能保持简单、无状态、专注做工具本身。
9.2 诚实的现状
需要坦白的是:目前没有一个官方标准 MCP 网关。MCP 规范还在演进,网关这一层各家都在自研——Spring AI 生态给的是 MCP Server/Client 的组件,阿里推 Higress,云厂商(AWS/Azure)推自己的 MCP 网关,Kong、Akka、TrueFoundry 等也在做各自的 MCP 访问控制与注册中心。方案大同小异——数据面代理 + 控制面注册中心——但生态远未收敛。这正是”谁管传输与治理”之争的当下状态:协议已定,治理未定。
可以预见的收敛方向有三个:工具级授权(scope/注解)进入 MCP 规范、网关能力(限流/熔断/审计)形成最佳实践标准、MCP 网关与 Service Mesh / A2A 融合成统一的 Agent 基础设施。
9.3 最小落地清单
别一上来就上全量。企业落地 MCP 网关,按这个顺序起步,每一环都能独立见效:
- 一个网关 + 一个注册中心:先解决”工具散落、没人管”——把工具登记起来、统一入口。
- JWT 鉴权 + 工具级 scope:先解决”谁能调什么”——这是安全底线,最优先。
- 一个限流器:先解决”模型打爆下游”——用最简单的租户级限流挡住最痛的坑。
- 审计日志:先解决”出了事能查”——append-only 日志,成本低、价值高。
- 再逐步补:熔断降级 → 多租户隔离 → 灰度路由 → 可观测体系。
一句话总结
企业级 MCP 落地的关键,不是把协议用得多花哨,而是把”注册发现、路由、鉴权、限流熔断、多租户隔离”这五件事收敛到一个网关里——MCP 管”能不能调”,网关管”让不让调、调多少、调了怎么查”。