claude-multiacc 2.0.38 → 2.0.39

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
@@ -2188,6 +2223,97 @@ EOF
2188
2223
  fi
2189
2224
  }
2190
2225
 
2226
+ # MCP servers for EVERY account (lib/mcp_registry.py) — the codex twin of
2227
+ # claude-accounts' cmd_mcp. `add`/`add-json`/`remove` edit this pool's registry under
2228
+ # the mutate lock, reconcile every account's config.toml, auto-sync, and by default
2229
+ # (--provider both) make the same change in the claude pool. `list`/`apply`/
2230
+ # `import-local` act on this pool alone; the overlay `import-local` writes is
2231
+ # machine-local by definition, so it is applied here and never synced.
2232
+ mcp_sibling_root() { # the other provider's pool root, by the same env precedence
2233
+ if [ "$MULTIACC_PROVIDER" = "codex" ]; then
2234
+ printf '%s\n' "${CLAUDE_ACCOUNTS_ROOT:-${CLAUDE_ACCOUNTS_DIR:-$HOME/.claude-accounts}}"
2235
+ else
2236
+ printf '%s\n' "${CODEX_ACCOUNTS_ROOT:-${CODEX_ACCOUNTS_DIR:-$HOME/.codex-accounts}}"
2237
+ fi
2238
+ }
2239
+ cmd_mcp() {
2240
+ require_manifest
2241
+ local sub="${1:-}" provider=both before_dd=1 a rc=0 sibling sibling_root
2242
+ local args=()
2243
+ case "$sub" in
2244
+ add|add-json|remove|list|apply|import-local) shift ;;
2245
+ "") usage; return 1 ;;
2246
+ *) die "unknown mcp subcommand: $sub (add, add-json, remove, list, apply, import-local)" ;;
2247
+ esac
2248
+ [ -f "$LIB_DIR/mcp_registry.py" ] || die "lib/mcp_registry.py is missing from this install"
2249
+ case "$sub" in
2250
+ list|apply|import-local)
2251
+ "$PYBIN" "$LIB_DIR/mcp_registry.py" --root "$ACC_ROOT" --provider "$MULTIACC_PROVIDER" "$sub" "$@"
2252
+ return $? ;;
2253
+ esac
2254
+ # --provider is OUR option: strip it, but only before a literal `--` — after it every
2255
+ # word belongs to the MCP server's own command line and is passed through untouched.
2256
+ while [ $# -gt 0 ]; do
2257
+ a="$1"
2258
+ if [ "$before_dd" = 1 ]; then
2259
+ case "$a" in
2260
+ --) before_dd=0 ;;
2261
+ --provider)
2262
+ [ $# -ge 2 ] || die "--provider needs a value: claude, codex or both"
2263
+ provider="$2"; shift 2; continue ;;
2264
+ --provider=*) provider="${a#--provider=}"; shift; continue ;;
2265
+ esac
2266
+ fi
2267
+ args+=("$a"); shift
2268
+ done
2269
+ case "$provider" in
2270
+ claude|codex|both) ;;
2271
+ *) die "--provider must be claude, codex or both (got '$provider')" ;;
2272
+ esac
2273
+ if [ "$provider" = "$MULTIACC_PROVIDER" ] || [ "$provider" = both ]; then
2274
+ mutate_lock || die "could not acquire the account lock — try again"
2275
+ trap mutate_unlock EXIT
2276
+ "$PYBIN" "$LIB_DIR/mcp_registry.py" --root "$ACC_ROOT" --provider "$MULTIACC_PROVIDER" "$sub" \
2277
+ ${args[@]+"${args[@]}"}
2278
+ rc=$?
2279
+ mutate_unlock
2280
+ trap - EXIT
2281
+ # 3 = the registry WAS saved and some account could not be reconciled (a corrupt
2282
+ # config, a layout the TOML editor refuses): the change still has to reach the
2283
+ # server, the peers and the sibling pool, and `mcp apply` repairs the account once
2284
+ # it is fixed. 1/2 = nothing was saved: stop here.
2285
+ case "$rc" in
2286
+ 0) ;;
2287
+ 3) printf '%s: registry saved; some accounts were not reconciled — run: %s mcp apply\n' \
2288
+ "$PROVIDER_CLI" "$PROVIDER_CLI" >&2 ;;
2289
+ *) return "$rc" ;;
2290
+ esac
2291
+ auto_sync
2292
+ fi
2293
+ if [ "$provider" != "$MULTIACC_PROVIDER" ]; then
2294
+ if [ "$MULTIACC_PROVIDER" = "codex" ]; then sibling=claude; else sibling=codex; fi
2295
+ sibling_root="$(mcp_sibling_root)"
2296
+ # With the DEFAULT (both), a machine without the sibling pool or CLI has nothing to
2297
+ # mirror, and silently so — a claude-only Mac must not warn on every codex
2298
+ # change. An EXPLICIT --provider claude asked for that pool by name: refuse loudly.
2299
+ if [ ! -f "$sibling_root/accounts.json" ]; then
2300
+ [ "$provider" = both ] && return "$rc"
2301
+ die "no $sibling pool on this machine ($sibling_root/accounts.json is missing)"
2302
+ fi
2303
+ if [ ! -x "$BIN_DIR/$sibling-accounts" ]; then
2304
+ [ "$provider" = both ] && return "$rc"
2305
+ die "$sibling-accounts is not installed beside $PROVIDER_CLI"
2306
+ fi
2307
+ # The caller's no-sync request travels with the hop, in the sibling's spelling.
2308
+ if [ "${CODEX_MULTIACC_NO_SYNC:-0}" = "1" ]; then export CLAUDE_MULTIACC_NO_SYNC=1; fi
2309
+ local sib_rc=0
2310
+ MULTIACC_PROVIDER="$sibling" "$BIN_DIR/$sibling-accounts" mcp "$sub" --provider "$sibling" \
2311
+ ${args[@]+"${args[@]}"} || sib_rc=$?
2312
+ [ "$rc" -eq 0 ] && rc="$sib_rc"
2313
+ fi
2314
+ return "$rc"
2315
+ }
2316
+
2191
2317
  cmd_post_sync() {
2192
2318
  require_manifest
2193
2319
  local_pool_fixup
@@ -2306,11 +2432,13 @@ PYEOF
2306
2432
  # distribution for as long as the arg loops rejected the flag. An UNKNOWN verb must still
2307
2433
  # FAIL, or the probe stops meaning what it says — so this list must hold exactly the verbs
2308
2434
  # 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"
2435
+ _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
2436
  case " $_KNOWN_VERBS " in
2311
2437
  *" ${1:-help} "*)
2438
+ # ...but never past a literal `--`: from there on the words belong to an MCP
2439
+ # server's own command line (`mcp add … -- node s.js -h 127.0.0.1`).
2312
2440
  for _arg in "$@"; do
2313
- case "$_arg" in --help|-h) usage; exit 0 ;; esac
2441
+ case "$_arg" in --) break ;; --help|-h) usage; exit 0 ;; esac
2314
2442
  done ;;
2315
2443
  esac
2316
2444
 
@@ -2335,6 +2463,7 @@ case "${1:-help}" in
2335
2463
  health) shift; cmd_health "$@" ;;
2336
2464
  self-update) shift; cmd_self_update "$@" ;;
2337
2465
  init-pool) shift; cmd_init_pool "$@" ;;
2466
+ mcp) shift; cmd_mcp "$@" ;;
2338
2467
  help|--help|-h) usage ;;
2339
2468
  *) usage; exit 1 ;;
2340
2469
  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,