OpenAI Codex · MCP 与 Skills

MCP 接进来:模型看见翻译过的名字

外部 server 的工具要先过一层翻译才进模型眼睛。skill 目录常在,缺 MCP 时另问人。

课程目标读完能说清三件事。Codex 当 client 时,外部工具怎么变成模型可见名。两家店清洗后撞名,怎么消歧。skill 目录为什么不看 MCP 活没活着。
先玩一遍 · 一家店接进来,名字怎么变
一个 MCP server 接进来:工具怎么变成模型看得见的能力
接入场景
右边两档会撞名。切一下,看清洗之后谁被加上哈希。
门外 · 原始 tools/list进门 0
还没接任何人。
模型眼前 · 翻译后的名字可见 0
清单空着。
逻辑轨迹 · 动画每一步对应源码里的哪一段
  1. 连接集整份发布,已有 binding 继续拿自己那份连接runtime.rs L246
  2. 各家 tools/list 汇成一张表,再交给命名翻译tool_catalog.rs L153
  3. 给命名空间加上历史前缀 mcp__tools.rs L228
  4. 非法字符洗成下划线,只留字母数字和下划线mcp/mod.rs L477
  5. 完全相同的原始身份丢掉一份tools.rs L134
  6. 清洗后命名空间撞车,末尾加 12 位 SHA-1tools.rs L166
  7. 清洗后工具名撞车,同样加 12 位哈希tools.rs L193
  8. 合起来超过 128 字节就截断再哈希,协议调用仍走原名tools.rs L226
点播放,看一家店接进来之后,工具名怎么变成模型看得见的能力。
两层名字左边是协议上的原名,右边是给模型看的翻译。调回去的时候走左边,不会因为右边加了哈希就进错店。
撞名才哈希干净两家不会加后缀。连字符和工具名这两档,清洗之后才会撞,哈希是消歧,不是装饰。
自己改第二家店在连字符档把店名改成和第一家清洗后一样的字,就能看见命名空间被拆开。
教学示意:哈希取前 12 位,算法是 SHA-1,演示里用固定示意后缀。行号对应 openai/codex 仓库 commit 4f39251a01。
思路一 · 对外一份清单,对内另一份
它解决什么问题

你把 codex mcp-server 写进 Cursor 的 MCP 配置。Cursor 当 client,Codex 当 server。若这一次 tools/list 把内部 GitHub 工具一并交出去,IDE 调一次就摸到内部能力。权限边界从「调一次 Codex」扩成「直接调内部工具」。

思路是什么

crate 拆成两套。mcp-server 从 stdin 读行,一行一条 JSON。initialize 只打开 tools。tools/list 写死两个名字:codexcodex-replycodexstart_thread,nested thread 再起自己的 McpRuntimecodex-mcp 管连接集,外部 server 的工具另做一份目录。

出处:codex-rs/mcp-server/src/lib.rs 第 131 至 152 行;codex-rs/mcp-server/src/codex_tool_runner.rs 第 66 至 90 行;codex-rs/codex-mcp/src/runtime.rs 第 88 至 98 行

同一份 JSON-RPC 线协议,处理器不是同一个。早期资料常把它们画成同一个 runtime 的两张脸。当前源码里它们甚至不共享 MessageProcessor

出处:codex-rs/mcp-server/src/message_processor.rs 第 274 至 277 行;codex-rs/mcp-server/src/message_processor.rs 第 336 至 348 行

IDE 看见的入口 Cursor MCP client mcp-server codex · codex-reply nested thread 一次 tools/call 变成一条会话 会话里看见的外部店 McpRuntime 连接集可整份 replace GitHub mcp__github__* Docs mcp__docs__* 自建 HTTP mcp__http__*
教学化结构图:上面是交给 IDE 的两个入口,下面是会话内部的外部工具目录。
为什么长期成立

对外承诺和对内能力分开,是网关的通用形状。换语言也是两个函数:hosted 返回 run / continue,external 返回 mcp__*。IDE 只看见入口,会话里才看见外部店。

思路二 · 模型看见的是翻译过的名字
它解决什么问题

两家店都报 search,前缀还能分开。一家叫 basic-server,一家叫 basic_server,连字符洗成下划线之后,命名空间会撞。模型看见两个同名工具,下一次调用就不知道进哪家店。API 还有字节上限。

思路是什么

server 接进来,先把各家 tools/list 汇成一张表,再走 normalize_tools_for_model_with_prefix。顺序是固定的四步。

1. 给命名空间加上 mcp__ 前缀。

2. 非法字符洗成下划线,只留字母、数字和 _

3. 完全相同的原始身份丢掉一份。清洗后命名空间或工具名还撞,就在末尾加 12 位 SHA-1。

4. 合起来超过 128 字节,截断再哈希。原始 server_nametool.name 留在 ToolInfo 上,协议调用走原名。

出处:codex-rs/codex-mcp/src/tools.rs 第 105 至 117 行;codex-rs/codex-mcp/src/tools.rs 第 134 至 137 行;codex-rs/codex-mcp/src/tools.rs 第 166 至 194 行;codex-rs/codex-mcp/src/tools.rs 第 226 至 227 行;codex-rs/codex-mcp/src/mcp/mod.rs 第 477 至 485 行

原始身份 server + tool.name 清洗 mcp__ 加下划线 消歧 撞了再加哈希 模型眼前 唯一且够短 协议调用仍带原名 翻译层只管给模型看,寻址还走 server_name 和 tool.name
教学化流水线:给模型看的名字和调回去的名字是两层。
给模型看的是翻译,调回去走原名。
为什么长期成立

给模型看的名字和协议上的名字本来就是两层。一层给人读、给 API 用,一层用来寻址。哈希消歧是撞名问题的通用答法。上限数字会变,这层翻译不会变。

思路三 · 目录常在,点名再给正文
它解决什么问题

若按 MCP 存活过滤目录,冷启动那几秒模型会以为 skill 不存在,下一轮又突然出现。说明书整份灌进每一轮,上下文也会被吃光。

思路是什么

点名记号是 $。目录只看 enabledprompt_visible。用户点了名,或者任务和描述对得上,这一轮才读 SKILL.md 正文。Guardian 评审会话直接返回空注入,父 transcript 里的 $skill 不能再触发新说明书。

出处:codex-rs/skills/src/mentions.rs 第 41 行;codex-rs/ext/skills/src/catalog.rs 第 261 至 263 行;codex-rs/core/src/session/turn.rs 第 766 至 770 行;codex-rs/core/src/session/turn.rs 第 808 至 817 行

缺 MCP 时另问人。first-party 且功能开关开,才弹出 Install MCP servers。审批是 Never 就静默跳过。用户选 Continue anyway,目录还在,对应工具可能仍不可用。

出处:codex-rs/core/src/mcp_skill_dependencies.rs 第 47 至 60 行;codex-rs/core/src/mcp_skill_dependencies.rs 第 268 至 270 行

为什么长期成立

发现和就绪是两件事。索引先给,全文按需再给,缺依赖问人,不要把条目从目录里抹掉。装不装是配置变更,列不列是发现。

横向对比 · 同一道题的另一种答法

DSH:只桥 tools,一条插件对一台 server

DSH 的 MCP 客户端把范围写死:连一台外部 server,工具注册到 ctx.tools,公开名是 mcp__<serverName>__<rawName>。干净情况原样拼接。字符或长度被改过,就在末尾加 12 位 SHA-256。上限 64 字符。卸载就断连、注销、放命名空间。

出处:packages/mcp/mcp-client/src/index.ts 第 1 至 14 行;packages/mcp/mcp-client/src/tools.ts 第 96 至 102 行

没有 elicitation,也不把自己交出去当 MCP server。外部工具失败仍按普通 tool 失败处理。哈希长度碰巧也是 12,算法和拼接规则不同。

已核对源码 · 2026-08-22 · DSH · MCP 与扩展

Claude Code:skill 是一等 tool

Claude Code 给模型一个 Skill tool。模型 call 才拿正文。注释写明同一时间只跑一个 skill,因为 tool 会把命令展开成整份 prompt。

出处:restored-src/src/tools/SkillTool/SkillTool.ts 第 331 至 344 行

MCP 上的 prompt 要标成 loadedFrom === 'mcp'type === 'prompt',才进发现列表。方向相反:Codex 是 skill 需要 MCP,Claude Code 是 MCP 贡献 skill。触发器也不同。Codex 扫 $name,命中就注入 <skill>,不经过一次 tool call。

出处:restored-src/src/tools/SkillTool/SkillTool.ts 第 81 至 94 行

已核对源码 · 2026-08-22
课堂练习
01

清洗之后谁还认得这家店

basic-serverlookupbasic_serverquery。写出模型看见的两个命名空间,并说明调回去时凭什么还能进对的店。

再问一问:把审批改成 Never,打 $deploy 的时候,skill 目录还在不在。观察点在 is_model_visibleshould_install_mcp_dependencies

Takeaway:对外只交两个入口,对内另做外部目录。模型看见的是翻译过的名字,撞了就哈希,原名留给协议。skill 目录常在,点名再给正文,缺 MCP 另问人。