claude-multiacc 2.0.38 → 2.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,6 +100,32 @@ USAGE
100
100
  codex-accounts health limits + full verify; logs to health.log
101
101
  codex-accounts self-update update this npm/git install; logs to update.log
102
102
  codex-accounts post-sync (server side) seed dirs, fix perms, quick verify
103
+ codex-accounts mcp add <name> [--provider claude|codex|both] [--scope user]
104
+ [-e KEY=VAL]... [-H 'Header: v']... [--transport stdio|http]
105
+ -- <command> [args...] (or a URL)
106
+ register an MCP server for EVERY account: writes the pool's mcp-servers.json
107
+ registry ([mcp_servers.<name>] in every account's config.toml), syncs the
108
+ registry to the server and peers (their post-sync applies it). Default
109
+ --provider both: the same server is registered in the claude pool too.
110
+ A stock `codex mcp add|remove` run under the shim is mirrored into the
111
+ registry the same way — it no longer lands in one random account.
112
+ Project scope is claude-only: Codex reads no per-project MCP config.
113
+ -e takes ONE variable per flag (KEY=VAL). Exit 3 = registry saved, but an account
114
+ could not be reconciled (named on stderr): the change still syncs; fix the account
115
+ and run `mcp apply`. On a replica pool the change lands in the machine-local
116
+ overlay instead of the synced registry — make registry changes on the source.
117
+ The codex registry syncs only to codex sync targets; a runner Mac's local-only
118
+ codex pool is fed by app-robot's overlay (`mcp import-local --owner app-robot`).
119
+ codex-accounts mcp add-json <name> '<json>' [--provider ...]
120
+ codex-accounts mcp remove <name> [--provider ...] [--user-only]
121
+ unregister AND retire EVERYWHERE: every account (fleet-wide after sync) drops the
122
+ server, even one that was hand-added to a single account (--user-only spares a
123
+ claude project-local copy in the claude pool).
124
+ codex-accounts mcp list [--json] effective registry (+ machine-local overlay)
125
+ codex-accounts mcp apply [--account-dir DIR]... [--all]
126
+ re-apply the registry now (repair drift)
127
+ codex-accounts mcp import-local --owner NAME (stdin JSON) machine-local overlay for
128
+ a runner daemon; applied here, never synced
103
129
 
104
130
  ENV
105
131
  CODEX_ACCOUNTS_ROOT pool root, overriding ~/.codex-accounts — one isolated pool
@@ -112,6 +138,7 @@ ENV
112
138
  CODEX_SHIM_RETRY=0 disable the `codex exec` auto-retry
113
139
  CODEX_MULTIACC_DISABLE=1 bypass the shim entirely
114
140
  CODEX_MULTIACC_AUTO_RESET=0 disable automatic usage-reset redemption
141
+ CODEX_MULTIACC_MCP=0 disable the MCP registry (no reconcile at seed/launch, no mirror)
115
142
  EOF
116
143
  }
117
144
 
@@ -2001,6 +2028,14 @@ sync_push_target() {
2001
2028
  || fail "cannot reach $server"
2002
2029
  rsync -az "$MANIFEST" "$server:$sroot/accounts.json" >>"$ACC_ROOT/sync.log" 2>&1 \
2003
2030
  || fail "manifest push to $server failed"
2031
+ # The pool's MCP registry rides with the manifest; the target's post-sync reconciles it
2032
+ # into every account dir there. A missing local registry leaves the target's alone,
2033
+ # and the machine-local overlay (mcp-servers.local.json, a runner daemon's own view
2034
+ # of THIS Mac) never travels.
2035
+ if [ -f "$ACC_ROOT/mcp-servers.json" ]; then
2036
+ rsync -az "$ACC_ROOT/mcp-servers.json" "$server:$sroot/mcp-servers.json" >>"$ACC_ROOT/sync.log" 2>&1 \
2037
+ || fail "MCP registry push to $server failed"
2038
+ fi
2004
2039
 
2005
2040
  local id d
2006
2041
  for id in $(account_ids); do
@@ -2173,7 +2208,12 @@ EOF
2173
2208
  if [ -n "$peers" ]; then
2174
2209
  while IFS="$tab" read -r pt pr pp; do
2175
2210
  [ -n "$pt" ] || continue
2176
- sync_push_target "$pt" "$pr" "$pp"
2211
+ # </dev/null: the ssh/rsync calls inside read STDIN, and stdin here is the
2212
+ # peer list itself — the first peer's ssh swallowed every later line, so sync
2213
+ # reached gas-mini (first in the list) and never mini-3..mini-8 (peers since
2214
+ # 2026-09-04; the limits fan-out, which reads its list through process
2215
+ # substitution, was the only thing that ever reached them).
2216
+ sync_push_target "$pt" "$pr" "$pp" </dev/null
2177
2217
  npeers=$((npeers + 1))
2178
2218
  done <<EOF
2179
2219
  $peers
@@ -2188,6 +2228,97 @@ EOF
2188
2228
  fi
2189
2229
  }
2190
2230
 
2231
+ # MCP servers for EVERY account (lib/mcp_registry.py) — the codex twin of
2232
+ # claude-accounts' cmd_mcp. `add`/`add-json`/`remove` edit this pool's registry under
2233
+ # the mutate lock, reconcile every account's config.toml, auto-sync, and by default
2234
+ # (--provider both) make the same change in the claude pool. `list`/`apply`/
2235
+ # `import-local` act on this pool alone; the overlay `import-local` writes is
2236
+ # machine-local by definition, so it is applied here and never synced.
2237
+ mcp_sibling_root() { # the other provider's pool root, by the same env precedence
2238
+ if [ "$MULTIACC_PROVIDER" = "codex" ]; then
2239
+ printf '%s\n' "${CLAUDE_ACCOUNTS_ROOT:-${CLAUDE_ACCOUNTS_DIR:-$HOME/.claude-accounts}}"
2240
+ else
2241
+ printf '%s\n' "${CODEX_ACCOUNTS_ROOT:-${CODEX_ACCOUNTS_DIR:-$HOME/.codex-accounts}}"
2242
+ fi
2243
+ }
2244
+ cmd_mcp() {
2245
+ require_manifest
2246
+ local sub="${1:-}" provider=both before_dd=1 a rc=0 sibling sibling_root
2247
+ local args=()
2248
+ case "$sub" in
2249
+ add|add-json|remove|list|apply|import-local) shift ;;
2250
+ "") usage; return 1 ;;
2251
+ *) die "unknown mcp subcommand: $sub (add, add-json, remove, list, apply, import-local)" ;;
2252
+ esac
2253
+ [ -f "$LIB_DIR/mcp_registry.py" ] || die "lib/mcp_registry.py is missing from this install"
2254
+ case "$sub" in
2255
+ list|apply|import-local)
2256
+ "$PYBIN" "$LIB_DIR/mcp_registry.py" --root "$ACC_ROOT" --provider "$MULTIACC_PROVIDER" "$sub" "$@"
2257
+ return $? ;;
2258
+ esac
2259
+ # --provider is OUR option: strip it, but only before a literal `--` — after it every
2260
+ # word belongs to the MCP server's own command line and is passed through untouched.
2261
+ while [ $# -gt 0 ]; do
2262
+ a="$1"
2263
+ if [ "$before_dd" = 1 ]; then
2264
+ case "$a" in
2265
+ --) before_dd=0 ;;
2266
+ --provider)
2267
+ [ $# -ge 2 ] || die "--provider needs a value: claude, codex or both"
2268
+ provider="$2"; shift 2; continue ;;
2269
+ --provider=*) provider="${a#--provider=}"; shift; continue ;;
2270
+ esac
2271
+ fi
2272
+ args+=("$a"); shift
2273
+ done
2274
+ case "$provider" in
2275
+ claude|codex|both) ;;
2276
+ *) die "--provider must be claude, codex or both (got '$provider')" ;;
2277
+ esac
2278
+ if [ "$provider" = "$MULTIACC_PROVIDER" ] || [ "$provider" = both ]; then
2279
+ mutate_lock || die "could not acquire the account lock — try again"
2280
+ trap mutate_unlock EXIT
2281
+ "$PYBIN" "$LIB_DIR/mcp_registry.py" --root "$ACC_ROOT" --provider "$MULTIACC_PROVIDER" "$sub" \
2282
+ ${args[@]+"${args[@]}"}
2283
+ rc=$?
2284
+ mutate_unlock
2285
+ trap - EXIT
2286
+ # 3 = the registry WAS saved and some account could not be reconciled (a corrupt
2287
+ # config, a layout the TOML editor refuses): the change still has to reach the
2288
+ # server, the peers and the sibling pool, and `mcp apply` repairs the account once
2289
+ # it is fixed. 1/2 = nothing was saved: stop here.
2290
+ case "$rc" in
2291
+ 0) ;;
2292
+ 3) printf '%s: registry saved; some accounts were not reconciled — run: %s mcp apply\n' \
2293
+ "$PROVIDER_CLI" "$PROVIDER_CLI" >&2 ;;
2294
+ *) return "$rc" ;;
2295
+ esac
2296
+ auto_sync
2297
+ fi
2298
+ if [ "$provider" != "$MULTIACC_PROVIDER" ]; then
2299
+ if [ "$MULTIACC_PROVIDER" = "codex" ]; then sibling=claude; else sibling=codex; fi
2300
+ sibling_root="$(mcp_sibling_root)"
2301
+ # With the DEFAULT (both), a machine without the sibling pool or CLI has nothing to
2302
+ # mirror, and silently so — a claude-only Mac must not warn on every codex
2303
+ # change. An EXPLICIT --provider claude asked for that pool by name: refuse loudly.
2304
+ if [ ! -f "$sibling_root/accounts.json" ]; then
2305
+ [ "$provider" = both ] && return "$rc"
2306
+ die "no $sibling pool on this machine ($sibling_root/accounts.json is missing)"
2307
+ fi
2308
+ if [ ! -x "$BIN_DIR/$sibling-accounts" ]; then
2309
+ [ "$provider" = both ] && return "$rc"
2310
+ die "$sibling-accounts is not installed beside $PROVIDER_CLI"
2311
+ fi
2312
+ # The caller's no-sync request travels with the hop, in the sibling's spelling.
2313
+ if [ "${CODEX_MULTIACC_NO_SYNC:-0}" = "1" ]; then export CLAUDE_MULTIACC_NO_SYNC=1; fi
2314
+ local sib_rc=0
2315
+ MULTIACC_PROVIDER="$sibling" "$BIN_DIR/$sibling-accounts" mcp "$sub" --provider "$sibling" \
2316
+ ${args[@]+"${args[@]}"} || sib_rc=$?
2317
+ [ "$rc" -eq 0 ] && rc="$sib_rc"
2318
+ fi
2319
+ return "$rc"
2320
+ }
2321
+
2191
2322
  cmd_post_sync() {
2192
2323
  require_manifest
2193
2324
  local_pool_fixup
@@ -2306,11 +2437,13 @@ PYEOF
2306
2437
  # distribution for as long as the arg loops rejected the flag. An UNKNOWN verb must still
2307
2438
  # FAIL, or the probe stops meaning what it says — so this list must hold exactly the verbs
2308
2439
  # the dispatcher below implements, and a test pins that both ways.
2309
- _KNOWN_VERBS="list status add import export-credential export-cred import-credential import-cred adopt dedupe remove login expired relogin re-login sync configure verify limits post-sync health self-update init-pool"
2440
+ _KNOWN_VERBS="list status add import export-credential export-cred import-credential import-cred adopt dedupe remove login expired relogin re-login sync configure verify limits post-sync health self-update init-pool mcp"
2310
2441
  case " $_KNOWN_VERBS " in
2311
2442
  *" ${1:-help} "*)
2443
+ # ...but never past a literal `--`: from there on the words belong to an MCP
2444
+ # server's own command line (`mcp add … -- node s.js -h 127.0.0.1`).
2312
2445
  for _arg in "$@"; do
2313
- case "$_arg" in --help|-h) usage; exit 0 ;; esac
2446
+ case "$_arg" in --) break ;; --help|-h) usage; exit 0 ;; esac
2314
2447
  done ;;
2315
2448
  esac
2316
2449
 
@@ -2335,6 +2468,7 @@ case "${1:-help}" in
2335
2468
  health) shift; cmd_health "$@" ;;
2336
2469
  self-update) shift; cmd_self_update "$@" ;;
2337
2470
  init-pool) shift; cmd_init_pool "$@" ;;
2471
+ mcp) shift; cmd_mcp "$@" ;;
2338
2472
  help|--help|-h) usage ;;
2339
2473
  *) usage; exit 1 ;;
2340
2474
  esac
@@ -165,6 +165,40 @@ set, which is what prevents two machines from overwriting each other's manifests
165
165
  Make account changes on the source; sign-ins (`login`/`relogin`) still happen on
166
166
  whichever machine needs the credential.
167
167
 
168
+ ### MCP servers for every account
169
+
170
+ MCP servers live in the config dir, i.e. per account. Never register one with a stock
171
+ `claude mcp add` on a pool Mac and expect it everywhere — under the shim that command is
172
+ mirrored into the registry (see below), but the registry is the source of truth, and it is
173
+ edited on the **source** machine (a replica's own changes land in its machine-local overlay
174
+ and are not pushed anywhere):
175
+
176
+ ```bash
177
+ claude-accounts mcp add appinspire-mcp -- npx -y appinspire-mcp@latest serve
178
+ # writes ~/.claude-accounts/mcp-servers.json AND ~/.codex-accounts/mcp-servers.json,
179
+ # reconciles every account of both pools, auto-syncs the registry to server + peers
180
+ claude-accounts mcp add --provider claude --scope project --project "$HOME/design-lab/run1" appinspire -e APPINSPIRE_LIBRARY_DIR="$HOME/.appinspire-mcp/library" -- node "$HOME/appinspire-mcp/bin/appinspire-mcp.mjs" serve
181
+ # a checkout, registered for ONE project (claude only: codex reads no project config);
182
+ # one -e per variable, and $HOME rather than ~ inside a value
183
+ claude-accounts mcp remove adspower-local-api # retire EVERYWHERE: user scope + every project entry, hand-added copies too
184
+ claude-accounts mcp remove foo --user-only # user scope only
185
+ claude-accounts mcp list # effective registry (+ machine-local overlay)
186
+ claude-accounts mcp apply # re-apply to every account now (exit 3 from add/remove = do this after fixing the account it named)
187
+ codex-accounts mcp list # the codex pool's view
188
+ ```
189
+
190
+ How a fleet Mac receives it: `sync` (and every claude limits pass) pushes the claude
191
+ `mcp-servers.json` next to the manifest to the server and every peer; the target's
192
+ `post-sync` re-seeds its account dirs, which applies the registry; and the shim there
193
+ re-applies it to the picked account right before exec whenever the file changed. The codex
194
+ registry travels only to codex sync targets (the server, gas-mini) — runner Macs' codex pools
195
+ are local-only and are fed by app-robot's runner overlay (`codex-accounts mcp import-local
196
+ --owner app-robot`) or a direct `codex-accounts mcp add --provider codex …` on that Mac. A
197
+ runner daemon's overlay (`<provider>-accounts mcp import-local --owner NAME`, JSON on stdin)
198
+ is applied on the Mac and never synced. `--help` on `mcp` lists every option;
199
+ `CLAUDE_MULTIACC_MCP=0` / `CODEX_MULTIACC_MCP=0` stop the reconcile at seed and launch and the
200
+ shim's mirror (the CLI, sync and fan-out keep working).
201
+
168
202
  ### Pointing sync somewhere else (or nowhere)
169
203
 
170
204
  The target is no longer baked in. In precedence order:
package/docs/CODEX.md CHANGED
@@ -83,6 +83,32 @@ codex-accounts --help # all account commands and options
83
83
  claude-multiacc codex status # same status command via the npm wrapper
84
84
  ```
85
85
 
86
+ ## MCP servers for every account
87
+
88
+ `codex-accounts mcp add|add-json|remove|list|apply|import-local` manages the pool's MCP
89
+ registry (`~/.codex-accounts/mcp-servers.json`); `add`/`remove` default to `--provider both`,
90
+ so the claude pool beside it gets the same change, and `mcp remove` retires the server in
91
+ every account (`--user-only` keeps a claude project-local copy). A stock `codex mcp add|remove`
92
+ run under the shim — or from inside a pooled codex session — is mirrored into the registry and
93
+ every account; on a replica pool the mirror lands in the machine-local overlay instead, so
94
+ make registry changes on the source. The registry holds Claude-style blocks; for codex each
95
+ becomes a `[mcp_servers.<name>]` table in every account's `config.toml`, and unrelated
96
+ tables, their comments and blank lines are left alone: `command`/`args` as is, literal `env`
97
+ values as an inline `env` table, a `KEY=${KEY}` reference as `env_vars = ["KEY"]` (codex
98
+ forwards a variable under its own name only and expands nothing, so `KEY=${OTHER}` and
99
+ `${VAR:-default}` cannot be expressed and are left out with a warning), `headers` on an HTTP
100
+ server as `http_headers` (literal) / `env_http_headers` (references), `Authorization: Bearer
101
+ ${VAR}` as `bearer_token_env_var`, and, for a stdio server, `startup_timeout_sec = 60` unless
102
+ the block sets one (a cold `npx` download must not trip codex's 10 s default). Codex-only keys
103
+ codex itself wrote (`oauth`, `enabled_tools`, …) ride along in the registry block and never
104
+ reach `.claude.json`. SSE transports cannot be expressed and are skipped with a warning;
105
+ project scope is claude-only because Codex 0.156 reads no per-project MCP config. Python
106
+ 3.11+ is found the same way as for `configure`. `CODEX_MULTIACC_MCP=0` stops the reconcile at
107
+ seed and launch and the mirror; the CLI keeps working. The codex registry syncs only to codex
108
+ sync targets — a runner Mac's local-only codex pool gets its servers from app-robot's runner
109
+ overlay (`mcp import-local --owner app-robot`) or a direct `codex-accounts mcp add
110
+ --provider codex …` there.
111
+
86
112
  ## Opt-in subagent concurrency policy
87
113
 
88
114
  The addon does not choose delegation policy by default. To manage the concurrent
package/lib/common.sh CHANGED
@@ -685,6 +685,23 @@ codex_share_state_index() { # $1 acct dir — seed time: whatever index names ex
685
685
  # shared sessions/ symlink (=> `codex resume` finds every session) with the
686
686
  # shared rollout index that tree needs, and a shared AGENTS.md symlink so
687
687
  # global instructions apply under any account.
688
+ # MCP servers for EVERY account (lib/mcp_registry.py). Both clients keep MCP servers in
689
+ # the CONFIG DIR (`<dir>/.claude.json`, `<CODEX_HOME>/config.toml`), so under the pool
690
+ # there is one MCP configuration per account and a stock `claude mcp add` lands in ONE
691
+ # random one. The pool's registry (mcp-servers.json, synced) plus the machine-local
692
+ # overlay (mcp-servers.local.json, never synced) are reconciled into every account here,
693
+ # at seed time, and by the shims right before exec. Fail-open: a seed never dies on it.
694
+ seed_mcp_registry() { # $1 = acct dir
695
+ local d="$1" off
696
+ if [ "$MULTIACC_PROVIDER" = "codex" ]; then off="${CODEX_MULTIACC_MCP:-1}"; else off="${CLAUDE_MULTIACC_MCP:-1}"; fi
697
+ [ "$off" = "0" ] && return 0
698
+ [ -f "$ACC_ROOT/mcp-servers.json" ] || [ -f "$ACC_ROOT/mcp-servers.local.json" ] || return 0
699
+ [ -f "$LIB_DIR/mcp_registry.py" ] || return 0
700
+ "$PYBIN" "$LIB_DIR/mcp_registry.py" --root "$ACC_ROOT" --provider "$MULTIACC_PROVIDER" apply \
701
+ --account-dir "$d" --fail-open --quiet >/dev/null || true
702
+ return 0
703
+ }
704
+
688
705
  seed_account_dir() { # $1 = acct dir
689
706
  local d="$1"
690
707
  mkdir -p "$d"
@@ -704,6 +721,7 @@ seed_account_dir() { # $1 = acct dir
704
721
  ln -s "$HOME/.codex/AGENTS.md" "$d/AGENTS.md" 2>/dev/null || true
705
722
  fi
706
723
  codex_share_state_index "$d"
724
+ seed_mcp_registry "$d"
707
725
  return 0
708
726
  fi
709
727
  if [ ! -f "$d/.claude.json" ] && [ -f "$HOME/.claude.json" ]; then
@@ -730,6 +748,7 @@ PYEOF
730
748
  if [ ! -e "$d/projects" ] && [ -d "$HOME/.claude/projects" ]; then
731
749
  ln -s "$HOME/.claude/projects" "$d/projects" 2>/dev/null || true
732
750
  fi
751
+ seed_mcp_registry "$d"
733
752
  }
734
753
 
735
754
  # Short-lived mutation lock: serialize only the brief critical sections (id reservation,