D3|MCP / Apps:Codex 是 client,stdio 启动即执行
先别把 Codex 当成又一个 MCP server
读完 D2,下一件错事是搜 codex mcp-server,想把整套 harness 当 stdio 工具塞进别的 agent。本仓库 CLI 的 subcommand 里 没有 McpServer(cli/src/main.rs:165-172 只有 Mcp 管理器、Plugin、AppServer)。对外嵌 Codex,走的是 app-server JSON-RPC(A1 的 Python SDK)。
判断先说清楚:Codex 的主身份是 MCP client。 它拉起或连上别人的 MCP 服务器,把工具翻成 ToolSpec 交给模型。McpHandler 只是 B2 四层里的一种 Handler,call 落到长生命周期进程,不进 Seatbelt 那套 per-command 沙箱(B3)。ChatGPT Apps / connectors 是同一条 MCP 管道上的一等服务器,名字叫 codex_apps。
1. 连接:stdio 是进程,HTTP 是地址
McpServerTransportConfig 两种(config/src/mcp_types.rs:554-585),对应 MCP 规范:
| 传输 | 配置 | 含义 |
|---|---|---|
| Stdio | command / args / env / cwd | 本机 spawn 子进程,stdin/stdout 是协议 |
| Streamable HTTP | url + 可选 bearer / headers | 连已有服务;OAuth 也走这条 |
codex mcp add NAME -- <COMMAND>... 或 --url(cli/src/mcp_cmd.rs:48-55, 92)。配置落在用户 config.toml 的 [mcp_servers.<name>];官方文档允许 受信任项目 的 .codex/config.toml 覆盖,未信任项目层本来就不会进有效配置(B5)。
stdio 的边界在 StdioServerLauncher:管「进程放哪」,不管 Seatbelt(B3 已引)。command 在 initialize 时就执行,不是等模型第一次 call。这是供应链面:装一条 npx -y 未知包 等于立刻跑别人的代码。D2 所以强调插件 MCP 的 enabled = false 必须在 spawn 前生效。
HTTP 侧:codex mcp login 只对 streamable HTTP(CLI 注释)。oauth / scopes / oauth_resource(RFC 8707)在 McpServerConfig(mcp_types.rs:264-274)。凭证进 keyring 或 CODEX_HOME(B5 的 OAuthCredentialsStoreMode),不进 git。
environment_id 默认 "local"(:22-23, 216-217)。可以指到远程 exec-server 上起 MCP——进程仍不在本机 Seatbelt 里(B6)。
没有「client 拉起 server」会怎样?每个工具变成一次 exec_command,连接和 OAuth 全裂;或者误以为 MCP 调用会进 workspace-write 的 bwrap。长进程和短命令不是一种隔离。
2. 模型看见的名字:mcp__server__tool,协议仍用原名
ToolInfo 把两套身份拆开(codex-mcp/src/tools.rs:24-45):
server_name+tool.name:打给 MCP 服务器的原名callable_namespace/callable_name:给模型看的名字,要消毒、去重、≤128 字节(:105-112)
历史前缀 mcp__(:22,handler 里同样 LEGACY_MCP_TOOL_NAME_PREFIX,handlers/mcp.rs:46-47)。命名空间是 mcp__<server>,工具是其中一项,扁平化常见写法 mcp__filesystem__read_file。prefix_mcp_tool_names 为真时加前缀,个别 server 可进 non_prefixed_mcp_tool_servers 例外(tools.rs:111-116)。
McpHandler 把这份 ToolInfo 收成 ToolSpec(handlers/mcp.rs:51-83),走 B2 的 Router。模型 call 的是 sanitised 名;真正 tools/call 用 raw 名。改名只为 Responses API 合法,不是再包装一层 RPC。
没有这层翻译会怎样?服务器工具名带空格或撞名,模型 schema 直接炸;或改了模型可见名却忘了 raw 名,call 打到错误 tool。
3. 白名单、审批、超时、截断:在 spawn 之后、模型看见之前
McpServerConfig 上的旋钮(mcp_types.rs:218-278):
| 旋钮 | 作用 |
|---|---|
enabled | false 则 不 initialize |
enabled_tools / disabled_tools | allow 先应用,再减 deny(ToolFilter::allows,tools.rs:63-95) |
default_tools_approval_mode + tools.<name>.approval_mode | AppToolApproval:Auto / Prompt / Writes / Approve(:26-33) |
startup_timeout_sec | 连上并 list tools;默认 30s(rmcp_client.rs:103) |
tool_timeout_sec | 单次 call;默认 300s(:104) |
tools.<name>.output_token_limit | 输出预算,再加 20% 序列化余量(:84-86) |
required | codex exec 下初始化失败则退出 |
supports_parallel_tool_calls | 该 server 全部工具可并行(B2 门闩) |
过滤发生在 catalog 进模型之前(filter_tools)。审批是 MCP 自己的轨,叠加 B3 的 Granular.mcp_elicitations 和 Guardian。Elicitation(服务器问用户要更多输入/OAuth)走 Event,不是 exec_command 的 Allow。
McpConnectionSet 聚合所有 RMCP 客户端(connection_manager.rs:1-6):启动状态、工具表、资源。/mcp 列出已配置工具(slash_command.rs:144)。codex mcp list|get|add|remove|login|logout 管的是 用户 config 里的 launcher 条目,不是 Session 里临时 submit 一个连接。
输出截断对齐 B4 有界:MCP 结果可以很大,必须有 token 顶,不能把一次 search 的全文当无限 fragment。
没有这些旋钮会怎样?stdio 服务器 list 出 80 个工具撑爆 prompt;一次 hang 住的 HTTP call 卡死 Turn;写操作当 Auto 放行。白名单是模型可见集,审批是同意,超时是存活——三件不同的事。
4. Apps / connectors:还是 MCP,只是有名字的那一台
内置服务器名 codex_apps(codex-mcp/src/mcp/mod.rs:63-67)。ChatGPT Apps 和 connectors 的工具走这条 MCP,再由 connectors crate 做 runtime 缓存(connectors/src/lib.rs;CodexAppsToolsCache 是它的别名,codex-mcp/src/lib.rs:33-36)。
A1 已钉:apps_route_available 要求 uses_codex_backend();API Key 路径 清空 apps(app_mcp_routing.rs:6-18)。插件若声明了 App,同名 MCP 条目会从 mcp_servers 里拿掉,避免双开(:21-27)。[features] apps 默认关(官方 config 表)。
Connectors 的 token 环境变量是 CODEX_CONNECTORS_TOKEN(mcp/mod.rs:67)。工具 meta 里可以带 connector_id / connector_name,审批弹窗用这些字段(mcp_tool_call.rs 一串 MCP_TOOL_APPROVAL_*)。不可信 connector 的 meta 键单独列出(rmcp_client.rs:109-114),避免把服务器自报的显示名当信任根。
沙箱状态可以作为 MCP capability meta 传给服务器(MCP_SANDBOX_STATE_META_CAPABILITY),不是把 MCP 进程塞进 bwrap。服务器若声称支持,可以按当前 sandbox 调整行为;不支持就当普通 RPC。
没有「Apps = 另一条 MCP」会怎样?OAuth 应用和 stdio 文档服务器各写一套 tool 协议;API Key 用户看见一排点了 401 的 App 按钮。同一条 McpConnectionSet,不同的 server_name 和鉴权门。
5. 旧 codex mcp-server 不在这棵树里;嵌 Codex 用 app-server
CLI 管理的是 外部 MCP(Subcommand::Mcp:「Manage external MCP servers」)。把 Codex 自己 当 MCP server 暴露给 Cursor/Claude 的那条 codex mcp-server,本仓库没有对应 subcommand。残留注释还提到「codex mcp-server 不一定处理 verification」(elicitation.rs:212),那是历史接收者,不是现役入口。
嵌 Codex:TUI / IDE / Python SDK 走 app-server(thread/turn JSON-RPC,A1)。CI 走 codex exec。不要把 MCP client 配置和「把 Codex 当 MCP server」搞反——前者是 codex mcp add,后者在这个版本里应该是 app-server 或官方仍文档化、但本 git 已拿掉的实验命令。有冲突以 本仓库 CLI 枚举 为准。
6. 结语:带走一句话
Codex 连 MCP 时是 client:stdio 在 initialize 就执行、HTTP 才走 OAuth;模型看见 mcp__server__tool,协议仍用原名——白名单、审批、超时、截断在进 prompt 之前做完;Apps 只是名叫 codex_apps 的那台服务器,把 Codex 嵌进别的产品请用 app-server,不要找已经删掉的 mcp-server。