Codex CLI does not interpolate ${CLAUDE_PLUGIN_ROOT} in plugin .mcp.json args, breaking imessage MCP

Open 💬 2 comments Opened Apr 25, 2026 by rexplx
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

The imessage plugin from anthropics/claude-plugins-official ships a .mcp.json that references ${CLAUDE_PLUGIN_ROOT} — a placeholder Claude Code interpolates with the plugin install dir. Codex CLI passes the literal string through to the spawned process, so bun run --cwd ${CLAUDE_PLUGIN_ROOT} ... fails with ENOENT: Could not change directory to "${CLAUDE_PLUGIN_ROOT}".

Reproduction

  1. codex plugin marketplace add claude-plugins-official https://github.com/anthropics/claude-plugins-official.git
  2. Enable imessage@claude-plugins-official
  3. Launch Codex — startup log shows: MCP client for \imessage\ failed to start: MCP startup failed: handshaking with MCP server failed: connection closed: initialize response

\codex mcp list\ confirms the literal placeholder:

\\\
imessage bun run --cwd \${CLAUDE_PLUGIN_ROOT} --shell=bun --silent start
\
\\

Expected

Codex CLI should interpolate \\${CLAUDE_PLUGIN_ROOT}\ (and likely \\${CLAUDE_PLUGIN_DATA}\, etc.) in \.mcp.json\ \args[]\ and \command\ per the Claude Code plugin spec, since plugins shared between marketplaces rely on this idiom.

Workaround

Register a custom MCP via \codex mcp add\ with the absolute install path; or edit cache \.mcp.json\ (reverts on plugin re-extract).

Affected versions

Codex CLI (current GA as of 2026-04-25), claude-plugins-official imessage 0.1.0.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #19372

Powered by Codex Action

tedjy971 · 3 months ago

Another repro / data point — mksglu/context-mode plugin hits the exact same bug on Codex CLI v0.130.0.

After fixing the marketplace layout so the plugin actually shows up in /plugin (context-mode#525), the MCP server still fails to start with the same error:

⚠ MCP client for `context-mode` failed to start: MCP startup failed:
  handshaking with MCP server failed: connection closed: initialize response

Codex log shows the literal placeholder is passed through:

Error: Cannot find module '/Users/.../<USER_CWD>/${CODEX_PLUGIN_ROOT}/start.mjs'

I probed what Codex actually passes to the spawned MCP process by replacing the server with node -e "console.error('CWD='+process.cwd()); console.error('ENV='+Object.keys(process.env).join('|'))":

| Mechanism | Codex v0.130.0 behavior |
|-----------|-------------------------|
| cwd of spawned process | user's launch dir, NOT plugin root |
| Env vars | only 10 POSIX vars (PATH, HOME, USER, …). No CODEX_*, CLAUDE_*, PLUGIN_* |
| ${VAR} in args | passed literally (no shell expansion, no substitution) |

So plugin-bundled MCP servers have no way at all to resolve their own location from inside the spawned process. Claude Code expands ${CLAUDE_PLUGIN_ROOT}, so the same .mcp.json works there.

Suggested fix (in priority order)

  1. Interpolate ${CODEX_PLUGIN_ROOT} (and ideally also ${CLAUDE_PLUGIN_ROOT} for cross-runtime plugins) in .mcp.json command and args[]. Cheapest fix; preserves existing manifests.
  2. Set cwd = plugin_root when launching bundled MCP servers. This alone would unblock most plugins (relative ./start.mjs would just work). No spec change needed.
  3. Inject CODEX_PLUGIN_ROOT as an env var so launcher scripts can self-resolve via process.env.CODEX_PLUGIN_ROOT.

Any one of these three would close the gap. (2) is probably the simplest semantic and matches what users expect from "bundled plugin component".

Current user-side workaround

Bypass the plugin manifest entirely by declaring the server in ~/.codex/config.toml:

[mcp_servers.context-mode]
command = "node"
args = ["/absolute/path/to/start.mjs"]

This works but defeats the whole point of plugin packaging — every user has to know the absolute install path, and upgrades break it.

Why this matters beyond imessage

Any non-trivial plugin that ships a real MCP server (not just npx some-package from PATH) is broken on Codex CLI today. context-mode, imessage, and likely many others. The plugin model effectively only works for plugins that publish their MCP as a globally-installed binary.

Happy to test patches against either case.