一、背景:AI 融入业务的”最后一公里”

当我们把 AI 引入业务系统时,绕不开一个问题:AI 怎么和现有系统对话?

目前最主流的答案是 MCP(Model Context Protocol)。AI 通过调用 MCP 工具来完成业务操作,这个方向是对的。但把 AI 引入业务系统,有两道坎绕不开。

第一道是接入成本: 业务系统的接口成百上千,但如果每个接口都要单独做 MCP 改造,不仅工作量巨大,还要动业务代码,推进几乎不可能。

第二道是调用安全: Agent 绕开了所有前端页面的逻辑控制,直接触达后端接口。接入之后,AI 会在什么时机、以谁的身份、调用哪些接口——如果这些问题没有答案,生产环境根本不敢上。

效率问题决定”能不能做”,安全问题决定”敢不敢上”。

我们的解法是:在业务网关层统一做 MCP 适配,并在同一层建立三层递进的鉴权体系。 接下来详细拆解:


二、为什么选择网关层

网关天然是所有业务请求的必经之路,它已经维护了完整的服务注册信息和接口路由规则。在网关层做 MCP 适配,意味着:

  • 零额外部署:不需要新服务,在现有网关上加插件
  • 复用现有基础设施:路由、负载均衡、熔断、监控全部复用
  • 一次接入,全局覆盖:所有已注册到网关的接口,理论上都可以转换成 MCP 工具
  • 鉴权体系统一:在同一个入口做统一鉴权,而不是每个业务系统各自把关

网关层的代价是插件复杂度上升,但相比分散建设的维护成本,这是值得的。


三、核心设计:网关 MCP 插件

3.1 MCP 服务注册

首先,我们在网关的管理服务中建设了 MCP 工具管理能力,用于定义 MCP 服务的元信息:

  • MCP 服务基础信息:供外部 Client 访问的 URL、服务介绍
  • 工具定义:每个工具的名称、参数定义、对应的业务接口

这些工具都映射到已经注册在网关上的业务接口。配置完成后,一个 MCP 服务就”存在”了,不需要额外部署任何东西。

3.2 请求转换流程

网关中新增一个 MCP 插件,处理所有 MCP 协议请求。完整的处理链路如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MCP 请求 /xxx/mcp

根据 URL path 找到对应的 MCP 服务

加载该 MCP 服务的注册信息(工具列表)

解析 MCP 请求 body,获取要调用的工具名称

根据工具名称找到对应的业务接口和业务服务

将请求 path 替换为工具对应的接口 path

将 MCP 请求 body 转换为业务接口所需的参数格式

将转发目标从 MCP Service 替换为业务 Service

完成请求转发,接收业务响应

将业务响应转换为 MCP Response 返回给 Client

整个过程对业务系统完全透明——业务服务收到的就是普通的 HTTP/RPC 请求,感知不到 MCP 的存在。这使得任何已接入网关的接口,无需修改即可成为 MCP 工具。


四、工具质量:不只是”能跑”

接口能通是基础,工具能被 AI 正确理解和调用才是关键。MCP 工具的质量核心在于两点:工具描述的语义准确性,以及工具粒度的合理性。

4.1 工具描述自动生成:从 Proto 到 MCP 工具 Schema

手工填写工具描述费时且容易与接口定义脱节。我们利用现有的 Protobuf 定义,实现了从 Proto 文件到 MCP 工具的自动推导。

整体思路是两层分离、最终合并:

1
2
3
4
5
6
7
Proto 文件
├── 类型层(结构信息) → 字段类型、嵌套关系、数组/枚举
└── 语义层(注释信息) → 描述、是否必填、枚举值含义、约束条件

合并生成 JSON Schema

符合 MCP 协议规范的工具参数定义

第一步:RPC 方法 → MCP 工具

解析 Proto 文件中的 RPC 方法定义,每个 RPC 方法映射为一个 MCP 工具:

  • tool.name ← 方法名(如 UpdateAuthorInfo
  • tool.description ← 方法注释(如 // 更新作者基础信息

第二步:Request Message → 参数 Schema(类型层)

递归遍历请求消息的所有字段,从 Proto 类型描述符自动推导 JSON Schema 类型:

1
2
3
4
5
6
7
8
9
Proto 类型        →    JSON Schema 类型
─────────────────────────────────────
string/bytes → "type": "string"
int32/int64 → "type": "integer"
float/double → "type": "number"
bool → "type": "boolean"
message(嵌套) → "type": "object", "properties": { ... }
repeated → "type": "array", "items": { ... }
enum → "type": "string", "enum": ["VAL_A", "VAL_B"]

嵌套 message 和 repeated 字段会递归处理,保留完整的层级结构。

第三步:字段注释 → 语义信息(语义层)

仅靠类型信息不够,AI 需要语义信息才能正确填参。我们通过正则匹配从字段注释中提取:

  • 字段描述:注释正文直接作为 description
  • 是否必填:识别注释中的 @required 标记
  • 枚举值含义:提取枚举常量的注释,补充每个枚举值的语义说明
  • 约束条件:识别 @min@max@maxLength 等标记,映射为 JSON Schema 的对应约束字段

注释中未提供的语义信息,由 Proto 结构本身兜底补全。

一个完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 更新作者等级
// @required author_id, level
rpc UpdateAuthorLevel(UpdateAuthorLevelRequest) returns (Response);

message UpdateAuthorLevelRequest {
// 作者 ID
string author_id = 1;

// 目标等级 @enum JUNIOR=初级 SENIOR=高级 EXPERT=专家
AuthorLevel level = 2;

// 变更原因 @maxLength 200
string reason = 3;
}

自动生成的 MCP 工具参数 Schema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"name": "UpdateAuthorLevel",
"description": "更新作者等级",
"inputSchema": {
"type": "object",
"required": ["author_id", "level"],
"properties": {
"author_id": {
"type": "string",
"description": "作者 ID"
},
"level": {
"type": "string",
"enum": ["JUNIOR", "SENIOR", "EXPERT"],
"description": "目标等级:JUNIOR=初级, SENIOR=高级, EXPERT=专家"
},
"reason": {
"type": "string",
"description": "变更原因",
"maxLength": 200
}
}
}
}

人工修正兜底

自动生成解决了”有没有”的问题,但部分场景仍需人工介入:

  • oneof 字段:多个互斥参数对 AI 语义不清晰,建议人工补充”三选一”的逻辑说明
  • 深层嵌套结构:超过两层的嵌套参数,AI 理解准确率会明显下降,可以考虑将常用子字段”打平”描述
  • 隐含业务约束:比如”author_id 必须是当前登录用户名下的作者”这类约束无法从 Proto 推导,需要人工在 description 中显式说明

系统支持对自动生成的工具进行人工二次修改,修改后的版本优先于自动生成结果,下次 Proto 更新重新导入时会提示冲突,由人工决策是否覆盖。

4.2 工具粒度设计

实践中我们遇到一个典型问题:接口的技术边界和工具的语义边界不一致

最常见的情况是:一个”大而全”的修改接口,可以修改几十个字段,对应十几种不同的业务场景。如果把它直接暴露为一个 MCP 工具,AI 面对复杂的参数列表往往不知道在具体场景下该填什么。

我们的解法是:按场景将一个接口拆分成多个垂直工具,每个工具只暴露当前场景相关的字段,但底层仍调用同一个接口。

1
2
3
4
/update_author(底层接口)
├── update_author_contact ← 修改联系方式
├── update_author_bio ← 修改个人简介
└── update_author_status ← 修改状态

好处显而易见:工具意图明确、参数简单、AI 调用准确率更高,鉴权也可以细化到每个工具维度。

对于需要多接口编排的复杂场景,我们通过原生 MCP 工具封装Skill 的方式来解决,而不是强行用单接口映射来覆盖。

4.3 工具组织:以场景为单位,而非以服务为单位

一个常见误区是:一个后端服务对应一个 MCP 服务。

我们的设计打破了这个限制——一个 MCP 服务的工具可以来自不同的后端业务服务
网关本身就维护所有服务的路由信息,工具对应的后端服务在转发时动态解析,
MCP 服务的边界与后端服务的边界完全解耦。

这样就可以以「Agent 的任务域」为单位来组织 MCP 服务,而不是受制于后端服务的划分:

1
2
3
4
5
6
7
8
9
传统做法(服务视角):
作者服务 → MCP服务A
内容服务 → MCP服务B ← Agent 需要同时接入三个
权限服务 → MCP服务C

我们的做法(场景视角):
作者服务 ┐
内容服务 ├→ 「作者管理」MCP服务 ← Agent 只需接入一个
权限服务 ┘

一个处理「作者管理」任务的 Agent,只需接入一个 MCP 服务,就能获取到跨多个后端服务的所有工具,工具组织的逻辑从「我有哪些服务」变成了「Agent 需要完成什么任务」。

结合前面提到的纵向拆分,完整的工具组织设计有两个维度:

  • 纵向:大接口 → 按场景拆成多个垂直工具(一接口 → 多个垂直工具)
  • 横向:多个后端服务 → 按任务域聚合进一个 MCP 服务(多个后端服务 → 一个MCP 服务)

两个维度结合,才能构建出真正以 Agent 为中心的工具体系。


五、鉴权体系:企业级 AI 不能没有安全护栏

为什么 AI 调用接口的风险更高

人工操作有天然的摩擦力——用户需要登录、找到页面、填写表单、点击确认,每一步都是隐性的权限过滤。

但 Agent 没有这些,它绕开了传统前端页面的所有逻辑控制,直接触达后端接口。它可以在一次对话里连续调用十个接口,操作速度和规模远超人工,而且不会因为”这个操作看起来不对劲”而停下来。

这意味着传统的”界面即权限边界”假设在 AI 时代完全失效。

对此我们设计了三层递进的鉴权体系,并引入 ABAC 来处理复杂的业务级权限判断,以及 Human-in-the-Loop 机制来对高风险操作兜底。

L1:Agent 身份验证

为每个 Agent 分配唯一 Token,确保请求来源合法,防止未授权的 AI 接入。这是最基础的一道门。

L2:用户身份 + 接口级 ACL(RBAC)

调用 MCP 工具时必须携带用户的身份认证信息。由于工具与接口一一对应,我们复用现有的 RBAC 体系——只有对该接口有访问权限的用户,才能正常调用对应工具。

RBAC 解决的是”能不能进门“的问题:操作人的角色是否有权调用这个功能接口。

L3:ABAC 细粒度鉴权

RBAC 回答的是”这个角色能不能调这个接口”——是静态的、粗粒度的控制。RBAC 能过滤掉大多数非法调用,但有一类场景它无能为力:

“只有某个作者的经纪人,或者经纪人的行政上级,才能修改其信息。”

这类权限依赖于请求中具体对象的属性,无法用静态角色描述——RBAC 在这里触到了天花板,这正是 ABAC 的用武之地。ABAC 解决的是”进门后能不能动这张桌子“的问题。

系统组件设计

我们 ABAC 鉴权体系划分了四个角色:

组件 角色 核心职责
PEP(网关) 策略执行点 拦截请求、提取身份、封装鉴权请求包、执行最终放行或拦截
PDP(鉴权服务) 策略决策点 解析 Policy、调度属性获取、执行规则判定、返回 Allow/Deny
PIP(业务服务) 策略信息点 提供标准化属性查询接口,只吐数据,不判断权限
PAP(管理后台) 策略管理点 配置和分发 YAML 格式的鉴权策略

声明式属性依赖:一次请求,收齐所有属性

ABAC 最容易出现的性能陷阱是:鉴权时反复向业务服务请求属性,每获取一个属性发一次请求,形成串行调用链。

我们的解法是声明式属性依赖:在 Policy 中预先声明这条策略需要哪些属性、从哪里获取,鉴权服务在执行前一次性并行拉取所有依赖属性,不做级联请求。

Policy 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
policy_id: "author_sensitive_data_policy"
target:
resource_type: "author"
actions: ["view_resume"]

# 显式声明属性依赖,鉴权服务据此并行拉取
data_requirements:
- attribute: "resource.director_id"
provider: "AuthorService"
- attribute: "resource.director_id.manager_chain"
provider: "HRService"

rules:
- id: "is_director_self"
description: "操作人是该作者的经纪人"
condition:
operator: "equals"
args: ["$.subject.id", "$.resource.director_id"]
effect: "ALLOW"

- id: "is_director_manager"
description: "操作人是经纪人的行政上级"
condition:
operator: "in"
args: ["$.subject.id", "$.resource.director_id.manager_chain"]
effect: "ALLOW"

default_effect: "DENY"

完整请求链路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
① Agent 发起 MCP 请求(携带用户身份)

② 网关 MCP 插件
- 根据 tool_name 查找映射(resource_type、action、resource_id_path)
- 用 JSONPath 从请求 body 提取 resource_id
- 封装标准鉴权请求包:{subject_id, action, resource_type, resource_id}

③ RBAC 预校验(静态鉴权)
- 检查用户角色是否有权调用该接口
- 不通过 → 直接 403,终止

④ ABAC 动态鉴权
- 鉴权服务读取 Policy 的 data_requirements
- 并行向各 PIP(业务服务)拉取所需属性
- 任一核心属性获取失败 → Fail-Close,直接拒绝
- 构建 Evaluation Context,执行规则判定

⑤ 返回结果
- ALLOW → 放行请求至业务接口
- DENY → 返回策略名称(不暴露具体原因)

业务零侵入:PIP 只吐数据,不判逻辑

业务服务只需实现一个通用属性查询接口:

1
2
3
POST /v1/resource/attributes
入参:resource_id, required_attributes[]
出参:attributes: { key: value }

业务系统完全不需要理解鉴权逻辑。权限规则的变更(例如:扩展到 GM 也可以操作),只需修改 Policy,无需修改任何业务代码。

新增工具不需要改代码

当需要上线新的 MCP 工具时,流程是:

  1. 在 MCP 管理服务配置映射:audit_article → {resource_type: article, action: audit}
  2. 在鉴权服务写一条 Policy
  3. 网关自动生效

全程不需要修改网关代码,也不需要修改 Agent 的 Prompt。

错误反馈设计

L3 鉴权失败时,返回的是不通过的策略名称(如 author_sensitive_data_policy),而不是具体失败原因。这样 AI 能从名称推断出”这个操作需要特定权限”,做出合理响应,同时不暴露”你不是 XXX 的对接人”这类业务信息。

工具风险控制

三层鉴权解决了”有没有权限”的问题,但对于高风险操作,”有权限”不等于”应该直接执行”。

因此在鉴权体系之外,我们还设计了工具风险分级机制:在注册 MCP 工具时声明风险等级(low / medium / high)和是否需要人工确认,对高风险工具在执行前暂停 Agent、向审批人发起确认,审批通过后再继续,把人工确认嵌入 Agent 的执行链路进一步降低安全风险


六、踩过的坑,说给后来者

工具描述质量决定 AI 调用准确率

从 PB 自动生成的描述解决了”有没有”的问题,但”好不好用”仍然需要人工打磨。特别是 oneof 字段、深层嵌套结构,自动生成的描述往往不够语义化,AI 容易理解偏差。工具上线前建议用真实的 Agent 调用测试一轮,发现问题及时补充描述。

接口粒度 ≠ 工具粒度

这是最容易忽视的问题。按接口 1:1 创建工具,短期省事,长期会导致工具参数复杂、AI 调用不稳定。从一开始就按语义场景拆分工具,收益远大于成本。

鉴权不能事后补

AI 调用业务接口的安全风险比人工调用更高——AI 可能在你意想不到的情况下触发敏感操作。三层鉴权体系需要在设计阶段就考虑清楚,而不是出了问题再打补丁。

RBAC 和 ABAC 要分层,不要混在一起设计

RBAC 控面(能不能调这个接口),ABAC 控点(能不能操作这个具体对象)。两者职责清晰,独立演进,互不干扰。如果把复杂的业务属性判断全塞入 RBAC 的角色定义里,维护成本会失控。


七、总结与展望

网关层 MCP 适配的核心价值在于:用基础设施的方式解决接入成本问题

业务团队不需要关心 MCP 协议细节,不需要修改现有代码,只需要在管理界面配置工具映射,就能让 AI 访问业务能力。这种”一次建设,全局受益”的模式,是 AI 在企业场景大规模落地的基础之一。