学 AI 产品 · 专业 AI 产品经理播客第 3 章 · T3 解剖 OpenAI Codex:把安全观写进类型系统 · EP 17
第 3 章 · EP 17

MCP 接进来:模型看见翻译过的名字 · 搬家只搬对得上的字段

时长 15:46音色 云健 · 男声

同步字幕

章节导航(点击跳转)

0:00开场:外部能力怎么进来,旧家当怎么带过来1:36
1:36对外一份清单,对内另一份1:48
3:24模型看见的是翻译过的名字1:35
5:00原名留给协议:两层名字1:33
6:33skill 目录常在,点名再给正文1:47
8:20搬家:每条字段只有三条出口1:32
9:53插件只从用户级装,慢活进后台1:54
11:48目标非空不覆盖,以及两家的另一种答法2:14
14:02可带走的设计原则1:43
解读全文

codex18 · MCP 接进来:模型看见翻译过的名字 · 搬家只搬对得上的字段

模块:T3 解剖 OpenAI Codex:把安全观写进类型系统
来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本页为《学 AI 产品,从入门到精通》配套解读,音频为二次演绎配音版。

一句话速览

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

搬家只搬对得上的字段;对不上的,丢掉。

本集包含两节课:MCP 接进来与搬家只搬对得上的字段。共同主题是"边界"——外部能力如何进来,旧家当能带过来多少。

能力地图

能力落点判断标准
区分对外入口与对内目录mcp-server / codex-mcp 两个 crate能说清为什么两者不共享 MessageProcessor
掌握命名翻译流水线normalize_tools_for_model_with_prefix能复述四步顺序并说明顺序不可换
理解两层名字ToolInfo 保留原始 server_name 与 tool.name能解释为何回程走原名不会进错店
目录常在、正文按需skills catalog 只看 enabled / prompt_visible能说明为什么要按依赖存活过滤目录
搬家三条出口external-agent-migration 白名单对任一条字段能说出原样/改写/丢掉
安装权威落在用户级detect 只在 home 扫插件能解释仓库 settings 为何不能当安装权威

上半场 · MCP 接进来

主线一 · 对外一份清单,对内另一份

  • mcp-server 从 stdin 读行,一行一条 JSON;initialize 只打开 tools;tools/list 写死两个名字:codex 与 codex-reply。
  • codex 会 start_thread,nested thread 再起自己的 McpRuntime。
  • codex-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 线协议,处理器不是同一个;当前源码里两者甚至不共享 MessageProcessor。
  • 出处:codex-rs/mcp-server/src/message_processor.rs 第 274–277 行、第 336–348 行
危险场景:把 codex mcp-server 写进 Cursor 的 MCP 配置,Cursor 是 client、Codex 是 server。若这一次 tools/list 把内部工具一并交出,IDE 调一次就摸到内部能力——权限边界从"调一次 Codex"扩成"直接调内部工具"。

主线二 · 命名翻译四步

normalize_tools_for_model_with_prefix 的固定顺序:

  1. 给命名空间加 mcp__ 前缀;
  2. 非法字符洗成下划线,只留字母、数字、_;
  3. 完全相同的原始身份丢掉一份;清洗后命名空间或工具名仍撞 → 末尾加 12 位 SHA-1;
  4. 合起来超过 128 字节 → 截断再哈希。原始 server_name 与 tool.name 留在 ToolInfo 上,协议调用走原名。
  • 出处:codex-rs/codex-mcp/src/tools.rs 第 105–117、134–137、166–194、226–227 行;codex-rs/codex-mcp/src/mcp/mod.rs 第 477–485 行

要点:

  • 撞名才哈希。basic-server 与 basic_server 清洗后命名空间相同 → 加哈希;两家都报 search、前缀可分时不动哈希。哈希是消歧,不是装饰。
  • 两层名字:左侧协议原名、右侧模型可见名。调回走左侧,不会因右侧带哈希而进错店。
  • 连接集整份发布;已有 binding 继续拿自己那份连接。
  • 出处:codex-rs/codex-mcp/src/runtime.rs 第 246 行;codex-rs/codex-mcp/src/tool_catalog.rs 第 153 行

主线三 · 目录常在,点名再给正文

  • 点名记号 $。目录只看 enabled 与 prompt_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、808–817 行
  • 缺 MCP 时另问人:first-party 且功能开关开 → 弹出 Install MCP servers;审批为 Never 则静默跳过;用户选 Continue anyway,目录仍在但对应工具可能不可用。
  • 出处:codex-rs/core/src/mcp_skill_dependencies.rs 第 47–60、268–270 行
反面设计:按 MCP 存活过滤目录 → 冷启动几秒模型以为 skill 不存在,下一轮又突然出现;整份灌进每轮 → 上下文被吃光。

下半场 · 搬家只搬对得上的字段

主线一 · 三条出口

  • 源只有两个变体:Cla(Claude Code)与 Cur(Cursor)。字符串对不上 cursor 就落到 Claude Code;调用方漏传源 → 去翻 ~/.claude。
  • 出处:codex-rs/external-agent-migration/src/migration_source.rs 第 51–67 行
  • 一次检测最多十种条目,每种导入必须给去处。字段先过白名单,再走原样 / 改写 / 丢掉三条出口之一。
字段规则出口
hook 事件名Codex 认 11 个,Claude Code 发出 27 个对不上的组整组消失
单条 hook type默认当 command对不上跳过;prompt 整条跳过
MCP command / url出现 ${整台 server 不要
市场来源只收 github、git、本地目录file / url / npm / settings 直接丢掉
说明文件CLAUDE.md → AGENTS.md改写
产品名按词边界改写为 Codex改写(Cursor 仅大小写敏感匹配)
会话30 天 / 50 条过滤太旧丢掉
memory需源支持 + 特性开关Cursor 不支持;开关关则拒绝
  • 出处:codex-rs/external-agent-migration/src/model.rs 第 53–64 行;codex-rs/hooks/src/lib.rs 第 23–35 行;codex-rs/external-agent-migration/src/hooks_cla.rs 第 131–137 行;mcp.rs 第 204–210 行;source_cla.rs 第 270–274 行;rewrite.rs 第 38–49 行;detect/mod.rs 第 59、330–332 行;sessions/common.rs 第 43 行

主线二 · 插件只从用户级装,慢活进后台

  • 检测只在 home 扫插件;注释写死"仓库控制的 settings 不能当安装权威"。Cursor 只要看见仓库根,插件检测直接返回空;导入看见非空工作目录 → repository-scoped plugin migration is not allowed。
  • 出处:detect/mod.rs 第 330–332 行;migration_source.rs 第 116–125 行;plugins.rs 第 26–45 行
  • 本地插件当场装;远程市场推进待装队列。
  • 会话两截:检测清单可含会话,同步 import() 对 Sessions 直接返回成功、什么都不写;真正写成 Codex thread 的是 app-server 后台任务,调用方先拿 import_id。
  • 出处:service.rs 第 437 行;app-server/src/external_agent_migration/session_importer.rs 第 100–111 行
  • 市场查找按固定相对路径找第一个存在的文件,四条路径中两条自有、两条竞品;同一套查找同时服务"用户主动加市场"与"从竞品搬市场"。
  • 出处:codex-rs/core-plugins/src/marketplace.rs 第 20–25 行

主线三 · 目标非空不覆盖

  • 目标 hook 文件非空 → 整项取消;已有 config.toml 只补缺失键;同名 MCP server 保留旧的。第一次搬家像填空,第二次多数条目不再出现。
  • 出处:codex-rs/external-agent-migration/src/hooks_common.rs 第 13–19 行
  • memory 另有一道门:特性开关默认关、阶段 UnderDevelopment;检测函数不看开关,导入前处理器会查,关着回 external agent memory import is disabled。导入按字节拷贝、不脱敏。
  • 出处:codex-rs/features/src/lib.rs 第 998–1003 行;app-server/.../processor.rs 第 180–185 行

约束说明

  1. 对外入口与对内目录必须在代码上分开,不共享处理器。物理隔离优于运行时判断。
  2. 展示名与寻址名分层。清洗、加哈希、截断只发生在朝向模型一侧;原始身份必须全程挂在旁边,回程永远走原始身份。
  3. 目录常在,正文按需。不得用依赖存活状态过滤索引;缺依赖去问人,不抹掉条目。
  4. 导入器只有三条出口,原样 / 改写 / 丢掉,没有"改写成近似物"。
  5. 可执行内容的安装权威必须落在用户级。面向任意 git clone 的产品,仓库启用名单天生不可信。
  6. 同步先回执,慢活进后台。长任务接口不阻塞调用方。
  7. 目标非空不覆盖。用户手写配置优先级最高,搬家是填空不是重写。
  8. 改产品名用词边界,避免把普通英文一起改坏。

审查清单

  • tools/list 对外是否只暴露两个入口?内部工具是否被一并交出?
  • 两个 crate 是否共享 MessageProcessor?(共享即边界失效)
  • 命名翻译四步顺序是否固定?是否先清洗、再判撞、最后哈希?
  • 去重写的是原始身份还是清洗后名字?
  • ToolInfo 是否保留原始 server_name 与 tool.name?回程是否走原名?
  • 超过 128 字节时是截断还是报错?截断后是否仍可寻址?
  • skill 目录是否受 MCP 存活状态影响?是否存在冷启动闪烁?
  • Guardian 会话是否返回空注入?父 transcript 中的 $skill 是否还能触发注入?
  • 搬家白名单是否覆盖全部十种条目?每种条目是否都有明确去处?
  • 插件检测是否只在 home 进行?仓库范围是否明确拒绝?
  • 同步 import() 是否对 Sessions 立即返回而不写盘?后台任务是否可追踪?
  • 目标文件非空时是否整项取消而非覆盖?
  • memory 导入是否受特性开关拦截?是否提示"按字节拷贝、不脱敏"?

提示一 · 把对外入口写成独立函数,别复用内部目录

实现时最容易犯的错是"复用同一份工具表,只是过滤一下"。过滤是可以被绕过、被忘记、被后来者改坏的。正确做法是让对外清单由一个独立函数返回,硬编码两个入口;内部目录由另一个模块维护。两者不共享结构,边界才是真的。

提示二 · 命名翻译写成单条流水线,步骤顺序写进注释

四步顺序(加前缀 → 洗字符 → 按原始身份去重 → 撞名加哈希 → 超长截断)必须显式固定,并在注释里写明"顺序不可换"。建议在单测里构造 basic-server 与 basic_server 两组输入,断言清洗后撞名、且哈希只加到其中一方。

提示三 · 目录与正文分离,缺失依赖走询问而不是过滤

检查自己的 skill / plugin 系统:目录是否常驻?是否因为某个依赖没起来就从列表里消失?正确实现是目录只按启用与可见过滤,缺依赖时弹提示并保留条目。可以在集成测试里模拟"依赖 server 未启动",断言目录条目数不变。

提示四 · 给导入器写一张白名单表,并为每条字段标注出口

不要写"尽力转换"的迁移代码。把每条源字段、目标落点、出口(原样/改写/丢掉)列成一张表,作为代码与文档的共同来源。对不上的字段一律走"丢掉",并在导入报告里显式列出,让用户知道少了什么、为什么少。

提示五 · 长任务接口:同步返回 id,后台落盘

会话导入、远程市场安装这类慢活,同步阶段只做"承认看见了",返回一个 import_id,落盘交给后台任务。调用方轮询或订阅 id 状态。避免让同步接口因等待外部网络而长时间阻塞。

自测卡

  1. basic-server 报 lookup,basic_server 报 query。写出模型看见的两个命名空间,并说明调回去时凭什么还能进对店。
  2. 把审批改成 Never,打 $deploy 时 skill 目录还在不在?观察点在 is_model_visible 与 should_install_mcp_dependencies。
  3. 桌上有四条 Claude Code 资产:PreToolUse 的 type: command hook、同事件的 type: prompt hook、一台 command 含 ${API_KEY} 的 MCP、一条 40 天前的会话。对照白名单列出应出现与不应出现的条目。
  4. 调用方何时收到 import_id?这条会话何时变成 thread?开关关着的 memory 送去导入,会在哪一扇门被挡回?

要点回顾(Takeaway)

对外只交两个入口,对内另做外部目录。模型看见的是翻译过的名字,撞了就哈希,原名留给协议。skill 目录常在,点名再给正文,缺 MCP 另问人。
搬家是检测加映射加重写。对得上的字段走拷或改,对不上的丢掉。插件只从用户级装,会话和远程市场进后台。目标非空不覆盖。

来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)

本页正文为二次演绎配音版的配套解读,与音频逐段对应。