Skip to content

[codex] Add Codex MCP installer support#527

Open
jianmosier wants to merge 3 commits into
aibtcdev:mainfrom
jianmosier:codex/install-support
Open

[codex] Add Codex MCP installer support#527
jianmosier wants to merge 3 commits into
aibtcdev:mainfrom
jianmosier:codex/install-support

Conversation

@jianmosier

Copy link
Copy Markdown

Summary

  • add --install --codex support that writes the AIBTC MCP server into Codex's shared ~/.codex/config.toml
  • document Codex quick start, testnet install, and manual TOML configuration
  • add codex to package keywords

Why

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 build
  • HOME=$(mktemp -d) node dist/index.js --install --codex --testnet and verified the generated .codex/config.toml contains the expected mcp_servers.aibtc block with NETWORK = "testnet"

@arc0btc arc0btc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
  • upsertCodexConfig correctly handles idempotent re-installs: removes both [mcp_servers.aibtc] and [mcp_servers.aibtc.env] before writing fresh blocks
  • The -y flag 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.

@jianmosier

Copy link
Copy Markdown
Author

Follow-up pushed in b18caf5: hardened Codex config updates by handling table headers with trailing comments and limiting readTextConfig fallback to ENOENT so permission/read errors are surfaced instead of treating the config as empty. Re-ran npm run build and verified reinstalling over comment-annotated TOML preserves unrelated sections and replaces the AIBTC tables.

@jianmosier jianmosier marked this pull request as ready for review May 17, 2026 01:31

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/index.ts
Comment on lines +45 to +47
function getTomlTableHeader(line: string): string | null {
const match = line.trim().match(/^(\[[^\]]+\])(?:\s*#.*)?$/);
return match?.[1] ?? null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 arc0btc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Arc (arc0btc)

[approve] Correct and minimal Codex installer addition.

What works well:

  • upsertCodexConfig correctly removes both [mcp_servers.aibtc] and [mcp_servers.aibtc.env] before re-writing, avoiding duplicate sections on re-install
  • escapeTomlString handles backslash and quote escaping — the main injection surface for a network value
  • readTextConfig correctly 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 arc0btc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review from Arc (production agent, arc0.btc)

Clean addition. Approving.

What's right

TOML parsing is correct: Using line-by-line state-machine parsing (getTomlTableHeaderremoveTomlTable) 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.

@biwasxyz

Copy link
Copy Markdown
Collaborator

Reviewed — this is a clean, well-scoped contribution. Validated approach mirrors the existing Claude Code / Desktop installers, and the manual HOME=$(mktemp -d) test in the PR description is exactly the right thing to check.

A few minor notes worth surfacing (none are blockers):

  1. Re-install wipes user-added env vars. upsertCodexConfig always rewrites [mcp_servers.aibtc.env] with only NETWORK. If a user had added e.g. CLIENT_MNEMONIC or OPENROUTER_API_KEY to that section, a re-run of --install --codex would silently drop them. This matches the behavior of the Claude Code / Desktop installers (overwrite-on-install), so it's consistent — just calling it out so we're deliberate about the trade-off.

  2. Inline-table form isn't handled. removeTomlTable only matches [mcp_servers.aibtc] / [[...]] headers. If a user already configured the server using inline-table syntax (aibtc = { command = "npx", ... } under a parent [mcp_servers]), the existing entry wouldn't be stripped and you'd end up with a duplicate key. Probably acceptable for an installer that owns the section, but worth a one-line README note if we want to be thorough.

  3. Flag precedence is silent. In the dispatcher, --codex + --desktop together → Codex wins with no warning. Easy fix if you want it explicit; otherwise fine to leave.

Overall happy to merge. Want to address (1) or (2) in a follow-up, or land as-is?

@secret-mars secret-mars left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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):

  • removeTomlTable correctly 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 at index.ts:248-249.
  • getTomlTableHeader:42 regex /^(\[\[?[^\]]+\]\]?)(?:\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 for mcp_servers.*.
  • The state machine in removeTomlTable:54-71 correctly 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 secret-mars left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. config.toml with existing [mcp_servers.aibtc] from prior install → upsert overwrites cleanly
  2. config.toml with [mcp_servers.other] alongside → other server preserved
  3. config.toml with [[some_array.table]] somewhere → array-table preservation (the third commit's specific case)
  4. 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 / --remove mode — users who want to clean aibtc.mcp from 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants