结论先行
在**技能(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 会话中,模型用到 mpub、imagegen 技能时,是临时执行 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_ngram、bm25、lru、rrf_lexical……)评估「哪些技能该被目录注入、哪些该藏起来」,遥测指标包括 skills.kept_total、skills.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,不合并):
.mcp.json(项目根,提交进 git,团队共享);~/.claude.json——实际存local(按项目)和user(全局)两种作用域;claude mcp add默认写这里;.claude/settings.json的mcpServers——历史支持,新版文档已把项目服务器导向.mcp.json(issue #6888 记录了这个文档与 CLI 实际行为不一致的坑);- 插件自带 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.json只enabledMcpjsonServers启用了前两个;用户级~/.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 显式禁用」的差异,正是配置解析机制的外在表现。
五、实践建议
- 技能放心多装:惰性加载让技能数量对上下文几乎零成本,25 个 Ark 技能随便装。真正占开销的是描述目录,预算闸会帮你兜底。
- MCP 要克制:每加一个服务器 = 启动时多注入一批工具 schema = 固定 token 税。不用的服务器注释掉或下线,别用「假禁用」。
- 同一个 MCP 服务,Claude 和 Codex 各配一遍:两套配置体系不互通,别指望一次配置两边生效。
.mcp.json的信任闸别漏:团队共享的.mcp.json,每个成员首次都要授权,忘授权就是「配置了但没跑」。
结语
Skill 与 MCP 的加载差异,本质是「知识」与「能力」的形态差异。理解了这个,你就知道为什么装 30 个技能不用慌、而每加一个 MCP 服务器都该掂量掂量。希望这篇对同样在折腾多 Agent 环境的人有帮助。
说明:文中 Claude Code 行为以 v2.1.x 为准,Codex 以本机 0.149.1 实测为准。Claude Code 相关细节核对自官方文档与上游 issue(skills、mcp、settings-reference、issue #6888)。