A local proxy that lets the OpenAI Codex CLI
run on your Cursor subscription's models instead of OpenAI's. It implements the
OpenAI Responses API (POST /v1/responses) that Codex speaks, and backs it
with @cursor/sdk.
No Codex source changes are needed: Codex already supports pointing at any
Responses-API-compatible endpoint through ~/.codex/config.toml. This bridge is
the translation layer in between.
Codex CLI ──POST /v1/responses (SSE)──▶ bridge ──@cursor/sdk──▶ Cursor model
▲ │
└────────── function_call / result ────────┘ (Codex keeps its own sandbox)
Cursor's SDK is an agent SDK, not a raw model endpoint — Cursor states this explicitly on cursor.com/docs/api. A Cursor agent runs its own tool loop (read, edit, shell) against a working directory. Left alone it would do the file edits itself and reduce Codex to a dumb terminal, bypassing Codex's sandbox and approvals.
The bridge inverts that so Codex keeps ownership of the tool loop:
- It creates a Cursor local agent with
tools: ["mcp"], which drops every built-in tool (shell/read/edit/...) while keeping the MCP capability group that carries custom tools. - It registers Codex's own tools as Cursor
customTools. When the Cursor model calls one, the tool'sexecute()blocks on a promise. - The bridge emits that call to Codex as a Responses
function_callitem and ends the HTTP turn — while the Cursor run stays alive. - Codex runs the tool in its own sandbox and sends the result on its next request. The bridge resolves the parked promise, and the same Cursor run continues.
This means Codex's sandbox, approval policy, and TUI all keep working normally.
- Node.js ≥ 22.13 (required by
@cursor/sdk; CI tests Node 22 and 24) - Codex CLI (developed against
codex-cli 0.140.0) - A Cursor API key: cursor.com/dashboard/integrations
No clone needed. Requires Node.js ≥ 22.13.
export CURSOR_API_KEY="cursor_..." # your Cursor key, passed to the SDK
export CURSOR_BRIDGE_KEY="some-secret" # shared secret Codex presents
npx @tarektweeti/codex-cursor-bridge catalog # once: writes ~/.codex/cursor-bridge-models.json
npx @tarektweeti/codex-cursor-bridge # starts the bridge on http://127.0.0.1:4712/v1Prefer a global install? npm i -g @tarektweeti/codex-cursor-bridge gives you a
codex-cursor-bridge command. Run codex-cursor-bridge --help for all options.
A .env file in the current directory is loaded automatically; variables
already set in your shell take precedence.
catalog queries your Cursor account and writes an account-specific model
catalog to $CODEX_HOME (default ~/.codex). Use --out <path> to write
it somewhere else.
| Command | What it does |
|---|---|
npx @tarektweeti/codex-cursor-bridge |
Start the bridge (same as serve) |
npx @tarektweeti/codex-cursor-bridge serve |
Start the bridge on http://127.0.0.1:4712/v1 |
npx @tarektweeti/codex-cursor-bridge catalog |
Write the Codex model catalog to $CODEX_HOME/cursor-bridge-models.json |
npx @tarektweeti/codex-cursor-bridge catalog --out <path> |
Write the catalog to a custom path |
npx @tarektweeti/codex-cursor-bridge --version (-v) |
Print the installed version |
npx @tarektweeti/codex-cursor-bridge --help (-h) |
Show commands, options, and environment variables |
Examples:
# Start the server
npx @tarektweeti/codex-cursor-bridge
# Start on a different port with debug logs
BRIDGE_PORT=5000 BRIDGE_LOG_LEVEL=debug npx @tarektweeti/codex-cursor-bridge serve
# Generate (or refresh) the model catalog
npx @tarektweeti/codex-cursor-bridge catalog
npx @tarektweeti/codex-cursor-bridge catalog --out ./models.json
# Check which version you're running
npx @tarektweeti/codex-cursor-bridge --version
# Always run the newest release (bypasses npx's cached copy)
npx @tarektweeti/codex-cursor-bridge@latestWith a global install (npm i -g @tarektweeti/codex-cursor-bridge), drop the
npx @tarektweeti/ prefix: codex-cursor-bridge serve, codex-cursor-bridge catalog,
codex-cursor-bridge --version.
Stop the server with Ctrl+C. It shuts down cleanly on SIGINT or SIGTERM.
Save this as ~/.codex/cursor-bridge.config.toml:
model = "composer-2.5"
model_provider = "cursor-bridge"
model_catalog_json = "/Users/you/.codex/cursor-bridge-models.json" # path printed by `catalog`
[model_providers.cursor-bridge]
name = "Cursor Bridge"
base_url = "http://127.0.0.1:4712/v1"
env_key = "CURSOR_BRIDGE_KEY"
wire_api = "responses"
[features]
multi_agent = false-p NAME layers $CODEX_HOME/NAME.config.toml over your base config, so your
main ~/.codex/config.toml is left untouched:
export CURSOR_BRIDGE_KEY="some-secret" # must match the bridge's value
codex exec -p cursor-bridge "Reply with exactly CURSOR_BACKEND_OK and say which model you are."
codex -p cursor-bridge # interactive TUIThe model_catalog_json entry silences Codex's "model metadata not found"
warning and gives Codex's picker the list of models to show. It replaces
Codex's built-in catalog, so only models listed there appear in Codex — though
the bridge itself never restricts models and will run any id you set in model.
The catalog is account-specific and changes as models ship, so re-run
npx @tarektweeti/codex-cursor-bridge catalog instead of editing it by hand. It
writes every concrete model from Cursor.models.list(), with composer-2.5
first as the default.
All configuration is via environment variables:
| Variable | Default | Purpose |
|---|---|---|
CURSOR_API_KEY |
(required) | Cursor credential, passed to @cursor/sdk. |
CURSOR_BRIDGE_KEY |
(unset) | Shared secret; when set, Codex must present it as Authorization: Bearer. Required to bind off-loopback. |
BRIDGE_HOST |
127.0.0.1 |
Listen host. Non-loopback requires CURSOR_BRIDGE_KEY. |
BRIDGE_PORT |
4712 |
Listen port. |
BRIDGE_MODEL |
composer-2.5 |
Model used when a request omits one. |
BRIDGE_CWD |
process.cwd() |
Fallback working directory for the Cursor agent (Codex's advertised cwd overrides it). |
BRIDGE_STREAM_THINKING |
true |
Forward Cursor thinking deltas as Responses reasoning summaries. |
BRIDGE_TOOL_BATCH_MS |
50 |
Window for collecting parallel tool calls into one batch. |
BRIDGE_SESSION_IDLE_MS |
1800000 |
Idle timeout before a session's agent is disposed. |
BRIDGE_SYSTEM_PROMPT |
0 |
Set 1 to try the account-gated systemPrompt option (see below). |
BRIDGE_LOG_LEVEL |
info |
debug / info / warn / error. |
BRIDGE_DUMP_DIR |
(unset) | If set, raw Responses requests are written here as JSON (debugging). |
Running from a clone:
git clone https://github.com/Tarektouati/codex-cursor-bridge.git
cd codex-cursor-bridge
npm install
npm start # runs src/ directly through tsxnpm run build # compile to dist/ (what gets published)
npm run typecheck # tsc --noEmit
npm test # offline unit + HTTP contract tests (no network)
npm run smoke # live SDK probe (needs CURSOR_API_KEY)
npm run gen:catalog # write codex/model-catalog.json (gitignored) from your accounttest/unit/covers request translation, tool mapping, and the full SSE frame contract against a stubbed Cursor agent — including the tool round trip that resumes a parked run. No network.scripts/smoke-sdk.tsprobes the real SDK: thattools: ["mcp"]keeps custom tools, that text streams incrementally, and that a deferred tool result resumes the same run.scripts/probe-roundtrip.tsdrives a full two-request tool round trip against a running bridge.scripts/bridge.shstarts/stops the bridge detached.
These are what the bridge is built against; they are the traps that break a hand-written Responses proxy silently:
- Codex is stateless. It sets
store: false, never sendsprevious_response_id, and resends the full conversation every request. The bridge holds all session state, keyed onprompt_cache_key(Codex's thread id), falling back to thethread-idheader. - Only
response.completedis mandatory, and itsresponse.idis required. Ifusageis included,input_tokens/output_tokens/total_tokensare all required — a partialusageobject fails the whole frame. The bridge omitsusage. - Never emit
response.function_call_arguments.*— Codex 0.140 ignores them. Each tool call is oneresponse.output_item.done. - Never return HTTP 429 — Codex does not retry it. Backpressure is a
response.failedwithcode: "rate_limit_exceeded"and atry again in Nsmessage, which Codex does honour. - Reasoning items must be opened with
response.output_item.addedbefore any summary part/delta, or Codex logs "without active item" and drops them.
systemPromptis account-gated. Cursor's own harness prompt can only be replaced with thesystemPromptoption if your account has access; without it the firstsend()fails withunknown option '--system-prompt'. The bridge defaults to not using it and instead delivers Codex'sinstructionsas the opening user message with a preamble that tells the model to ignore its built-in tool vocabulary. SetBRIDGE_SYSTEM_PROMPT=1to try the option; the bridge falls back automatically if it's rejected.- First-call argument mismatches. A Cursor model driving Codex's tool
schemas may produce slightly wrong arguments on its first tool call (e.g. a
missing field cmdrouter error), then self-correct on retry. This is model-side; the bridge relays the error back faithfully and the model recovers. - One run per session, serialized. Concurrent
send()on a single local agent is undocumented; turns within a conversation are queued. - Local runtime only. Cloud Cursor agents, image input, and MCP bridging beyond Codex's own tool calls are out of scope.
- Transitive advisories.
npm auditreports advisories via@cursor/sdk → @connectrpc/connect-node → undici. No compatible fix is published upstream at the pinned SDK version.
Cursor's Acceptable Use Policy prohibits "accessing the Service through automated or non-human means, whether through a bot, script, or otherwise," which — read literally — also describes the SDK's own documented purpose. Cursor staff have stated publicly that scripts, CI, and automation are what the SDK is built for, and the resale clause (ToS §1.5) is aimed at repackaging Cursor access for third parties. A single-user, local, personal bridge sits well inside that line.
However, the AUP text and the SDK docs genuinely contradict each other, and the SDK is in public beta — ToS §1.6 states beta services are "not for production use." Use this at your own discretion; if your use approaches reselling or multi-user access, get written confirmation from Cursor.
This project is licensed under the MIT License.