OpenAI Codex · MCP and Skills

MCP comes in: the model sees translated names

Tools from an external server pass a translation layer before they reach the model’s eye. The skill catalog stays up; if MCP is missing, ask a person.

Course goalAfter this you can name three things. When Codex is the client, how an external tool becomes a model-visible name. When two shops collide after sanitizing, how they disambiguate. Why the skill catalog does not care whether MCP is alive.
Try it first · One shop comes in: how the name changes
One MCP server comes in: how a tool becomes a capability the model can see
Hookup scene
The two right-hand modes collide. Flip them and see who gets a hash after sanitizing.
Outside · raw tools/listIn the door 0
Nobody is hooked up yet.
In front of the model · translated namesVisible 0
The list is empty.
Logic trail · each animation step maps to a source span
  1. The connection set is published whole; existing bindings keep their own connectionruntime.rs L246
  2. Each shop’s tools/list is merged into one table, then handed to name translationtool_catalog.rs L153
  3. Give the namespace the historical prefix mcp__tools.rs L228
  4. Illegal characters become underscores; only letters, digits, and _ remainmcp/mod.rs L477
  5. Identical raw identities drop one copytools.rs L134
  6. After sanitizing, a namespace collision appends 12 SHA-1 hex digitstools.rs L166
  7. After sanitizing, a tool-name collision gets the same 12-digit hashtools.rs L193
  8. Over 128 bytes together, truncate then hash; protocol calls still use the original nametools.rs L226
Hit Play. Watch how a tool name becomes a capability the model can see after one shop hooks up.
Two layers of namesLeft is the protocol original; right is the translation the model sees. Calling back walks the left side, so a hash on the right does not send you into the wrong shop.
Hash only on collisionTwo clean shops get no suffix. The hyphen and tool-name modes only collide after sanitizing. The hash is disambiguation, not decoration.
Edit the second shop yourselfIn the hyphen mode, change the shop name to the same sanitized letters as the first shop, and you’ll see the namespace split apart.
Teaching sketch:The hash takes the first 12 hex digits; the algorithm is SHA-1. The demo uses a fixed teaching suffix. Line numbers map to openai/codex commit 4f39251a01.
Idea 1 · One list for the outside, another for the inside
What problem it solves

You write codex mcp-server into Cursor’s MCP config. Cursor is the client; Codex is the server. If this tools/list also hands out internal GitHub tools, one IDE call touches internal capability. The permission boundary grows from “call Codex once” into “call internal tools directly.”

What the idea is

The crate splits into two. mcp-server reads lines from stdin, one JSON per line. initialize only opens tools. tools/list hardcodes two names: codex and codex-reply. codex will start_thread; the nested thread starts its own McpRuntime. codex-mcp owns the connection set; tools from external servers get a separate catalog.

Source:codex-rs/mcp-server/src/lib.rs lines 131–152; codex-rs/mcp-server/src/codex_tool_runner.rs lines 66–90; codex-rs/codex-mcp/src/runtime.rs lines 88–98

Same JSON-RPC wire protocol; the processors are not the same. Early notes often draw them as two faces of one runtime. In the current source they do not even share a MessageProcessor.

Source:codex-rs/mcp-server/src/message_processor.rs lines 274–277; codex-rs/mcp-server/src/message_processor.rs lines 336–348

The door the IDE sees Cursor MCP client mcp-server codex · codex-reply nested thread One tools/call becomes one session External shops seen inside the session McpRuntime The connection set can be replaced whole GitHub mcp__github__* Docs mcp__docs__* Home-grown HTTP mcp__http__*
Teaching diagram:Above: two doors handed to the IDE. Below: the external tool catalog inside the session.
Why it lasts

Split the outward promise from inward capability — that is the usual gateway shape. In another language it is still two functions: hosted returns run / continue, external returns mcp__*. The IDE only sees the door; the session is where the external shops appear.

Idea 2 · What the model sees is a translated name
What problem it solves

Both shops report search; the prefix can still split them. One is basic-server, one is basic_server. After hyphens become underscores, the namespaces collide. The model sees two same-named tools and the next call does not know which shop to enter. The API also has a byte cap.

What the idea is

When a server hooks up, each shop’s tools/list is first merged into one table, then sent through normalize_tools_for_model_with_prefix. The order is a fixed four steps.

1. Prefix the namespace with mcp__.

2. Illegal characters become underscores; only letters, digits, and _ remain.

3. Identical raw identities drop one copy. If namespace or tool name still collide after sanitizing, append 12 SHA-1 hex digits.

4. Over 128 bytes together, truncate then hash. The original server_name and tool.name stay on ToolInfo; protocol calls use the original names.

Source:codex-rs/codex-mcp/src/tools.rs lines 105–117; codex-rs/codex-mcp/src/tools.rs lines 134–137; codex-rs/codex-mcp/src/tools.rs lines 166–194; codex-rs/codex-mcp/src/tools.rs lines 226–227; codex-rs/codex-mcp/src/mcp/mod.rs lines 477–485

Raw identity server + tool.name Sanitize mcp__ plus underscores Disambiguate Hash only after a collision In front of the model Unique and short enough Protocol calls still carry the original name The translation layer is only for the model to read; addressing still uses server_name and tool.name
Teaching pipeline:The name the model sees and the name you call back with are two layers.
The model sees a translation. Calling back walks the original name.
Why it lasts

The name the model sees and the name on the wire were always two layers. One is for humans and the API; one is for addressing. Hashing to disambiguate is the usual answer to collisions. The cap number may change; this translation layer will not.

Idea 3 · The catalog stays up; the body arrives when named
What problem it solves

If the catalog were filtered by MCP liveness, for those cold-start seconds the model would think the skill does not exist, then it would pop up next turn. Pouring the whole manual into every turn would also eat the context.

What the idea is

The mention mark is $. The catalog only looks at enabled and prompt_visible. When the user names it, or the task matches the description, this turn reads the SKILL.md body. A Guardian review session returns empty injection; a $skill in the parent transcript cannot trigger a new manual.

Source:codex-rs/skills/src/mentions.rs line 41; codex-rs/ext/skills/src/catalog.rs lines 261–263; codex-rs/core/src/session/turn.rs lines 766–770; codex-rs/core/src/session/turn.rs lines 808–817

If MCP is missing, ask a person. First-party and the feature flag on is what pops Install MCP servers. Approval Never skips silently. If the user picks Continue anyway, the catalog stays; the matching tools may still be unavailable.

Source:codex-rs/core/src/mcp_skill_dependencies.rs lines 47–60; codex-rs/core/src/mcp_skill_dependencies.rs lines 268–270

Why it lasts

Discovery and readiness are two facts. Give the index first, the full text on demand, and ask a person when a dependency is missing — do not wipe the entry from the catalog. Install or not is a config change; list or not is discovery.

Side-by-side · Another answer to the same question

DSH: only bridge tools, one plugin per server

DSH’s MCP client hardcodes the scope: connect one external server, register tools on ctx.tools, public name mcp__<serverName>__<rawName>. The clean case concatenates as-is. If characters or length were rewritten, append 12 SHA-256 hex digits. Cap is 64 characters. Uninstall disconnects, unregisters, and frees the namespace.

Source:packages/mcp/mcp-client/src/index.ts lines 1–14; packages/mcp/mcp-client/src/tools.ts lines 96–102

No elicitation, and it does not hand itself out as an MCP server. An external tool failure is still an ordinary tool failure. The hash length happens to be 12 too; the algorithm and join rules differ.

Checked against source · 2026-08-22 · DSH · MCP and extensions

Claude Code: a skill is a first-class tool

Claude Code gives the model a Skill tool. The model calls before it gets the body. A comment says only one skill runs at a time, because the tool expands the command into a full prompt.

Source:restored-src/src/tools/SkillTool/SkillTool.ts lines 331–344

An MCP prompt only enters the discovery list if it is marked loadedFrom === 'mcp' and type === 'prompt'. The direction flips: Codex skills need MCP; Claude Code MCP contributes skills. The trigger differs too. Codex scans $name and, on a hit, injects <skill> without a tool call.

Source:restored-src/src/tools/SkillTool/SkillTool.ts lines 81–94

Checked against source · 2026-08-22
Classroom Exercise
01

After sanitizing, who still knows this shop

basic-server reports lookup; basic_server reports query. Write the two namespaces the model sees, and say what still sends a call back into the right shop.

Then ask: flip approval to Never and type $deploy — is the skill catalog still there? Watch is_model_visible and should_install_mcp_dependencies.

Takeaway:Hand the outside only two doors; keep a separate external catalog inside. The model sees translated names; collisions get a hash; originals stay for the protocol. The skill catalog stays up, the body arrives when named, and a missing MCP asks a person.