Skip to main content
Zhimalab
中文
This article is not yet available in English. View the Chinese version

Codex 与 Claude Code:Skill 和 MCP 到底是怎么加载的?

2026-09-01 · 22 min

结论先行

在**技能(Skill)**和 MCP 服务器的加载方式上,Claude Code 与 OpenAI Codex 的设计惊人地一致:

  • Skill 是惰性加载(lazy / 按需读取):启动时模型只看到每个技能的名称 + 一句话描述 + 位置,正文(SKILL.md)直到真正用到才被读取进上下文。
  • MCP 是常驻加载(eager):会话启动时就把服务器拉起来,并把所有工具的 schema 一次性注入上下文——这就是社区常说的 MCP「token 税」。

两者在「省上下文」这件事上走了完全相反的路,但各自有充分理由。这篇文章用本机真实配置、会话日志和二进制字符串三层证据,把这个机制拆开讲清楚。

背景:一次技能安装引发的疑问

前几天给机器装了火山方舟的 Ark CLI,它的 arkcli +connect 一键把 25 个技能同时装进了 16 个 Agent(Claude Code、Codex、Cursor、Gemini CLI……)。装完我就想:每个 Agent 装 25 个技能,启动时不会把上下文撑爆吗?于是翻了两个主流 Agent(Claude Code 与 Codex)的实现,结论就是上面那句。

一、Skill:两个 Agent 都是「惰性加载」

1.1 Claude Code 的技能加载

Claude Code 遵循 Agent Skills 规范。技能就是一个目录,里面必须有 SKILL.md,通常还带 references/scripts/ 等资源。它的发现与加载分两阶段:

阶段一(启动时):只解析 frontmatter。 启动时扫描技能目录,只解析 YAML frontmatter 里的 name + description,组装成一个 <available_skills> 目录注入系统提示词。正文完全不进上下文。为了防止描述本身撑爆上下文,还有三道预算闸:

  • 单个技能 description + when_to_use 合计上限 1,536 字符(可用 maxSkillDescriptionChars 调);
  • /skills 界面里每条描述显示上限 250 字符(v2.1.86+);
  • 所有技能描述合计约占上下文窗口 1–2%,超预算时最不常用的技能被降级为「只有名字」。

/doctor 可以查看预算溢出和哪些描述被丢弃。

阶段二(用到时):才读正文。 当用户输入 /技能名,或模型判定任务命中某技能描述而调用 Skill(...) 工具时,才把 SKILL.md 正文读进上下文。references/scripts/ 等附属资源永远不会自动注入——Agent 需要时自己按需读/执行。技能一旦被激活,正文会在本次会话里保留;上下文压缩时也会重新挂回每个被调用技能最近的调用内容。

技能存放位置(全部生效,取并集):

位置 作用域 说明
~/.claude/skills/ 用户级 所有项目可见
.claude/skills/ 项目级 含仓库根父目录、嵌套子目录(monorepo)
<plugin>/skills/ 插件级 命名空间 plugin:<技能名>,不与上面冲突
.claude/commands/ 历史遗留 旧的 /命令 已并入技能体系

值得注意的一个细节:metadata.requires.bins(声明技能依赖某个二进制,如 Ark 技能的 requires: ["arkcli"])其实是 AgentSkills 开放规范里的字段,并不是 Claude Code 原生解析的字段。写技能时别指望它在所有 Agent 里都生效。

1.2 Codex 的技能加载

Codex CLI(本机 0.149.1)的机制类似但实现不同。技能从 ~/.agents/skills/(用户级)、.codex/skills/(项目级)、~/.codex/skills/ 发现。它注入给模型的指令写得很直白(从二进制提取):

每个技能条目只包含 name、description、SKILL.md 位置。位置可能是文件路径或别名,需要你用 Read 工具去读。 触发规则:用户点名某技能,或任务明确匹配其描述时,那一轮才使用不跨轮携带,除非再次被提及。

也就是说:目录(name+description)常驻提示词,正文按需读取。我在它的会话数据库里找到了实证——DeepSeek 会话中,模型用到 mpubimagegen 技能时,是临时执行 shell 去读的:

Get-Content -LiteralPath ".claude/skills/mpub/SKILL.md" -Encoding UTF8
Get-Content -LiteralPath "C:/Users/peini/.codex/skills/.system/imagegen/SKILL.md" -Encoding UTF8

这正是惰性加载:先看到目录条目,用到哪份读哪份。顺带一提,Codex 还在后台跑一个 shadow skill selection 实验——用十几种候选算法(character_ngrambm25lrurrf_lexical……)评估「哪些技能该被目录注入、哪些该藏起来」,遥测指标包括 skills.kept_totalskills.truncated。目前只是后台评估,但方向很明确:未来可能连「目录」都不全量注入,只注入与当前任务相关的几个。

1.3 对比表

维度 Claude Code Codex
启动时注入 只有 name+description(frontmatter) 只有 name+description+位置
正文加载时机 被调用/激活时才读 模型判定匹配时才用 Read 读
跨轮保留 激活后本会话保留;压缩时重挂最近一次 明确「不跨轮携带」
描述预算 三道闸(单条 1536 / 展示 250 / 总量 1–2%) 有截断指标,另有选择性实验
附加资源 references/ 等永不自动注入 按需读
技能格式 标准 Agent Skills(SKILL.md 同款 SKILL.md 目录

结论:两者都是「目录常驻 + 正文惰性」,差异只在预算管理和未来是否对目录本身做选择。

二、MCP:都是「启动即连 + 工具常驻注入」

2.1 Claude Code 的 MCP 加载

配置来源(同时生效,取并集,同名冲突时 local > project > user,不合并):

  1. .mcp.json(项目根,提交进 git,团队共享);
  2. ~/.claude.json——实际存 local(按项目)和 user(全局)两种作用域;claude mcp add 默认写这里;
  3. .claude/settings.jsonmcpServers——历史支持,新版文档已把项目服务器导向 .mcp.jsonissue #6888 记录了这个文档与 CLI 实际行为不一致的坑);
  4. 插件自带 MCP(plugin:<名>:<服务名> 命名空间)。

信任机制(.mcp.json 专有): 项目级服务器首次需要一次性授权才能连。三种授权方式:在 /mcp TUI 里点 Approve、在 settings 里用 enabledMcpjsonServers 预授权、或 enableAllProjectMcpServers: true 全开。而 disabledMcpjsonServers 黑名单永远优先。另外自 v2.1.193 起:未受信任的文件夹会忽略仓库内提交的授权,防止新 clone 的仓库给自己「盖章」。

启动与注入: MCP 服务器在会话启动时启动,配置改了要重启。启动后,所有启用服务器的工具 schema 一次性注入模型上下文,工具以 mcp__<服务>__<工具> 形式可调用。这就是 MCP「token 税」的由来——工具定义很占上下文。连接超时:HTTP/SSE/WS 5 分钟、stdio 30 分钟(可用 CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT 调)。

传输方式: stdio(本地子进程)、HTTP(远程,支持 OAuth/bearer/header)、SSE(已废弃)、WebSocket。

CLI 与作用域: claude mcp add 支持 --scope local|project|user

  • local(默认)→ 写 ~/.claude.json 的该项目条目(私有);
  • project → 写 .mcp.json(提交,团队共享);
  • user → 写 ~/.claude.json 全局。

2.2 Codex 的 MCP 加载

Codex 的 MCP 配置在 ~/.codex/config.toml[mcp_servers.<名字>] 段,项目级 .codex/config.toml 会合并进来。命令行用 codex mcp add/list/get/remove/login/logout。支持本地 command 和远程 --url(streamable HTTP),远程可用 --bearer-token-env-var 注入令牌。

加载行为与 Claude 相同:会话启动时拉起服务器,工具注入上下文。我在会话记录里看到了大量真实的工具调用(mcpToolCall 共 908 条),比如:

{"server":"playwright","tool":"browser_navigate","arguments":{"url":"https://..."}}
{"server":"playwright","tool":"browser_find","arguments":{"text":"Download"}}

Codex 还支持按工具配置审批模式approval_mode = "auto" 自动放行只读工具),以及细粒度启停。

2.3 对比表

维度 Claude Code Codex
项目级配置 .mcp.json(提交共享) .codex/config.toml
用户级配置 ~/.claude.json ~/.codex/config.toml [mcp_servers]
作用域 local / project / user 用户 + 项目合并
信任机制 .mcp.json 需一次性授权 无(但项目配置影响面小)
启动时机 会话启动,改动需重启 会话启动
工具注入 全量注入(token 税) 全量注入(同样 token 税)
远程/OAuth HTTP/SSE/WS,支持 OAuth streamable HTTP,bearer token
细粒度控制 无 per-tool 审批 有 per-tool approval_mode

三、为什么 Skill 惰性、MCP 却常驻?

这背后是两类东西的本质差异:

  • Skill = 知识/文档。 它教模型「怎么做」,不进模型就只是少一份参考。所以做成渐进式披露:先用一句话描述告诉模型「有这能力」,用到再读全文。25 个技能,正文全读进来可能几十万 token,而惰性加载让它们对上下文几乎零成本。
  • MCP = 工具/执行能力。 工具要能被模型正确调用,参数 schema 必须在上下文里——模型看不到工具定义就没法构造调用。所以工具注入是不可偷懒的,只能全量常驻。这就是「token 税」的根源,也是社区出现「MCP Mode」这类按需连服务器工具的原因。

一句话:知识可以按需取,能力必须随身带。

四、本机实测数据

以下是我这台机器(Windows)上的真实状态,可作为对照参考。

4.1 技能清单

  • Claude Code 用户级 ~/.claude/skills/:31 个 = 25 个 arkcli-* + codebase-memory、computer-use、find-skills、mpub、orca-cli、orchestration;
  • Claude Code 项目级 .claude/skills/:13 个(speckit 系列、mpub、imagemagick、threejs-layout-verify 等);
  • Codex 用户级 ~/.agents/skills/:30 个(同上 25 个 arkcli + 5 个内置);另有 ~/.codex/skills/ 1 个(codebase-memory);
  • Codex 项目级 .codex/skills/:1 个(imagemagick)。

装了这么多,日常用起来上下文并没有被撑大——正是惰性加载的效果。

4.2 MCP 清单

  • Claude Code 项目 .mcp.json:4 个(codebase-memory-mcp、codegraph、voice-tts、playwright),但 settings.local.jsonenabledMcpjsonServers 启用了前两个;用户级 ~/.claude.json 还有 3 个;
  • Codex 用户级:5 个(codebase-memory-mcp、node_repl、playwright、chart-mcp、browsermcp);项目级:voice-tts。

这里藏着两个细节:同一台机器、同一个项目,Claude 与 Codex 的 MCP 配置是两套独立的.mcp.json vs .codex/config.toml),并没有自动互通;而 Claude 侧 .mcp.json 的服务器还要过一道「是否启用」的信任闸,所以写了 4 个实际只跑 2 个。

4.3 一个真实的坑:损坏的 Codex MCP 配置

排「为什么 codex mcp list 报错」时发现:某个项目(olympic)的 .codex/config.toml 里写着:

[mcp_servers.imagemagick-mcp]   # 图片处理
enabled = false

本意是「禁用这个服务器」,但 Codex 仍会解析这个表,发现它既没有 command 也没有 url,直接报 invalid transport——整个 codex mcp list 直接挂掉。也就是说 enabled = false 并不能优雅地关掉一个服务器,正确做法是注释掉整个段或补全字段。这个「注释 vs 显式禁用」的差异,正是配置解析机制的外在表现。

五、实践建议

  1. 技能放心多装:惰性加载让技能数量对上下文几乎零成本,25 个 Ark 技能随便装。真正占开销的是描述目录,预算闸会帮你兜底。
  2. MCP 要克制:每加一个服务器 = 启动时多注入一批工具 schema = 固定 token 税。不用的服务器注释掉或下线,别用「假禁用」。
  3. 同一个 MCP 服务,Claude 和 Codex 各配一遍:两套配置体系不互通,别指望一次配置两边生效。
  4. .mcp.json 的信任闸别漏:团队共享的 .mcp.json,每个成员首次都要授权,忘授权就是「配置了但没跑」。

结语

Skill 与 MCP 的加载差异,本质是「知识」与「能力」的形态差异。理解了这个,你就知道为什么装 30 个技能不用慌、而每加一个 MCP 服务器都该掂量掂量。希望这篇对同样在折腾多 Agent 环境的人有帮助。

说明:文中 Claude Code 行为以 v2.1.x 为准,Codex 以本机 0.149.1 实测为准。Claude Code 相关细节核对自官方文档与上游 issue(skillsmcpsettings-referenceissue #6888)。