08-platform-connectors.md 5.6 KB

08 内部平台数据接入:MCP Gateway

状态:MCP Gateway 已上线(2026-10-23,M2 ②),承载内置工具;第三方 MCP 服务(远程 HTTP / 本地进程)可在后台"MCP 服务"页面添加,经网关转接(2026-10-24,ADR-0021)。工具返回的嵌入资源(导出文件等)一律存入工作区、只返回路径。已有 MCP 的平台优先用这种方式接入;需要定制逻辑时再写连接器(§3,仍暂缓)。

已实现:

  • POST /mcp:无状态 Streamable HTTP(@modelcontextprotocol/sdk 1.32.1),每个请求按令牌绑定到对应 Run 的上下文
  • 运行令牌:RunManager 每个 Run 签发(rt_ + 32 字节随机数),Run 结束即作废,24 小时兜底过期
  • 工具:convert_document、recalc_workbook(原 Claude 进程内工具迁移至此)、web_fetch、kb_list_roots;只读模式下生成文件的工具被拒绝;每次调用写入审计(gateway_call)
  • web_fetch:undici 自定义 lookup 在连接层拒绝私网 / 回环 / 链路本地 / CGNAT 地址(防 DNS 重绑定),每跳重定向重新校验;HTML 转 Markdown(去除 script/style),超过 40 KB 截断并把全文存到工作区 .agent/fetch/,PDF 等二进制下载到工作区;单次 3 MB、20 秒上限
  • Claude:mcpServers.gateway = { type: 'http', url, headers },网关工具在 PreToolUse 中直接放行(校验在网关内)
  • Codex:thread config mcp_servers.gateway = { url, http_headers, default_tools_approval_mode: 'approve', tool_timeout_sec: 300 },令牌随 thread/start / resume 每次更新
  • 原 §5 规划的 ask_user 不再需要:Codex 原生支持 item/tool/requestUserInput

1. 目标

让 Agent 能查询(必要时操作)你的内部平台数据,同时做到:

  • 平台凭证只保存在服务端,Agent 拿不到
  • Agent 不直接访问局域网(见 06 §5)
  • Claude 和 Codex 用同一套工具定义
  • 读操作自动放行,写操作必须人工审批
  • 每次调用都有审计记录

2. 方案:服务内置的 MCP Gateway

claude CLI ──┐  MCP (Streamable HTTP)         ┌─► 平台 A API (LAN)
             ├──► /mcp (AgentServer 进程内) ───┼─► 平台 B API (LAN)
codex  ──────┘  Authorization: Bearer <run-token>  └─► 本地资料索引 / web_fetch
  • 两个引擎都支持 HTTP 类型的 MCP Server,所以只做一个 Gateway,不在引擎侧分别实现。
  • Run Token:每个 Run 签发一个短期令牌(绑定 sessionId、userId、profileId,Run 结束即失效)。Gateway 据此判断当前 Run 能用哪些工具、以谁的身份调用。
  • 不选 Claude SDK 的进程内 MCP:Codex 用不了,会导致两套实现。

3. 连接器(Connector)

一个内部平台对应一个连接器,以 TS 模块的形式放在 apps/server/src/connectors/<name>/:

export default defineConnector({
  id: 'finance',
  name: '家庭账本',
  config: z.object({ baseUrl: z.string().url(), token: z.string() }), // 后台填写,token 加密存储
  tools: [
    defineTool({
      name: 'query_transactions',
      description: '按日期范围、分类查询收支流水,返回 JSON 行',
      input: z.object({ from: z.string(), to: z.string(), category: z.string().optional() }),
      access: 'read',                       // read: 自动放行;write: 必须审批
      async run(input, { config, user, fetch }) { /* 调平台 API */ },
    }),
    defineTool({
      name: 'create_budget',
      access: 'write',
      approvalTitle: i => `创建预算:${i.name} ¥${i.amount}`,
      /* ... */
    }),
  ],
})

工具命名规则:<connectorId>__<toolName>;在引擎侧显示为 mcp__gateway__finance__query_transactions。

3.1 结果处理

  • 大结果(超过 200 行或 100KB)不直接塞进上下文:写成 workspace/.agent/data/<toolcall>.json|csv,返回摘要、行数和文件路径,再由 Agent 用 pandas 分析。这一点对数据分析场景很重要。
  • 统一错误格式,平台不可用时给出清楚的报错。

3.2 使用者身份

  • 当前不区分使用者;连接器以服务端配置的身份访问平台。以后如需区分,可以在 Run Token 中携带调用方(API Key)标识。

4. 审批在 Gateway 内完成

access: 'write' 的工具被调用时,Gateway 自己调用 Interaction Service 发起 interaction.required,挂起 MCP 请求直到人工回复,再执行或返回拒绝。

这样不依赖引擎侧的审批机制,两个引擎行为一致。引擎侧(Claude 的 canUseTool)会把 mcp__gateway__* 加入自动放行,避免重复审批。

需要注意 MCP 请求的超时:引擎侧的 MCP 工具调用超时要调大(至少不低于交互超时),具体配置项在实现时核对。

5. 内置工具(Gateway 自带)

工具 说明 期次
web_fetch 抓取 URL → 清洗为 Markdown;拦截私网地址;遵守域名黑名单 M2
kb_search 本地资料库全文检索(见 07 §4.1) M3
kb_list_roots 列出当前 Profile 可用的资料库及说明 M2
ask_user 不再需要:两个引擎都有原生提问并已接入交互卡片 —

6. 后台管理

  • 连接器列表:启用状态、健康检查(每个连接器可以实现 ping())、最近调用情况
  • 配置表单:由连接器的 config schema 自动生成,敏感字段只写不读
  • 工具列表:可以单独禁用某个工具,或把 read 工具改为需要审批
  • Profile 中勾选启用的连接器和工具
  • 调用日志:参数、耗时、结果摘要、审批记录