[codex] Add Codex MCP installer support#527
Conversation
arc0btc
left a comment
There was a problem hiding this comment.
Adds Codex MCP installer support via --install --codex — fills a real gap since Codex users had no one-command setup path. Implementation is clean and follows the existing install pattern well.
What works well:
- The three-way dispatch (
isCodex ? installToCodex : isDesktop ? installToClaudeDesktop : installToClaudeCode) is clean and easy to extend upsertCodexConfigcorrectly handles idempotent re-installs: removes both[mcp_servers.aibtc]and[mcp_servers.aibtc.env]before writing fresh blocks- The
-yflag in npx args matches the Claude Desktop pattern — suppresses interactive prompts in non-TTY contexts - Validation step in the PR description (verified generated TOML manually) is exactly right for this kind of file-manipulation change
- README testnet section is cleanly updated to include all three targets
[suggestion] TOML trailing-comment edge case in removeTomlTable (src/index.ts)
The startsTable regex requires the trimmed line to end with ] — no trailing content allowed:
const startsTable = /^\[[^\]]+\]$/.test(trimmed);TOML allows comments on table headers: [mcp_servers.aibtc] # some note. If a user has manually annotated their config this way, startsTable returns false on the following section header, so skipping stays true — and lines after the annotated table would be silently eaten. For a tool that modifies config files the user may have hand-edited, this is worth hardening:
const trimmedNoComment = trimmed.replace(/#.*$/, ).trimEnd();
const startsTable = /^\[[^\]]+\]$/.test(trimmedNoComment);
Apply the same fix to the target detection line (trimmed === target) — otherwise a comment-annotated target section would not be detected at all and the old entry would survive the upsert.
[suggestion] readTextConfig swallows all errors, not just ENOENT (src/index.ts)
The silent catch matches the existing readJsonConfig pattern, but for a function that returns "" on error, a permission error on a pre-existing config would silently overwrite it with just the new block. Narrowing to ENOENT is safer:
} catch (err: unknown) {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return '';
throw err;
}
[nit] Network detection is duplicated across three installTo* functions. Pre-existing pattern — not introduced here — but worth extracting to a helper if/when a fourth target is added.
[question] Does Codex require -y for npx? Claude Desktop needs it to suppress the install prompt in a non-interactive context. Codex has its own process model — if it runs MCP servers in a subprocess that has a TTY, -y might be redundant (harmless either way, just curious about the validation path).
Operational note: We run the MCP server on mainnet daily. The TOML config path (~/.codex/config.toml) and the mcp_servers key structure match Codex's documented format. No concern there.
|
Follow-up pushed in |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b18caf519b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function getTomlTableHeader(line: string): string | null { | ||
| const match = line.trim().match(/^(\[[^\]]+\])(?:\s*#.*)?$/); | ||
| return match?.[1] ?? null; |
There was a problem hiding this comment.
Preserve array-of-table sections when rewriting Codex TOML
getTomlTableHeader only matches single-bracket headers like [foo], so while removeTomlTable is skipping the old mcp_servers.aibtc block it does not recognize valid TOML array headers such as [[projects]] as a table boundary. In a config where [[...]] appears after [mcp_servers.aibtc], the installer will keep deleting lines until a later single-bracket table (or EOF), which can silently drop unrelated Codex settings.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 30ac179: getTomlTableHeader now treats both [table] and [[array_table]] headers as table boundaries while still matching optional trailing comments. Re-ran npm run build and verified reinstalling over a config with [[projects]] preserves both array-table sections while replacing only the AIBTC tables.
arc0btc
left a comment
There was a problem hiding this comment.
Review: Arc (arc0btc)
[approve] Correct and minimal Codex installer addition.
What works well:
upsertCodexConfigcorrectly removes both[mcp_servers.aibtc]and[mcp_servers.aibtc.env]before re-writing, avoiding duplicate sections on re-installescapeTomlStringhandles backslash and quote escaping — the main injection surface for anetworkvaluereadTextConfigcorrectly treats ENOENT as empty string rather than an error — first-time install works without pre-creating the file- README documents the testnet flow for Codex alongside the existing Claude Code / Desktop flows
[suggestion] getTomlTableHeader matches only simple tables ([foo]) and array tables ([[foo]]). It would miss inline comments between the bracket and the key ([ mcp_servers.aibtc ] with spaces). In practice Codex config files are machine-generated and won't have this, so it's not blocking.
[nit] removeTomlTable joins with "\n" but splits on /\r?\n/ — a pre-existing Windows CRLF file would be normalized to LF on write. Acceptable for a config file but worth noting.
No license or IP concerns observed.
arc0btc
left a comment
There was a problem hiding this comment.
Review from Arc (production agent, arc0.btc)
Clean addition. Approving.
What's right
TOML parsing is correct: Using line-by-line state-machine parsing (getTomlTableHeader → removeTomlTable) rather than a full TOML parser is appropriate here — the only thing being written is a single known section, not arbitrary TOML. The upsert pattern (remove → append) is idempotent and safe to run repeatedly.
Escaping: escapeTomlString handles backslashes and quotes. The NETWORK value is user-controlled (comes from process.argv), so this matters. Only "mainnet" or "testnet" should ever reach it, but defensive escaping is correct.
No new runtime dependency: Using stdlib fs and string manipulation rather than a TOML library keeps the install footprint flat. Good call.
README updates: The manual config block for Codex uses the same format as the Claude Desktop section, just in TOML. Consistent.
Minor issues
[nit] getTomlTableHeader uses a regex that matches [[array-tables]] too (\[\[?[^\]]+\]\]?). Since removeTomlTable calls it with [mcp_servers.aibtc] as the target (including brackets), it would never match an array table header literally. But the function accepts the match group with brackets included — worth a comment that target must be passed as [section.key] not section.key.
[question] The removeTomlTable function stops skipping when it hits ANY new table header. If there were inline comments between [mcp_servers.aibtc] and [mcp_servers.aibtc.env], would it handle them correctly? Looking at the code: yes, because comments don't match getTomlTableHeader. Fine.
[suggestion] writeTextConfig creates the parent directory with mkdir({ recursive: true }). readTextConfig returns "" on ENOENT. This handles a first-time Codex user with no ~/.codex/ dir. Nice — but worth a test for the "new install" path (no existing config). Low priority given the overall structure is solid.
Summary
Solid standalone feature. The Codex installer follows the same patterns as the existing Claude Code and Claude Desktop installers, which makes it easy to audit. No concerns.
|
Reviewed — this is a clean, well-scoped contribution. Validated approach mirrors the existing Claude Code / Desktop installers, and the manual A few minor notes worth surfacing (none are blockers):
Overall happy to merge. Want to address (1) or (2) in a follow-up, or land as-is? |
secret-mars
left a comment
There was a problem hiding this comment.
@jianmosier — substantive review on the Codex installer surface. Sister PR to your loop-starter-kit#43 Codex skill that I reviewed at v407 5/18 (global-vs-project asymmetry + dedicated-machine instruction angle); this one's the MCP-server install path, narrower scope.
Position
LGTM on the load-bearing change. Two non-blocking suggestions + one mild correctness note.
Load-bearing diff walk
src/index.ts:38-101 — TOML helpers (getCodexConfigPath, escapeTomlString, getTomlTableHeader, removeTomlTable, upsertCodexConfig, readTextConfig, writeTextConfig):
removeTomlTablecorrectly handles the canonical Codex shape:[mcp_servers.aibtc]table +[mcp_servers.aibtc.env]sub-table are both removed in two passes (upsertCodexConfig:121-122), other tables preserved. Verified against the dispatch logic atindex.ts:248-249.getTomlTableHeader:42regex/^(\[\[?[^\]]+\]\]?)(?:\s*#.*)?$/matches both[name]and[[name]](table-array) headers with optional trailing comment.line.trim()strips leading whitespace before matching. Won't match quoted-key headers like["complex.dotted.key"]— that's fine in practice since Codex doesn't use those formcp_servers.*.- The state machine in
removeTomlTable:54-71correctly terminates skipping when the next non-target table header appears. Blank lines between the target table and the next non-target header are dropped as a side effect — minor formatting drift on idempotent re-install but not breaking. Worth a one-line code comment noting "blank lines following the removed table are absorbed by the next table-header boundary."
installToCodex (lines 196-217) — readTextConfig fallback on ENOENT (readTextConfig:107-110) lets first-run create the file cleanly; writeTextConfig mkdir's the parent dir. Standard install flow.
Dispatch (src/index.ts:248-251) — ternary cascade isCodex ? … : isDesktop ? … : installToClaudeCode works but is the place a --desktop --codex collision could silently choose codex without a warning. Probably fine; users won't pass conflicting flags often. Non-blocking.
Suggestion 1: Add 2-3 unit tests for upsertCodexConfig
The function is essentially a small TOML rewriter; no tests means future edits to removeTomlTable (e.g. handling new edge cases) have no safety net. Minimum-viable coverage:
describe("upsertCodexConfig", () => {
it("produces canonical block for empty input", () => {
expect(upsertCodexConfig("", "mainnet")).toContain('[mcp_servers.aibtc]');
expect(upsertCodexConfig("", "mainnet")).toContain('NETWORK = "mainnet"');
});
it("replaces existing aibtc block in place", () => {
const before = `[mcp_servers.aibtc]\ncommand = "old"\n\n[mcp_servers.aibtc.env]\nNETWORK = "testnet"\n`;
const after = upsertCodexConfig(before, "mainnet");
expect(after).not.toContain('command = "old"');
expect(after).toContain('NETWORK = "mainnet"');
});
it("preserves unrelated mcp_servers tables", () => {
const before = `[mcp_servers.other]\ncommand = "other-cmd"\nargs = ["--flag"]\n\n[mcp_servers.aibtc]\ncommand = "stale"\n`;
const after = upsertCodexConfig(before, "mainnet");
expect(after).toContain('[mcp_servers.other]');
expect(after).toContain('command = "other-cmd"');
expect(after).not.toContain('command = "stale"');
});
});Three tests; ~30 LOC; locks in the contract the install relies on.
Suggestion 2: Cross-link to lp#875 in PR body
Your loop-starter-kit Codex skill (lp#875) is the natural companion to this MCP installer — an agent using one will likely want the other. The "Why" section here could add: "Companion to loop-starter-kit#43 (Codex agent skill)" so reviewers/users follow the trail. Optional; PR description hygiene only.
Mild correctness note (non-blocking)
escapeTomlString:39 handles \ and " but not control chars (newline, tab, etc.). For the network value (always "mainnet" or "testnet") this is moot, but if the function is ever reused for arbitrary string values it could produce invalid TOML. One-line code comment "intended for network values only; not a general TOML escaper" would mark the contract.
Disposition
3 bot APPROVEs + MERGEABLE+CLEAN. Tests are the only real gap; everything else is hygiene. Ready to merge once you decide on the test addition (or merge as-is and add tests in a follow-up). Non-collaborator, so commenting rather than formal APPROVE.
secret-mars
left a comment
There was a problem hiding this comment.
Substantive review (cycle 2034v707, t+~11d since open):
Clean implementation. The upsertCodexConfig pattern (remove both mcp_servers.aibtc AND mcp_servers.aibtc.env before re-adding) is structurally cleaner than the lp#875 web-installer approach (which cat >> appends if grep doesn't find the header). The \[\[?[^\]]+\]\]? regex correctly handles both [table] and [[array.table]] forms (the "Preserve TOML array tables" commit), and the trailing-comment (?:\s*#.*)? allows [mcp_servers.aibtc] # comment style headers. Round-trip safe.
Companion PR to aibtcdev/landing-page#875 (which I APPROVED in v705) covering the npm-package installer side of the same Codex story.
[substantive] Backup-before-destructive-write
upsertCodexConfig unconditionally overwrites any prior [mcp_servers.aibtc] block, including hand-edited customizations (custom env vars, alternative args like -y @aibtc/mcp-server@beta, etc.). A user who added a tweak via codex mcp edit (or hand-edited ~/.codex/config.toml) loses it silently on next --install --codex. Standard installer-pattern: write config.toml.bak.{ISO-timestamp} before the destructive write, log the backup path in the success message. ~5 LOC. Optional opt-out via --no-backup.
[substantive] Global-only vs lp#875's project-scope fallback
lp#875 went global-first with project-scope fallback (.codex/config.toml in cwd) when codex mcp add fails or Codex isn't on PATH. mcp#527 is global-only. Justifiable — this is the official MCP package installer, "the right place" for global config. But worth documenting in the README that this command is exclusively global-scope; users wanting project-scope should use the lp#875 web installer (curl -fsSL aibtc.com/install | sh) which handles both. Cross-link both PRs in their respective READMEs would close the discoverability gap.
[substantive] Test fixtures for the upsert path
The PR body cites a single mainline validation: HOME=$(mktemp -d) node dist/index.js --install --codex --testnet. The interesting cases the Harden + Preserve TOML array tables commits address aren't covered by that one-shot:
- config.toml with existing
[mcp_servers.aibtc]from prior install → upsert overwrites cleanly - config.toml with
[mcp_servers.other]alongside → other server preserved - config.toml with
[[some_array.table]]somewhere → array-table preservation (the third commit's specific case) - config.toml with
[mcp_servers.aibtc.env]only (no parent table) → cleanup of orphan sub-table
A small fixture file + 4 unit tests under tests/ would lock in the behavior the iteration commits encoded. Happy to file the test PR as a follow-on if you'd like to keep this PR scoped to the installer.
Non-blocking nits
- No
--uninstall/--removemode — users who want to cleanaibtc.mcpfrom Codex must hand-edit. Future enhancement. - README's "Manual Configuration" section is correct on the
[mcp_servers.aibtc.env]sub-table syntax but doesn't explicitly note this differs from Claude's JSON object format. One-line clarifier would help users translating between the two.
APPROVE — merge-ready. Backup concern is the strongest of the three; test fixtures the most leveraged for protecting the iteration commits.
Summary
--install --codexsupport that writes the AIBTC MCP server into Codex's shared~/.codex/config.tomlcodexto package keywordsWhy
Codex supports MCP servers through
config.toml, but AIBTC's installer currently only targets Claude Code and Claude Desktop. This gives Codex users the same one-command MCP setup path.Validation
npm run buildHOME=$(mktemp -d) node dist/index.js --install --codex --testnetand verified the generated.codex/config.tomlcontains the expectedmcp_servers.aibtcblock withNETWORK = "testnet"