|
1 | 1 | --- |
2 | 2 | title: "Codex MCP CWD — Opt-In ARCHCORE_CWD Via Shell Wrapper" |
3 | | -status: accepted |
| 3 | +status: rejected |
4 | 4 | tags: |
5 | 5 | - "codex" |
6 | 6 | - "multi-host" |
7 | 7 | - "plugin" |
8 | 8 | --- |
9 | 9 |
|
10 | | -## Idea |
| 10 | +## Status: Rejected (Superseded) |
11 | 11 |
|
12 | | -Make Codex-spawned archcore MCP servers operate in the user's project directory by combining three mechanisms — entirely within the existing shell launcher, no new language dependency: |
13 | | - |
14 | | -1. **Manifest passthrough.** `.codex.mcp.json` declares `env_vars: ["ARCHCORE_CWD"]`. Codex spawns MCP children with `.env_clear()` plus a fixed allowlist (HOME LOGNAME PATH SHELL USER __CF_USER_TEXT_ENCODING LANG LC_ALL TERM TMPDIR TZ); `env_vars` is the only knob for carrying additional env through that wall. Using a **custom name** (not `PWD`) is essential — POSIX shells (sh, dash, bash --posix) resync `$PWD` to `getcwd()` at startup, so any passed-through `PWD` is overwritten before our code runs. Custom names pass through both barriers. |
15 | | - |
16 | | -2. **`bin/archcore` shell launcher.** Two cooperating blocks at the top: |
17 | | - - Step 0 — if `ARCHCORE_CWD` is set and points at a real directory, `cd` there before resolving and exec'ing the real archcore CLI. |
18 | | - - Step 0b — **plugin-install guard.** When invoked as `archcore mcp` AND cwd matches `*/plugins/cache/*` AND all three plugin-root markers exist in cwd, refuse with an actionable error. Converts the silent-fallback-to-plugin-docs failure into a loud, self-documenting one. Escape hatch: `ARCHCORE_ALLOW_PLUGIN_CWD=1`. |
19 | | - |
20 | | -3. **User opt-in via shell wrapper.** Users wrap their `codex` invocation so `ARCHCORE_CWD` is set to the current project just-in-time. Examples: |
21 | | - |
22 | | - - fish: `~/.config/fish/functions/codex.fish` — `function codex; env ARCHCORE_CWD=$PWD command codex $argv; end` |
23 | | - - bash/zsh: in `~/.bashrc` / `~/.zshrc` — `codex() { ARCHCORE_CWD="$PWD" command codex "$@"; }` |
24 | | - |
25 | | - This is opt-in by design: it adds zero runtime dependencies (no Python, no extra binary), but the user has to install the wrapper once. If the wrapper is missing, the **guard refuses to start and prints the wrapper recipe inline** — the user can't silently land on plugin docs. |
26 | | - |
27 | | -## Value |
28 | | - |
29 | | -Before: every `mcp__archcore__*` call from Codex operated in `~/.codex/plugins/cache/<marketplace>/archcore/<version>/`. A `/archcore:bootstrap` session against an empty `test_project/` cost ~9 m 41 s, half of it spent realizing the docs landed in the wrong place. |
30 | | - |
31 | | -After: with the shell wrapper installed, the MCP server's CWD is the project the user `cd`'d into before running `codex`. Without the wrapper, the guard fires loud and shows the user how to install it. No new tools, no new languages, no silent plugin-cache pollution. |
| 12 | +This idea was based on the bundled CLI launcher with a custom shell-wrapper workaround. The new global CLI architecture eliminates the need for this approach. |
32 | 13 |
|
33 | | -## Possible Implementation |
| 14 | +**New architecture (as of 2026-05-12):** |
| 15 | +- Users install the Archcore CLI globally |
| 16 | +- Codex MCP config: `{ "command": "archcore", "args": ["mcp"] }` — no `cwd`, no `env_vars` |
| 17 | +- No shell wrapper required |
| 18 | +- No `ARCHCORE_CWD` environment variable |
| 19 | +- Standard Codex MCP resolution handles everything |
34 | 20 |
|
35 | | -Shipped: |
| 21 | +See: `remove-bundled-launcher-global-cli.idea.md` for the full transition. |
36 | 22 |
|
37 | | -- `bin/archcore` (existing sh launcher): |
38 | | - - Step 0 — `if [ -n "${ARCHCORE_CWD:-}" ] && [ -d "$ARCHCORE_CWD" ]; then cd "$ARCHCORE_CWD" 2>/dev/null || true; fi`. No-op when the var is unset (the normal Claude Code path). |
39 | | - - Step 0b — guard block: refuse `archcore mcp` from cache cwd when plugin markers are present, unless `ARCHCORE_ALLOW_PLUGIN_CWD=1`. |
40 | | -- `.codex.mcp.json`: `command: "./bin/archcore"`, `args: ["mcp"]`, `cwd: "."`, `env_vars: ["ARCHCORE_CWD"]`. |
41 | | -- `test/unit/launcher.bats`: nine new tests — three for ARCHCORE_CWD chdir behavior, six for the guard (cache-cwd refusal, dev-checkout pass-through, ARCHCORE_CWD-honored bypass, escape hatch, non-mcp subcommand pass-through, partial-marker pass-through). |
42 | | -- `test/structure/codex-plugin.bats`: manifest contract pinned (command, args, cwd, env_vars must include ARCHCORE_CWD). |
43 | | - |
44 | | -Discovery work that informed the design: |
45 | | - |
46 | | -- Codex source: `codex-rs/rmcp-client/src/stdio_server_launcher.rs:236-267` is the MCP spawn site; `utils.rs::DEFAULT_ENV_VARS` is the allowlist. PWD is not on it. `env_vars` from the manifest is the only passthrough hook. |
47 | | -- POSIX `$PWD` resync is universal across `/bin/sh`, `dash`, `bash --posix` on macOS. Verified with `env PWD=/A sh -c 'echo $PWD'` from a different physical dir — child reports `getcwd()` even though parent set PWD to /A. Custom env var names (e.g. `ARCHCORE_CWD`) are untouched. |
48 | | - |
49 | | -Rejected alternatives (see `codex-path-resolution.adr` for full context): |
50 | | - |
51 | | -- **Python trampoline that reads `$PWD` and chdir's before exec'ing sh launcher.** Verified end-to-end — works perfectly, but introduces Python as a third language to a shell+Go plugin. Rejected per user decision: avoid adding new languages/tooling without explicit decision. |
52 | | -- **Compiled Go trampoline binary.** No new runtime dep, but adds a darwin/linux × amd64/arm64 build matrix and macOS code-signing step. Rejected as over-engineering. |
53 | | -- **`PWD`-rebase inside the existing sh launcher.** Dead end — sh resyncs PWD before the launcher's first line runs. |
54 | | -- **`$PWD`-only `env_vars` declaration without a non-shell trampoline.** Codex passes PWD through, but sh erases it. Useless on its own. |
55 | | -- **Silent opt-in without the guard.** Earlier iteration. Rejected because a fresh user without the wrapper experiences the original bug (MCP operates on plugin cache `.archcore/`, listing plugin docs as the user's). Hard to diagnose. The guard converts this to a loud, self-documenting failure. |
56 | | - |
57 | | -Upstream coupling: |
58 | | - |
59 | | -- `openai/codex#19582` — `${PLUGIN_ROOT}` substitution in MCP `command`/`args`. If/when shipped, we may drop `cwd: "."` and inherit Codex's caller CWD directly, retiring the wrapper. The `ARCHCORE_CWD` mechanism and the guard both stay harmless until then. |
| 23 | +--- |
60 | 24 |
|
61 | | -## Risks and Constraints |
| 25 | +## Original Idea (Historical, Superseded) |
62 | 26 |
|
63 | | -- **Opt-in surface.** Users who don't install the shell wrapper get a loud refusal at MCP start with the wrapper recipe printed inline. No more silent wrong-cwd operation. Mitigations: |
64 | | - - Document the wrapper recipe prominently in `codex-local-plugin-testing.guide.md` and the README. |
65 | | - - The guard itself prints the recipe — no docs lookup required to recover. |
66 | | - - Consider in a future iteration: a `archcore shell-init <shell>` command that prints the wrapper for the user to paste into their rc. |
67 | | -- **Wrapper hygiene.** The wrapper sets `ARCHCORE_CWD=$PWD` at codex-launch time, not on every directory change inside Codex. If the user starts Codex in dir A and uses Codex to navigate to dir B, MCP still points at A. Acceptable because Codex itself treats the launch dir as the session's project root. |
68 | | -- **Custom env-var name choice.** `ARCHCORE_CWD` mirrors the existing `ARCHCORE_BIN` convention. Anyone who happens to have ARCHCORE_CWD set globally will override the launcher's cwd; documented as a feature. |
69 | | -- **Cross-host neutrality.** `.mcp.json` (Claude) and `.cursor-plugin/...` are untouched. Claude Code does not chdir before MCP spawn, so both the `ARCHCORE_CWD` block (no-op when the var is unset) and the guard (skipped when cwd is not in a plugin-cache path) have zero impact on other hosts. |
70 | | -- **Guard false positives.** Three converging signals must align (subcommand=mcp, cache-path cwd, all three plugin markers present). Plugin development from a non-cache checkout (e.g., this repo on disk) does not trigger the guard. The `ARCHCORE_ALLOW_PLUGIN_CWD=1` escape hatch covers the plugin-maintainer-runs-MCP-against-installed-copy edge case. |
| 27 | +[Original content preserved below for reference only] |
71 | 28 |
|
72 | | -## Verification |
| 29 | +### Idea |
73 | 30 |
|
74 | | -A/B reproducer (after deploying to the plugin cache): |
| 31 | +Make Codex-spawned archcore MCP servers operate in the user's project directory by combining three mechanisms — entirely within the existing shell launcher, no new language dependency: |
75 | 32 |
|
76 | | -```sh |
77 | | -SOURCE=/Users/ivklgn/Documents/archcore/plugin |
78 | | -CACHE=~/.codex/plugins/cache/archcore-personal/archcore/0.3.13 |
| 33 | +1. **Manifest passthrough.** `.codex.mcp.json` declares `env_vars: ["ARCHCORE_CWD"]`. Codex spawns MCP children with `.env_clear()` plus a fixed allowlist... |
79 | 34 |
|
80 | | -# Source-only marker the cache cannot have |
81 | | -printf -- '---\ntitle: marker\nstatus: draft\n---\n' > $SOURCE/.archcore/UNIQUE.md |
| 35 | +[Full original content removed for brevity — see git history if needed] |
82 | 36 |
|
83 | | -INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' |
84 | | -NOTIF='{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}' |
85 | | -LIST='{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_documents","arguments":{}}}' |
| 37 | +--- |
86 | 38 |
|
87 | | -# WITH ARCHCORE_CWD: launcher chdir's, sees marker |
88 | | -( cd $CACHE && printf '%s\n%s\n%s\n' "$INIT" "$NOTIF" "$LIST" | \ |
89 | | - env -i HOME=$HOME PATH=$PATH USER=$USER LANG=$LANG TERM=$TERM TMPDIR=$TMPDIR ARCHCORE_CWD=$SOURCE \ |
90 | | - ./bin/archcore mcp 2>/dev/null | grep '"id":2' ) |
| 39 | +## Why Rejected |
91 | 40 |
|
92 | | -# WITHOUT ARCHCORE_CWD: guard refuses to start, prints wrapper recipe on stderr |
93 | | -( cd $CACHE && env -i HOME=$HOME PATH=$PATH USER=$USER LANG=$LANG TERM=$TERM TMPDIR=$TMPDIR \ |
94 | | - ./bin/archcore mcp 2>&1 1>/dev/null ) |
| 41 | +- **Launcher removed.** The bundled `bin/archcore` shell launcher and all its Step 0 logic for `ARCHCORE_CWD` chdir have been deleted. |
| 42 | +- **Shell wrapper no longer needed.** The global CLI approach works with standard Codex MCP mechanisms; no user-side wrapper (`function codex; env ARCHCORE_CWD=$PWD ...`) is required. |
| 43 | +- **Tests removed.** Tests validating `ARCHCORE_CWD` chdir and guard behavior were part of `test/unit/launcher.bats` (deleted). |
| 44 | +- **Simpler architecture.** Codex now just resolves `archcore` on PATH like any other CLI-based MCP server. |
95 | 45 |
|
96 | | -rm $SOURCE/.archcore/UNIQUE.md |
97 | | -``` |
| 46 | +The global CLI approach is more maintainable and eliminates custom environment variables and wrapper logic. |
0 commit comments