# 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/`,用于 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 对话中两类工具都能调用,非只读工具会弹出审批