ADR-0021 第三方 MCP 服务经 MCP Gateway 转接
- 状态:Accepted · 日期:2026-10-24 · 补充 ADR-0006
背景
两个引擎原先只连接 AgentServer 内置的 MCP Gateway。个人终端里给 Claude Code / Codex 配置的 MCP 被刻意隔离(不读取个人配置、专用 CODEX_HOME),后台也没有添加第三方 MCP 的入口。需要支持两类第三方服务:远程 HTTP 服务与本地进程(npx / uvx 等启动的 stdio 服务)。
决策
- 统一经网关转接:网关作为 MCP 客户端连接第三方服务,把它们的工具与内置工具一起提供给两个引擎。不把第三方服务直接写进引擎配置。
- 工具名为
<服务名>__<工具名>,引擎侧显示为 mcp__gateway__<服务名>__<工具名>
- 一处配置,两个引擎共用
- 密钥(请求头、环境变量)只保存在服务端,后台只写不读
- 按 Profile 开放:Profile 新增
mcpServerIds。网关按每次对话的令牌只列出、只允许调用该 Profile 勾选且已启用的服务。内置工具始终可用。
- 工具策略(后台可逐个修改),默认值:服务声明了
readOnlyHint 的工具为"直接调用",其余为"跟随权限模式"。
- 直接调用:不询问
- 跟随权限模式:需审批模式下在网关内发起审批卡片,可"本会话允许";自动模式直接调用
- 关闭:不提供
- 只读模式下只列出只读工具
- 审批与审计都在网关内完成
- 本地进程放进沙箱(bubblewrap,与 LibreOffice 转换相同的隔离思路):
- /usr、/etc 只读,宿主 Unix socket 不可达
- 独立的可写 home
data/mcp/<id>,用于 npx / uvx 缓存
- 默认断网,可按服务开放网络
- 只能访问后台额外开放的路径;不能开放服务数据目录、会话工作区和全局禁止项
- 密钥只通过环境变量传给该进程,不出现在命令行参数中
- 远程服务:Streamable HTTP,失败时退回旧版 SSE。可配置请求头(如 Bearer 令牌)。需要 OAuth 登录的服务暂不支持。
- 连接:按需建立,多个对话共用,空闲 10 分钟关闭;配置变化时重新连接。工具列表缓存在数据库中,引擎列工具时不必每次启动服务。
- 偶发网络错误:连接重试一次;只读工具调用重新连接后重试一次;写工具不自动重试,避免重复执行。
- 结果处理:超过 10 万字的文本、超过 5 MB 的图片 / 音频存入工作区
.agent/mcp/,返回路径。嵌入资源(resource,如导出的 CSV、文件)一律存入 .agent/mcp/(文件名取 URI 最后一段,重名加序号;blob 解码为二进制),结果中只返回路径、大小和 MIME,不放进上下文(2026-10-24 补充)。
- 超时:
- 第三方工具调用最长 10 分钟(有进度时顺延)
- 引擎等待网关工具的时间 = 交互超时 + 10 分钟 + 余量,Claude 用
MCP_TOOL_TIMEOUT,Codex 用 tool_timeout_sec
- Codex 刷新工具:已加载的 Codex 会话不会重新列工具。因此网关地址带上第三方工具的版本(勾选的服务、工具列表与策略的更新时间);变化时会话配置不同,触发分叉(ADR-0020),重新获取工具列表。
后果
- 后台新增"MCP 服务"页面:内置工具只读展示;第三方服务的添加、测试连接、刷新工具、逐个工具策略、启停、删除
- 本地进程以服务账号运行在沙箱中,"允许联网"后不限制目标地址;只应添加可信的 MCP 程序,建议写明版本号
- 已实测:官方示例服务
@modelcontextprotocol/server-everything(npx,沙箱内)与公开的 DeepWiki 远程服务,Claude 对话中两类工具都能调用,非只读工具会弹出审批