From e03f6ed6b5283054521215fac8a38a123138bb0f Mon Sep 17 00:00:00 2001 From: George Vaintrub Date: Sun, 31 May 2026 15:47:37 +0100 Subject: [PATCH] fix(mcp): make Codex MCP declarative (config base), not blocking CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #36's 'codex mcp add --url' blocks/fails on remote servers in non-interactive chezmoi apply (seen on the jetson: slow then 'failed'; CI missed it — unauthed codex failed fast). Codex MCP belongs in ~/.codex/config.toml [mcp_servers], already managed by modify_config.toml — so render it declaratively in codex-config-base.toml from the shared mcp: list. No CLI, no node/auth/network during apply. Claude keeps its working 'claude mcp add' (no declarative user-scope path exists). Same single source (packages.yaml mcp:). --- ..._onchange_after_70-install-plugins.sh.tmpl | 20 ++++++++----------- .chezmoitemplates/codex-config-base.toml | 7 +++++++ AGENTS.md | 4 ++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.chezmoiscripts/run_onchange_after_70-install-plugins.sh.tmpl b/.chezmoiscripts/run_onchange_after_70-install-plugins.sh.tmpl index e699f5b..97a6691 100644 --- a/.chezmoiscripts/run_onchange_after_70-install-plugins.sh.tmpl +++ b/.chezmoiscripts/run_onchange_after_70-install-plugins.sh.tmpl @@ -39,8 +39,10 @@ if command -v claude >/dev/null 2>&1; then {{ end -}} claude plugin marketplace update >/dev/null 2>&1 || true - # MCP servers (shared list, registered per-agent). User scope = all - # projects. Idempotent: `mcp get` succeeds if already registered. + # MCP servers (shared list in packages.yaml). Claude via CLI (no + # declarative user-scope config file exists); Codex gets the same list + # declaratively in config.toml. User scope = all projects. Idempotent: + # `mcp get` succeeds if already registered. {{ range .mcp -}} if ! claude mcp get {{ .name | quote }} >/dev/null 2>&1; then claude mcp add -s user --transport http {{ .name | quote }} {{ .url | quote }} >/dev/null 2>&1 \ @@ -62,16 +64,10 @@ if command -v codex >/dev/null 2>&1; then fi {{ end -}} codex plugin marketplace upgrade >/dev/null 2>&1 || true - - # Same MCP server list as Claude, via codex's own CLI (writes ~/.codex/ - # config.toml [mcp_servers]; survives modify_config.toml's merge). -{{ range .mcp -}} - if ! codex mcp get {{ .name | quote }} >/dev/null 2>&1; then - codex mcp add {{ .name | quote }} --url {{ .url | quote }} >/dev/null 2>&1 \ - && echo "codex mcp add {{ .name }}" \ - || echo "codex mcp add {{ .name }} failed" >&2 - fi -{{ end -}} + # Codex MCP servers are declarative — rendered into ~/.codex/config.toml + # [mcp_servers] by .chezmoitemplates/codex-config-base.toml. (codex's + # `mcp add` CLI blocks/fails on remote servers in non-interactive apply; + # the config file is robust + needs no node/auth/network.) else echo "codex CLI not found, skipping Codex plugin install" >&2 fi diff --git a/.chezmoitemplates/codex-config-base.toml b/.chezmoitemplates/codex-config-base.toml index 87476a9..5addf46 100644 --- a/.chezmoitemplates/codex-config-base.toml +++ b/.chezmoitemplates/codex-config-base.toml @@ -6,6 +6,13 @@ personality = "pragmatic" [plugins."{{ . }}"] enabled = true +{{ end -}} +{{ range .mcp -}} +# MCP servers (shared list in .chezmoidata/packages.yaml; Claude gets the same +# via `claude mcp add`). Declarative here = no blocking CLI during apply. +[mcp_servers.{{ .name }}] +url = "{{ .url }}" + {{ end -}} # Runtime-mutated sections ([projects.*], [notice], [tui.*], # [tool_suggest], windows_wsl_setup_acknowledged) are stripped on every diff --git a/AGENTS.md b/AGENTS.md index 73c8937..bd2c25d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,8 +29,8 @@ sh -c "$(curl -fsLS get.chezmoi.io/lb)" -- init --apply vaintrub | `iterm/com.googlecode.iterm2.plist` | real iTerm2 binary plist (chezmoi-ignored). iTerm2 reads + writes here directly via `PrefsCustomFolder` (set by run_once_after script) | | `dot_claude/` | Claude Code: instructions, settings (modify_), statusline, agents, skills, rules | | `dot_codex/` | Codex CLI: AGENTS.md, config.toml (modify_), shared skills (symlink) | -| `.chezmoidata/packages.yaml` | single source of truth for all packages (Mac brews/casks, Linux apt/dnf/npm), Claude/Codex plugin lists, AND MCP servers (`mcp:` — agent-agnostic, registered into both Claude + Codex; keyless remote only) | -| `.chezmoiscripts/` | install hooks (run_once_after_*, run_onchange_after_*). Per-OS subdirs: `darwin/`, `linux/`. Numerical prefix (50/70/99) controls ordering — 50 installs packages (+ mise tools + post-installs from lib), 70 installs Claude+Codex plugins + registers MCP servers into both, 99 prints first-apply hint | +| `.chezmoidata/packages.yaml` | single source of truth for all packages (Mac brews/casks, Linux apt/dnf/npm), Claude/Codex plugin lists, AND MCP servers (`mcp:` — one list, into both agents: Claude via `claude mcp add` in 70, Codex declaratively in `codex-config-base.toml`; keyless remote only) | +| `.chezmoiscripts/` | install hooks (run_once_after_*, run_onchange_after_*). Per-OS subdirs: `darwin/`, `linux/`. Numerical prefix (50/70/99) controls ordering — 50 installs packages (+ mise tools + post-installs from lib), 70 installs Claude+Codex plugins + registers Claude MCP servers (Codex MCP is declarative in its config base), 99 prints first-apply hint | | `hooks/` | hook scripts registered in `.chezmoi.toml.tmpl`. `ensure-prereqs.sh` runs BEFORE source-state read on every apply, installing minimum tools (git/zsh/vim/tmux/curl + brew on Mac) | | `.chezmoitemplates/` | shared template partials (base configs read by `modify_` scripts via `includeTemplate`, not applied to $HOME) | | `lib/` | shell libraries sourced by `.chezmoiscripts/*` wrappers via `{{ .chezmoi.sourceDir }}/lib/X.sh`. Pure POSIX `sh`, no template syntax. Chezmoi-ignored so it never deploys to $HOME — also makes the libraries `source`-able by bats unit tests with mocked externals. |