Plugin MCP servers: no workspace/project-root signal (hooks have cwd; MCP servers don't)

Open 💬 1 comment Opened Aug 11, 2026 by bartolli

Use case

A plugin ships a stdio MCP server that serves per-project data (in our case: a per-project knowledge index — the server must know which project the session is working in to serve the right one). Hooks can do this today: every hook payload carries cwd with the session working directory. Plugin MCP servers cannot.

Current behavior (codex-cli 0.146.0)

  • A plugin-registered stdio MCP server with "cwd": "." is spawned in the plugin's materialized cache directory (~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/), so process.cwd() carries no project information.
  • No substitution token or injected environment variable exposes the workspace/session root to plugin MCP server configs (nothing analogous to hook payload cwd; env_vars only whitelists variables from the codex process environment, which requires a manual per-shell export).
  • The MCP client does not declare the roots capability, so roots/list is unavailable as an in-protocol signal (codex-rs/codex-mcp/src/rmcp_client.rs constructs client capabilities without roots as of rust-v0.146.0).

The net effect: a plugin MCP server cannot be project-aware without asking users to export an environment variable in every shell before launching codex.

Ask

Any one of these would close the gap:

  1. A substitution token for plugin MCP server env/args values that resolves to the session workspace root (the equivalent of the cwd field hooks already receive), or
  2. An injected environment variable carrying the workspace root in the MCP server child process, or
  3. Declaring the roots capability and answering roots/list with the workspace folder.

Option 1 or 2 seems most consistent with the existing hook contract, and with the MCP spec's 2026-07-28 direction (Roots is deprecated there in favor of server configuration).

Environment

  • codex-cli 0.146.0, macOS
  • Plugin installed via codex plugin add from a marketplace repo

View original on GitHub ↗

1 Comment

jdcodes1 · 9 days ago

Implementation note supporting option 1/2: the plumbing is one hop away. The stdio launcher already threads a fallback_cwd (used when the server config omits cwd) plus per-server env_vars through spawn (https://github.com/openai/codex/blob/1f41cc5d92/codex-rs/rmcp-client/src/stdio_server_launcher.rs#L190-L199, #L94-L95) — so both proposed shapes are local changes at the same site:

  • Injected env var (option 2): set CODEX_WORKSPACE_ROOT (and maybe CODEX_SESSION_CWD, mirroring the hook payload's cwd) unconditionally in the child env at spawn. Smallest change, zero config surface.
  • Substitution token (option 1): expand something like ${workspaceRoot} in args/env values before spawn. This pairs naturally with #38438, which reports that plugin mcp.json ${VAR} env references aren't resolved at all today — one substitution pass could fix both.

One caveat worth designing for: server processes are currently spawned per connection-set and can outlive a single session or serve a session whose cwd changes mid-thread (ThreadSettingsOverrides supports cwd updates, and #38585 wants /cd). An env var freezes the value at spawn; if mid-session correctness matters, the MCP-spec answer is passing it per-request (e.g. _meta) rather than per-process — worth stating which contract the feature promises.