A thin MCP server bridging coding-agent CLIs to Claude Code (Opus). It exposes two
tools: run_cursor_agent, which offloads implementation/review tasks to Cursor's
cursor-agent CLI (Composer 2.5 Fast by default), and generate_image, which
generates/edits images with gpt-image-2 by driving the Codex CLI. The final review
gate stays on Opus.
Published as @jonaspauleta/cursor-bridge. No clone or build needed:
# one-off
CURSOR_BRIDGE_ALLOWED_ROOT="$(pwd)" npx -y @jonaspauleta/cursor-bridge
Register with Claude Code:
claude mcp add --transport stdio --scope project \
--env CURSOR_BRIDGE_ALLOWED_ROOT="$(pwd)" \
cursor-bridge -- npx -y @jonaspauleta/cursor-bridge
Or as project-scoped .mcp.json:
{
"mcpServers": {
"cursor-bridge": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@jonaspauleta/cursor-bridge"],
"env": {
"CURSOR_API_KEY": "${CURSOR_API_KEY:-}",
"CURSOR_BRIDGE_ALLOWED_ROOT": "${CLAUDE_PROJECT_DIR}"
}
}
}
}You can also run straight from GitHub without npm: npx github:jonaspauleta/cursor-bridge
(npx clones and builds via the prepare hook).
Register once at user scope to make the tools (including generate_image) available in
every project, with the workspace following whichever project you're in:
claude mcp add --transport stdio --scope user \
--env CURSOR_BRIDGE_ALLOWED_ROOT=cwd \
cursor-bridge -- npx -y @jonaspauleta/cursor-bridge
CURSOR_BRIDGE_ALLOWED_ROOT accepts a sentinel — cwd or ${CLAUDE_PROJECT_DIR} — instead of
a fixed path. The server resolves it to CLAUDE_PROJECT_DIR (which Claude Code sets to the
active project), else its working directory — so one global registration follows you across
projects. Prefer cwd: Claude Code does not expand ${CLAUDE_PROJECT_DIR} for user-scope
servers, so that form still works (the server resolves it internally) but Claude Code logs a
harmless "Missing environment variables" warning for it. An unset or relative value still fails
loudly; it never implicitly widens to $HOME.
Prerequisites (not bundled): the cursor-agent CLI (for run_cursor_agent) and/or
the codex CLI v0.134.0+ (for generate_image) must be installed on PATH and logged in
(cursor-agent login / codex login). CURSOR_BRIDGE_ALLOWED_ROOT is required: either an
absolute path, or the cwd / ${CLAUDE_PROJECT_DIR} sentinel for global use.
npm install
npm run build # -> dist/index.js
node dist/index.js # stdio MCP server (Claude Code launches this for you)
To develop against your local build instead of the published package, wire the built
dist/ at project scope:
# Export CURSOR_API_KEY in your shell profile — do NOT put it on argv (ps/history leak).
claude mcp add --transport stdio --scope project \
--env CURSOR_BRIDGE_ALLOWED_ROOT="$(pwd)" \
cursor-bridge -- node dist/index.js
CURSOR_API_KEY is read from the ambient environment and never hardcoded or passed on a
command line. If unset, cursor-agent falls back to your interactive cursor-agent login
session. CURSOR_BRIDGE_ALLOWED_ROOT is required: an absolute path, or the cwd /
${CLAUDE_PROJECT_DIR} sentinel.
| input | type | default | notes |
|---|---|---|---|
prompt |
string | — | task/prompt sent to the worker |
cwd |
string | — | working dir; validated against CURSOR_BRIDGE_ALLOWED_ROOT |
mode |
default/plan/ask |
default |
default = read-write (--force); plan/ask = read-only |
model |
string | composer-2.5-fast |
allowlisted (composer-2.5-fast, composer-2.5, claude-4.6-sonnet-medium) |
timeoutMs |
number | 270000 |
wall-clock; wrapper SIGKILLs cursor-agent past this |
Returns the worker's final text on SUCCESS (plus structuredContent with usage).
Every other outcome returns isError: true with a taxonomy-tagged message.
Generates or edits images with gpt-image-2 by driving codex exec with the
$imagegen skill. Subscription-only: it uses your codex login (ChatGPT/Codex
plan) auth and counts toward your Codex usage limits — no API key. OPENAI_API_KEY is
scrubbed from the child env so an ambient key can't silently switch you to API billing.
| input | type | default | notes |
|---|---|---|---|
prompt |
string | — | image task; free-form, may include edit/iterate instructions |
outputDir |
string | <allowedRoot>/generated-images |
absolute, validated inside the allowed root |
referenceImages |
string[] | — | paths attached via Codex -i (edit/iterate); png/jpg/jpeg/webp, inside root, max 8 |
count |
number | 1 |
1–10 (gpt-image-2 ceiling) |
size |
enum | auto |
auto/1024x1024/1536x1024/1024x1536 (best-effort directive) |
quality |
enum | auto |
auto/low/medium/high (best-effort directive) |
inlineImages |
boolean | false |
also return generated images as base64 blocks (first 4) |
timeoutMs |
number | 300000 |
wall-clock; wrapper SIGKILLs codex past this |
Each call writes into a fresh <outputDir>/<runId>/ subdir. On SUCCESS it returns the
generated file paths (and Codex's summary); every other outcome returns isError: true.
size/quality/count are natural-language directives templated into the prompt (the
built-in image_gen tool is invoked by the model, not via CLI flags), so they are
best-effort rather than hard API parameters.
Status taxonomy: SUCCESS | NO_IMAGE | AUTH_REQUIRED | TIMEOUT | TOOL_ERROR. Success
means ≥1 image file actually landed on disk — the exit code is not trusted.
Requirements: the codex CLI (v0.134.0+) must be installed and on the server's PATH,
and you must be logged in (codex login; check with codex login status). Override the
binary with CODEX_AGENT_BIN. Unlike cursor-agent, Codex's -s workspace-write
sandbox is genuinely enforced, so the image worker can only write within the workspace
root and cannot run destructive shell outside it.
Smoke test: npm run test:smoke:codex (live gpt-image-2 call; requires codex login).
SUCCESS | AUTH_REQUIRED | TIMEOUT | TOOL_ERROR | MALFORMED. Success requires a
parsed {"type":"result"} envelope with is_error:false; the exit code is NOT
trusted (the auth-failure path is exit-code-unstable).
cursor-agent status --format json # {"isAuthenticated":true,...}
The server exposes preflightAuth() for a fail-fast check before a real call.
- argv is always an array;
spawnruns with no shell — no command injection via the wrapper. - All MCP-tool inputs pass
src/validate.ts(mode/model allowlist, cwd allowlist + symlink-safe) before reaching argv. The git-diff SHAs used by the final review gate are NOT validated here — they live in the orchestrator's shell (Part B) and are resolved mechanically withgit rev-parse, never taken from task text. - Secrets come from env only; the logger redacts secret-looking values and never logs prompt bodies or raw output (stderr only). Never pass
CURSOR_API_KEYon a command line (ps/history leak) — export it in your shell profile so.mcp.json's${CURSOR_API_KEY}picks it up. CURSOR_BRIDGE_ALLOWED_ROOTis REQUIRED — an absolute path, or thecwd/${CLAUDE_PROJECT_DIR}sentinel (follows the active project, for global/user-scope use). It never implicitly defaults to$HOME; the server refuses to start on an unset/relative value and logs the resolved root. Set it as narrowly as possible.- Blast radius — VERIFIED on
cursor-agentv2026.05.28:--sandbox enableddoes NOT contain a--forceworker on this build. A probe worker wrote a file outside--workspace(into$HOME) despite--sandbox enabled.--mode plan/askare genuinely read-only and safe. Butmode:defaultpasses--force(auto-approves arbitrary shell + writes) and--workspaceis only a cwd, not a jail — so a default-mode worker is effectively full user-level shell: it can read~/.ssh, exfiltrate over the network, orgit push. The wrapper still passes--sandbox enabled(harmless future-proofing should a later build enforce it — re-run the Task A20 Step 5 probe to recheck), but today you MUST treatmode:defaultas unsandboxed and run workers ONLY in disposable/throwaway git worktrees with NO access to real secrets. TheassertWorkspacePathallow-root and the Opus security gate are the real controls; cwd validation alone does not contain--force.
npm test # unit + in-memory round-trip (no network)
npm run test:smoke # RUN_CURSOR_SMOKE=1; live call to Composer 2.5 Fast