codex-mcp-bridge
# codex-mcp-bridge
MCP server that lets an MCP client (Claude Code) drive OpenAI Codex as a
sub-agent: many independent or threaded queries, plus actions (shell
commands, file edits) when explicitly opted into.
Important: this project does not call the OpenAI HTTP API. It drives the
Codex CLI (the `codex` executable) locally and/or reads local Codex session
files. Authentication and session state come from the locally-installed
`codex` CLI (for example, `codex login`), and the bridge operates against
that local CLI and its session files rather than an external API key or
billing endpoint.
## Auth
Uses whatever `codex login` has already set up on this machine — a ChatGPT
subscription login, not API-key billing. The bridge launches the `codex`
executable as a child process so it inherits the current environment and the
CLI's local auth state. Do not try to configure `apiKey`/`baseUrl`/`env`
settings here: they are not used and `env` would replace the child's
environment rather than merging it, which breaks `codex` PATH resolution and
auth lookup.
Check status any time with:
```
codex login status
```
## Tool: `codex_ask`
One tool, parameterized:
- `prompt` (required), `working_directory` (required, absolute path — no
implicit default, so Codex never silently operates on the wrong repo).
- `thread_id` — pass back the id returned by a previous call to continue
that conversation; omit to start a new one. Threads are persisted by the
Codex CLI itself (`~/.codex/sessions`), so ids remain valid across
restarts of this server.
- `sandbox_mode` (`read-only` default / `workspace-write` / `danger-full-access`)
and `approval_policy` (`never` default / `on-request` / `on-failure` /
`untrusted`) — the "actions" knob. Default is read-only with no
auto-approval: Codex can inspect the repo and answer, but cannot write
files or run arbitrary commands unless a call explicitly asks for
`workspace-write`. Since this runs non-interactively (`codex exec` under
the hood), there's no one to answer an approval prompt either way —
anything outside `sandbox_mode` is simply denied.
- `require_git_repo`, `additional_directories`, `network_access`,
`web_search`, `model`, `reasoning_effort`, `output_schema` (JSON Schema
for structured output), `timeout_ms`.
Independent calls (different or omitted `thread_id`) run concurrently as
separate Codex conversations — that's what covers "many queries" alongside
a single ongoing thread.
## Tool: `codex_cloud_tasks`
Lists Codex Cloud background tasks, or (with `task_id`) shows one task's
status. Wraps `codex cloud list --json` / `codex cloud status <id>` directly
(not part of the CLI's documented surface) — these are undocumented/
experimental, so the JSON field names in the list output aren't guaranteed
stable across CLI versions. `status` has no `--json` form; its output is
passed through as-is.
## Tool: `codex_local_sessions`
Lists recent local Codex CLI sessions — the ones `codex resume` picks
from — most recently touched first. `codex resume` itself is an
interactive-only picker with no scriptable output, so this reads
`~/.codex/session_index.jsonl` directly instead (also undocumented). That
index only carries session id, current thread name, and last-updated time —
no live/idle status or working directory; a session can be renamed multiple
times, so entries are deduped by id, keeping the most recent name.
Note this index only records *interactively* created sessions. Threads
started programmatically (including by `codex_ask`) are real and resumable
but do not appear here.
## Tool: `codex_read_thread`
Reads the message history of **any** thread, interactive or programmatic, by
locating its rollout JSONL under `~/.codex/sessions` and parsing it. No API
call, no quota — it works even when the account is rate-limited.
- `thread_id` (required), `limit` (default 50, counts back from newest),
`include_reasoning` (default false), `max_chars` (per-message truncation).
Returns the thread's `cwd` and `source` from its `session_meta` header along
with the messages.
## Notes on driving a thread you want to watch
A thread created programmatically never enters the interactive session index,
so it won't show up in `codex resume`'s default picker or the desktop app. If
you want to *watch* the conversation in the normal Codex UI, start the thread
there first, then pass its id to `codex_ask`.
Two behaviours worth knowing:
- The desktop UI does not live-tail a thread, and navigating to it in an
existing window serves a cached copy. Open the thread in a **new window**
to see externally injected messages.
- A turn that fails (e.g. rate limit) has *already* appended your message to
the rollout. Rollouts are append-only, so a failed call is not a no-op.
## Build
```
npm install
npm run build
```
## Register with Claude Code (user scope)
```
claude mcp add codex-bridge --scope user -- node /absolute/path/to/codex-mcp-bridge/dist/index.js
```
## Probe scripts
`probe-*.mjs` are small standalone experiments used to work out the
behaviours above (app-server wire framing, what `codex mcp-server` exposes,
why `codex-reply` can't reach persisted threads, SDK-only resume). They are
not part of the server; each prints its usage when run without arguments.
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose: codex_ask sends prompts, codex_cloud_tasks manages cloud background tasks, codex_local_sessions lists local sessions, and codex_read_thread reads message history. There is no overlap in either target resource or action.
All tools share the codex_ prefix, making them easy to group, but the pattern is not perfectly uniform: codex_ask and codex_read_thread use verb_noun, while codex_cloud_tasks and codex_local_sessions are noun-only. Still, the names are clear and predictable enough.
Four tools is well-scoped for a lightweight bridge to Codex. Each tool covers a distinct integration point without being too sparse or bloated.
The set covers the core needs: sending prompts, reading threads, listing sessions, and checking cloud tasks. Minor gaps exist, such as no ability to cancel a cloud task or resume a local session programmatically, but these are not essential for the bridge's apparent purpose.