Codex 的 MCP 集成方案
Codex 的 MCP 集成方案
学习目标
- 理解 Codex 的 MCP 客户端聚合架构
- 掌握 Codex 作为 MCP 服务器的实现方式
- 分析 Codex 的传输层支持和会话恢复机制
前置知识
本章涉及 MCP 协议的通用原理,建议先阅读:
下文假设你已理解上述概念,直接聚焦 Codex 的具体实现。
项目实践
MCP 客户端架构
Codex 的 MCP 客户端分布在三个 crate:
codex-mcp:聚合层(连接管理、工具聚合)rmcp-client:底层协议实现codex-mcp-server:Codex 作为 MCP 服务器
McpConnectionManager
codex-mcp 中的 McpConnectionManager 是 MCP 客户端的管理中枢:
pub struct McpConnectionManager { clients: HashMap<String, AsyncManagedClient>, // 按服务器名索引}启动流程:
new()并行启动所有启用的 MCP 服务器(通过JoinSet收集结果)- 每个服务器启动后发送
McpStartupCompleteEvent - 启动期间返回缓存的
startup_snapshot工具列表
工具聚合:
list_all_tools():收集所有服务器的工具,执行模型可见名称归一化- 去重逻辑:同名工具通过截断至 64 字节并附加 SHA1 后缀来区分
- 非
codex-apps服务器的 connector 元数据被剥离,防止不可信服务器伪装
传输层实现
rmcp-client 支持三种传输:
Stdio 传输:
LocalStdioServerLauncher:本地子进程启动 MCP 服务器ExecutorStdioServerLauncher:通过ExecBackendAPI 在远程启动,stdin/stdout 通过 executor 转发
Streamable HTTP:
- 基于 SSE 的 HTTP 传输
- 支持
Mcp-Session-Id会话管理 - OAuth 2.0 自动刷新:通过
AuthClient包装请求
会话恢复:
- 遇到
SessionExpired404时自动重建 transport - 使用
session_recovery_lock防止并发恢复 - 恢复后重新
initialize并拉取工具列表
Codex 作为 MCP 服务器
mcp-server crate 将 Codex 自身暴露为 MCP 服务器:
暴露的工具:
codex:启动新会话,参数包括prompt、model、sandbox、approval_policycodex-reply:基于thread_id继续已有会话
通信方式:JSON-RPC over stdio,使用 rmcp 库处理协议帧。
事件流:
- Codex 内部事件(
ExecApprovalRequest、ApplyPatchApprovalRequest、TurnComplete) - 通过自定义通知
codex/event推送给 MCP 客户端 - 实现方式:
MessageProcessor处理标准 MCP 方法,工具调用在独立 Tokio task 中运行
问题与规避
MCP 服务器启动失败
Codex 的对策:
- 每个服务器独立启动,失败不影响其他服务器
AsyncManagedClient内部使用Shared<BoxFuture>实现懒加载- 启动失败的服务器标记为不可用,后续工具调用跳过
工具名称冲突
Codex 的解决方案:
- 截断至 64 字节(模型限制)
- 附加 SHA1 哈希后缀保证唯一性
- 提供
ToolFilter白名单/黑名单控制
会话过期
Codex 的自动恢复:
- HTTP 传输层检测
SessionExpired404 - 自动重建 transport 并重连
- 通过
session_recovery_lock串行化恢复操作 - 恢复后重新拉取工具列表(可能已变更)
设计取舍
为什么 Codex 同时作为 MCP 客户端和服务器?
这种双向集成实现了 Agent 生态的互操作性:
- 作为客户端:Codex 可以调用外部工具(如数据库查询、API 调用)
- 作为服务器:Claude Desktop、IDE 等可以调用 Codex 的能力
- 这是 MCP 协议的核心理念——任何 Agent 既可以消费工具,也可以提供工具
代价是增加了架构复杂度,需要同时维护两套 MCP 相关代码。
为什么使用 rmcp 库而非自研协议?
Codex 选择使用官方的 rmcp Rust SDK 而非自研 MCP 协议实现。优势是:
- 与 MCP 标准保持同步
- 减少维护成本
- 生态兼容性好
代价是对 rmcp 的依赖意味着必须跟随其版本升级,且某些定制化需求可能受限。