Codex Bridge
by jonwadsworth
README.md
# Codex Bridge
A self-hosted MCP server that lets **claude.ai** (and other MCP clients) delegate
work to **OpenAI Codex CLI** — billed through the operator's existing **ChatGPT
subscription**, not per-token API usage.
## Why
Claude Code can already shell out to Codex CLI locally (via `codex-plugin-cc`)
because it has a local shell. **claude.ai has no shell** — custom MCP connectors
are reached from Anthropic's cloud over HTTPS, with no way to run a local CLI.
Codex Bridge closes that gap: a small always-on server, designed to run on a
personal VPS, that exposes Codex CLI as MCP tools over HTTPS using a
ChatGPT-authenticated Codex CLI session running on the server itself.
## Architecture
```
claude.ai ──HTTPS(OAuth 2.1)──▶ Codex Bridge MCP Server ──subprocess──▶ Codex CLI ──ChatGPT session──▶ OpenAI
(Anthropic's cloud) (your VPS) (codex exec)
```
- **MCP server:** Python, MCP Python SDK (`FastMCP`) directly — no separate web
framework. Streamable HTTP transport.
- **Job manager:** wraps `codex exec` as a detached subprocess per job. State
(`queued`/`running`/`done`/`errored`/`cancelled`) and captured stdout/stderr
paths persist in SQLite, so a server restart doesn't orphan job tracking.
Every job is confined to an explicit working-directory allowlist — the tools
never accept an arbitrary filesystem path.
- **Auth:** OAuth 2.1 (dynamic client registration + PKCE), the only thing
claude.ai's Connectors feature actually speaks. One resource owner
(username/password you set at install time), tokens persisted to SQLite.
## MCP tools
| Tool | Purpose |
|---|---|
| `codex_delegate(prompt, working_dir, model?)` | Submit a task, returns a `job_id` immediately |
| `codex_status(job_id)` | Cheap poll — job state and timestamps |
| `codex_result(job_id, max_chars?)` | Tailed stdout/stderr once the job is done or errored |
| `codex_cancel(job_id)` | Cancel a running or queued job |
`codex_delegate` never blocks on the Codex run — MCP tool calls must return
quickly, and Codex jobs can run for minutes. Submit, then poll.
## Install
Requires a Linux box you control (tested against Ubuntu). You'll need root.
```bash
git clone https://github.com/jonwadsworth/codex-bridge.git /opt/codex-bridge
cd /opt/codex-bridge
sudo ./setup/install.sh
```
The installer is idempotent — checks for/installs `uv`, Node, and Codex CLI;
runs `codex login` (you complete the ChatGPT sign-in yourself, in your own
browser — the installer never touches your credentials); prompts for your
domain, a working-directory allowlist, and a login credential; generates
`/etc/codex-bridge/env`; installs and starts the systemd service on
`127.0.0.1:8427`.
**You still need to do two things it can't do for you:**
1. Point a reverse proxy or tunnel (Cloudflare Tunnel, nginx+certbot, etc.) at
`http://localhost:8427` for your domain — Codex Bridge only binds loopback.
2. Add `https://<your-domain>/mcp` as a custom connector in claude.ai
(Settings → Connectors → Add custom connector), and sign in with the
credential the installer printed.
### Push-to-deploy (optional)
`.github/workflows/deploy.yml` SSHes into your server and runs
`deploy/deploy.sh` (`git pull && uv sync && systemctl restart`) on every push
to `main`. To enable it:
1. Generate a dedicated SSH keypair — don't reuse an existing admin key.
2. Add the public half to `/root/.ssh/authorized_keys` on your server, prefixed
with a forced command so this key can only ever run that one script:
```
command="/opt/codex-bridge/deploy/deploy.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA...
```
3. Add the private half as the `ATLAS_CODEXBRIDGE_DEPLOY_KEY` secret on the
GitHub repo, and the server's hostname/IP as `ATLAS_HOST`.
## Security model
- Every request to the MCP server is OAuth-authenticated — no anonymous access.
- `codex_delegate`'s `working_dir` is checked against a fixed allowlist
(`CODEXBRIDGE_WORKDIR_ALLOWLIST`) set at install time — never an arbitrary path.
- Job submission is rate-limited (`CODEXBRIDGE_MAX_CONCURRENT_JOBS`,
`CODEXBRIDGE_RATE_LIMIT_PER_HOUR`) to bound both cost and blast radius.
- Every job also runs under Codex CLI's own command-execution sandbox
(`--sandbox`, `CODEXBRIDGE_SANDBOX_MODE`, default `workspace-write`) — Codex
Bridge never passes `--dangerously-bypass-approvals-and-sandbox`.
- `~/.codex/auth.json` and the server's own OAuth credential are secrets —
never logged, never committed. `.gitignore` excludes `.env`/`*.db` by default.
- The optional CI deploy key is scoped with a forced SSH command, so even a
leaked key can only re-run the fixed deploy script, not arbitrary commands.
- This is a **personal-use bridge**, not a resale or multi-tenant proxy —
OpenAI's ToS govern what you do with a ChatGPT-authenticated Codex session
accessed this way. Don't share your instance's credential with anyone whose
usage you wouldn't want counted against your own plan.
## Known limitations (v1)
- Single operator, single Codex identity, one server — no multi-tenancy.
- No `codex_review` tool yet (mirroring `/review` from the official plugin) —
tracked as a stretch goal.
- No session resume support (`codex exec resume`) yet — `codex_status`/
`codex_result` report the `session_id` Codex CLI assigns each run (parsed
from its exec banner), but nothing in Codex Bridge uses it to resume.
- Headless login (`codex login`) needs either `--device-auth` (requires Device
Code Authorization enabled in your ChatGPT account's Security Settings) or
an SSH-tunneled browser flow — the installer won't attempt it for you, see
its printed instructions.
- If the service restarts while a job is running, the job survives
(`KillMode=process` in the systemd unit), but the exit code is unavailable —
it's reported as `done` with a note, since the reparented process can no
longer be waited on for a real exit status.
- Job history/log storage on disk has no retention/cleanup policy yet — will
grow unbounded under heavy use.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues