codex-bridge
by ahmadjz
README.md
# codex-bridge
An MCP server that gives Claude Code (or any MCP client) the current Codex CLI as a tool.
Upstream Codex removed `codex mcp-server` in 0.154.0. Its successor, `codex app-server`, speaks its own JSON-RPC protocol, which is not MCP. codex-bridge sits in between: it runs `codex app-server` and exposes the same `codex` and `codex-reply` tools the old server had, so existing callers keep working. To update Codex, you swap the binary.
> Unofficial. Not affiliated with, endorsed by or supported by OpenAI. "Codex" is used only to describe what this tool talks to.
## Why not just pin an old Codex?
You can, but on a ChatGPT login the backend gates models by client version. A client pinned to 0.153.4 (the last one with `mcp-server`) only gets the older models. Newer ones (at the time of writing `gpt-6.1-sol`, `gpt-6-sol`, `gpt-6-luna`) return `400 ... not supported when using Codex with a ChatGPT account` on 0.153.4 and 0.154, but work on 0.163.0. Check what a given binary can see with:
```sh
/path/to/codex debug models
```
codex-bridge lets you run the newest binary through MCP, so you get the newest models.
## Requirements
- Node.js 20 or newer
- A Codex CLI that has `codex app-server` (built and tested against codex-cli 0.163.0)
- An existing Codex login (`codex login`). codex-bridge never reads or touches your tokens. Codex uses its own login from `~/.codex`, or from `CODEX_HOME` if set.
If you use a standalone release binary from GitHub, put `codex-code-mode-host` from the same release next to `codex`. Without it, 0.163.0 can't run shell commands ("execution host is missing"), and codex-bridge logs Codex's warning about it.
## Install
### Claude Code
```sh
claude mcp add codex-bridge -s user -e CODEX_BIN=/path/to/codex -- npx -y codex-bridge
```
Leave out `-e CODEX_BIN=...` to use whichever `codex` is on `PATH`.
Long reviews can run for many minutes. Claude Code's per-call MCP timeout is `MCP_TOOL_TIMEOUT` (milliseconds). Raise it in `~/.claude/settings.json`:
```json
{
"env": {
"MCP_TOOL_TIMEOUT": "1800000"
}
}
```
or per launch: `MCP_TOOL_TIMEOUT=1800000 claude`. codex-bridge also sends MCP progress notifications (turn started, commands run, files edited, a heartbeat every 15 s) while Codex works.
### Codex as the client
`~/.codex/config.toml`:
```toml
[mcp_servers.codex-bridge]
command = "npx"
args = ["-y", "codex-bridge"]
env = { CODEX_BIN = "/path/to/codex" }
tool_timeout_sec = 1800
```
### Cursor
`~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"codex-bridge": {
"command": "npx",
"args": ["-y", "codex-bridge"],
"env": { "CODEX_BIN": "/path/to/codex" }
}
}
}
```
### From a clone
```sh
npm ci && npm run build
claude mcp add codex-bridge -s user -e CODEX_BIN=/path/to/codex -- node /abs/path/to/codex-bridge/dist/index.js
```
## Tools
### `codex`
Starts a new Codex thread and runs one turn.
| Argument | Type | Notes |
| --- | --- | --- |
| `prompt` | string, required | The first user prompt |
| `model` | string | e.g. `gpt-6.1-sol` |
| `sandbox` | `read-only` \| `workspace-write` \| `danger-full-access` | |
| `approval-policy` | `on-request` \| `never` | See [Approvals](#approvals) |
| `cwd` | string | Relative paths resolve against the server's working directory |
| `config` | object | `config.toml` overrides, e.g. `{"model_reasoning_effort": "high"}` |
| `base-instructions` | string | Replaces the default instructions |
| `developer-instructions` | string | Injected as a developer message |
| `compact-prompt` | string | Prompt used when compacting the conversation |
Any argument you leave out falls back to your Codex config.
### `codex-reply`
Continues a thread: `threadId` (or the deprecated `conversationId`) plus `prompt`. The thread keeps its model, sandbox and approval settings. Threads are stored by Codex, so a reply works even after codex-bridge or app-server restarts.
### Result
Both tools return
```json
{ "threadId": "…", "content": "final agent message", "codexVersion": "0.163.0" }
```
as `structuredContent`, and the same JSON as the text content. `content` is the final answer of the turn. A failed or interrupted turn comes back with `isError: true` and the error text, starting with `threadId: …` when a thread exists.
## Approvals
No human sits between codex-bridge and Codex, so:
- `approval-policy: never`: Codex runs without asking, inside the sandbox you chose.
- `approval-policy: on-request`: when Codex asks to run a command or change files, codex-bridge asks you through MCP elicitation if your client supports it. The prompt shows the command, cwd and reason, or the list of files. Only an explicit accept approves, and only that one request, with one exception: a file-change request can also ask for write access to a directory for the rest of the session (`grantRoot`). The prompt says so, and accepting grants it. codex-bridge never picks Codex's "accept for session" option itself. If the client has no elicitation support, or the prompt is dismissed, fails or goes stale, the request is declined.
- Other Codex requests: permission escalations are granted nothing. Questions to the user and MCP elicitations from Codex's own MCP servers are declined. Token refresh and attestation requests are refused.
## Configuration
| Env var | Default | Meaning |
| --- | --- | --- |
| `CODEX_BIN` | `codex` on `PATH` | The Codex binary to drive |
| `CODEX_HOME` | `~/.codex` | Passed through to Codex (login, config, threads) |
| `CODEX_BRIDGE_DEBUG` | unset | `1` echoes app-server stderr to codex-bridge's stderr |
The app-server child inherits codex-bridge's full environment, because Codex needs `HOME`, `PATH`, `CODEX_HOME` and any provider keys you normally give it. codex-bridge logs to stderr only. It logs the binary path and version at startup, crash exit codes, and Codex's own warnings verbatim. App-server stderr is logged only with `CODEX_BRIDGE_DEBUG=1`. Progress messages sent to your MCP client include the commands Codex runs and the files it edits.
On startup codex-bridge runs `codex --version` and `codex app-server --help`. It logs the version to stderr, or exits with a clear error if the binary is missing or has no `app-server`.
## Updating Codex
1. Download the new release (or `npm i -g @openai/codex@latest`). For standalone binaries, take `codex` and `codex-code-mode-host` from the same release.
2. Replace the file `CODEX_BIN` points to (or update the env var).
3. Restart the MCP server (in Claude Code, `/mcp` and reconnect, or start a new session) so a fresh app-server starts from the new file.
4. Run `codex debug models` to see which models the new version unlocks.
You don't need to change anything in codex-bridge unless the app-server protocol changes in a way that breaks it. `codexVersion` in every result comes from the running app-server, so it shows what actually ran.
## How it works
One `codex app-server` child per codex-bridge process, started on first use and reused. Each tool call maps to `thread/start` (or `thread/resume`) plus `turn/start`, and the answer is the last `agentMessage` with `phase: "final_answer"` from that turn. Calls on the same thread run one at a time. If the child crashes, in-flight calls fail and the next call starts a fresh child. If the client cancels, the call sends `turn/interrupt`. On stdin EOF or a signal, codex-bridge closes the child's stdin, then sends SIGTERM and finally SIGKILL to its process group. Details are in [docs/design.md](docs/design.md).
Protocol: built against `codex app-server` from **codex-cli 0.163.0** (v2 API, non-experimental), with types generated by `codex app-server generate-ts`. To regenerate against another binary:
```sh
CODEX_BIN=/path/to/codex npm run gen:protocol
```
## Limitations
- `codex app-server` is marked experimental upstream. A future Codex release can change the protocol. Pin a known-good binary if that bites, and regenerate the types to see what changed.
- Text prompts only. No images, audio or file attachments.
- `codex-reply` can't change model, sandbox or approval settings mid-thread, matching the old server.
- `on-request` approvals need a client with MCP elicitation support. Otherwise every approval is declined.
- Progress notifications only flow if the client sends a progress token. Whether progress resets the client's timeout depends on the client, so set `MCP_TOOL_TIMEOUT` (or your client's equivalent) high for long runs.
- A turn has no server-side time limit. A cancelled call returns right away. If the turn ignores `turn/interrupt`, its thread stays busy, so later replies to it wait until it ends.
- All threads share one app-server child. If it crashes, or fails to answer a request within 120 s, codex-bridge restarts it, and every turn running at that moment fails, not only the one that hit the problem. The threads themselves survive and can be continued.
- If codex-bridge is killed with SIGKILL, cleanup depends on app-server exiting on stdin EOF (it does as of 0.163.0).
- Tested on Linux. macOS should work. Windows is untested.
## Development
```sh
npm ci
npm run lint && npm run typecheck && npm test
CODEX_BIN=/path/to/codex npm run test:e2e # opt-in, uses your real Codex login and quota
```
`CODEX_BRIDGE_E2E_MODEL` picks the e2e model (default `gpt-6.1-sol`).
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues