claude-codex-bridge
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@claude-codex-bridgetell Claude Code to run the test suite on the active thread"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
claude-codex-bridge
Let Claude Code talk to the Codex TUI right in front of you — and you see it all. Conversely, Codex can also send messages into the Claude Code session that's currently running.
No screen scraping, no file polling, no headless subagent. Both sides are attached to the same thread of the same codex app-server: messages sent from Claude Code appear instantly in the TUI you're looking at.
English: README.en.md
To install, see: SETUP.md (Chinese) · SETUP.en.md (English)
This README covers design rationale and verification records — why this approach, which facts have been measured, and which haven't. If you want to get running quickly, SETUP is faster.
Verification environment: codex-cli 0.147.0, tested on both Windows 11 and macOS 26, both running on Node 24 LTS (Krypton). The code is platform-agnostic (paths always use node:path, resolveCodex() has a Windows branch as the only exception). Differences between the two and their respective results are shown below in "Verified / Unverified".
Node requirement is >=22 (package.json engines), recommended to use v24 LTS directly. Every push runs the full matrix of ubuntu / macOS / Windows × Node 22, 24 on CI.
codex is often a global package under some nvm version, and that version might be below 22 — so the default node becomes outdated. The cleanest solution is to keep node and codex on the same LTS:
nvm install 24 && nvm alias default 24
nvm reinstall-packages 20 # 把 codex 等全域套件搬過去(20 換成你原本的版本)(codex's bin is a shim with #!/usr/bin/env node, it runs with the node on PATH, not bound to the version at installation time.)
Architecture
┌──────────────────────────────┐
│ codex app-server │ ← 真正持有 thread 的地方
│ --listen ws://127.0.0.1:8787│
└───────┬──────────────┬───────┘
│ │
codex --remote ws://… │ JSON-RPC over ws
│ │
┌───────┴──────┐ ┌────┴─────────────┐
│ Codex TUI │ │ Claude Code │
│ (你在看) │ │ (scripts/talk) │
└──────┬───────┘ └────┬─────────────┘
│ ▲
└───────────────┘
.bridge-inbox/<name>.jsonl → Stop hook
(反方向:Codex → Claude Code)The key to the forward direction is the semantics of thread/resume:
If thread_id identifies a running thread, app-server rejoins that thread.
So the second client doesn't start a new conversation or replay a saved one — it joins the same running thread. Only after joining does it receive the notification stream for that thread; just connecting to the endpoint is not enough.
Related MCP server: Claude-Gemini MCP Integration Server
Usage
Three windows.
1. Shared server (keep it open)
node scripts/serve.mjs --cwd C:\path\to\你的專案--cwd is the actual working directory of Codex. When a thread doesn't specify its own cwd, it inherits the app-server's, so omitting this parameter leaves it at the directory where you started the script (i.e., the bridge's own folder). You can also use the environment variable CODEX_BRIDGE_CWD. Port uses --port or CODEX_BRIDGE_PORT (default 8787). The endpoint and workspace are written into .bridge.json, which talk.mjs reads automatically.
2. The Codex TUI you want to watch
codex --remote ws://127.0.0.1:8787 -C C:\path\to\你的專案-C pins the workspace for that window; if omitted, it inherits the cwd set by the --cwd above.
First, say something in the TUI and wait for it to reply. A thread must have its first turn "completed" before it becomes resumable; before that, thread/resume returns no rollout found for thread id.
Note this trap: talk.mjs list already sees the thread before that — as soon as the TUI connects, it creates the thread and it appears in thread/loaded/list. So "list sees it" does not mean "can send messages". If you skip this step, say sends the message, Codex replies in the TUI, but the bridge doesn't receive the reply stream and just times out (tested on macOS).
3. The Claude Code side
node scripts/talk.mjs list # 列出活著的 thread(含各自的 cwd)
node scripts/talk.mjs say "跑一下測試" # 送話進去,你會在 TUI 看到
node scripts/talk.mjs read # 讀完整 thread(結構化 JSON)When there's only one thread, say / read automatically selects it; when there are multiple, use --thread <id> to specify — it doesn't guess which session you're talking to. list also prints the cwd of each thread, which helps distinguish them when multiple are open.
say additionally accepts --cwd <dir> (changes the working directory for this turn only) and --approvals (see below).
MCP Interface
CLI is still usable directly; the MCP interface provides structured tools with the same core capabilities. Both directions share the same codebase, but each active talker starts its own STDIO process:
--role claude: Claude Code actively speaks to a Codex thread.--role codex: Codex actively queues messages into a Claude mailbox.
Installation Steps
0. Check prerequisites first
Node
>=22(see the version notes at the top).This repo has already been
npm installed.MCP is just an interface, not a transport layer. It still needs a running
serve.mjsand a connected TUI to have something to talk to — see "Usage" above.
cd <這個 repo>
npm install1. Decide which side to install
What you want | What to install |
Only Claude Code can speak to Codex | Only install |
Only Codex can leave messages for Claude Code | Only install |
Bidirectional | Install both |
When you only need one direction, do not install both. The passive receiving half does not rely on MCP — it goes through the app-server/TUI and Claude's Stop hook respectively.
2. Install
Claude Code side (--role claude):
# macOS / Linux
claude mcp add --scope project claude-codex-bridge -- \
node /path/to/claude-codex-bridge/scripts/mcp.mjs --role claude# Windows
claude mcp add --scope project claude-codex-bridge -- `
node "C:/path/to/claude-codex-bridge/scripts/mcp.mjs" --role claudeCodex side (--role codex):
# macOS / Linux
codex mcp add claude-codex-bridge -- \
node /path/to/claude-codex-bridge/scripts/mcp.mjs --role codex# Windows
codex mcp add claude-codex-bridge -- `
node "C:/path/to/claude-codex-bridge/scripts/mcp.mjs" --role codex--scope project writes to the project's .mcp.json; use --scope user for cross-project sharing.
Use absolute paths, but "which directory you start from" doesn't affect the result — all state files (.bridge.json, .bridge-inbox/, .bridge-output/) are resolved from the module's location, not the cwd. So one installation is enough; you don't need to install separately for each project.
3. Restart
claude mcp add / codex mcp add only modify configuration files; a running session does not load the new MCP server. After installation, close and restart that session for the tools to become available.
4. Verify installation
In the restarted session, call bridge_status. If ok: true and role is correct, it's successful. Then codex_threads_list should show your TUI thread (including its cwd).
If you don't want to open a session, you can also verify the same path from the command line:
npm run test:e2e:mcp-send # 需要 serve + 已跑完第一輪的 TUITools
Role | Tools |
Shared |
|
Claude |
|
Codex |
|
codex_message_send waits for the entire turn, so for Codex's MCP configuration it's recommended to increase the timeout and require approval for write tools:
[mcp_servers.claude-codex-bridge]
tool_timeout_sec = 360
default_tools_approval_mode = "writes"MCP mode only allows CODEX_BRIDGE_APPROVALS=tui (default) or decline; automatic accept is not accepted. Short replies are inlined; if they exceed 64 KiB, they are written to .bridge-output/, returning an opaque artifact ID with TTL, which is then read in pages via bridge_output_read. A single capture defaults to a maximum of 10 MiB, preventing unbounded replies from being stuffed into a single tool result or Node heap.
Local verification:
npm test # 語法、unit、in-memory MCP、真實 STDIO smoke;不呼叫模型
npm run test:integration:mcp-app-server # 真實 app-server 連線,不建立模型 turn
npm run test:spikes # 真實 app-server regression,可能使用模型
npm run test:e2e:mcp-send # 完整 MCP → 真實 TUI;需要 serve + 已跑完第一輪的 TUItest:e2e:mcp-send is different from other spikes: it does not start a separate app-server, but connects to the TUI you're currently looking at via .bridge.json, creating a real turn in the thread you're watching. So it's not in test:spikes; you have to run it manually.
Reverse direction: Codex → Claude Code
Claude Code doesn't have an equivalent app-server, no socket to push things into. What it does have is a Stop hook: before Claude finishes, the hook runs, and if it returns {"decision":"block","reason":...}, it tells it not to stop and uses reason as new input to continue.
So we put a mailbox in between. The mailbox is named — because there may be several Claude Code sessions listening at the same time; if they share a single file, whoever finishes first will swallow the others' messages:
# Codex 那側(或任何地方)留話
node scripts/inbox.mjs push --to bridge "順便幫我看一下 auth 那段"
# 現在有誰在聽(含各自的工作目錄)
node scripts/inbox.mjs list
# 看某個信箱(不消耗)
node scripts/inbox.mjs peek --as bridge--to is "who this message is for", --as is "who I am reading as", both default to $CODEX_BRIDGE_MAILBOX then fall back to default.
This repo's .claude/settings.json already has a Stop hook set up (mailbox name bridge), so when Claude Code finishes working in this project, it automatically drains the mailbox and continues. That file is version-controlled, so when you clone this repo and open it with Claude Code, the hook is active for you — when the mailbox is empty, it's completely silent; if you don't need it, delete .claude/settings.json. Messages are delivered only once: drain() first renames then reads, so someone writing at the same time won't be read halfway.
Details and trade-offs are in docs/reverse-channel.md.
Let other Claude Code sessions also use this bridge
The server only needs to run once; other sessions share it. The script's state (.bridge.json, mailbox) is resolved from the module's own location, not the cwd, so calling with absolute paths from any directory works.
Forward direction (that session → Codex): No setup needed, just call.
$bridge = "C:\path\to\claude-codex-bridge"
node "$bridge\scripts\talk.mjs" list
node "$bridge\scripts\talk.mjs" say --thread <threadId> "..."When running multiple TUI instances, be sure to use --thread — list prints the cwd of each thread for identification. (Or set CODEX_BRIDGE_URL to avoid relying on .bridge.json.)
Reverse direction (Codex → that session): Install a Stop hook in that project's .claude/settings.json and give it its own mailbox name:
{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [
{ "type": "command",
"command": "node \"C:/path/to/claude-codex-bridge/scripts/inbox.mjs\" hook --as web" }
] } ] } }Note that $CLAUDE_PROJECT_DIR cannot be used here — it would point to that project itself, not the bridge. The path must be hardcoded to the bridge. The mailbox name (web in the example) is up to you, one per session.
Then the Codex side can send messages by name:
node scripts/inbox.mjs push --to web "先把 CORS 那條修掉"
node scripts/inbox.mjs list # 確認名字沒打錯、對方還活著list data comes from each session's Stop hook self-registering every time it runs, so that session must have finished at least once before it appears in the list.
Approvals (when Codex needs to modify something)
If a turn sent from Claude Code needs to run commands or modify files, Codex issues approval requests. The app-server broadcasts these requests to all connected clients; whoever responds first counts (the others receive serverRequest/resolved). So the default strategy is tui: the bridge stays silent, letting the prompt in your TUI decide.
If no one responds, the entire turn gets stuck, so there's a safeguard: after CODEX_BRIDGE_APPROVAL_TIMEOUT_MS (default 300 seconds) with no response, the bridge automatically fail-closes with a rejection, allowing the turn to proceed.
node scripts/talk.mjs say --approvals decline "..." # 沒開 TUI 時用
node scripts/talk.mjs say --approvals accept "..." # 只用在你已經信任的環境You can also set a default value with CODEX_BRIDGE_APPROVALS.
Broadcast routing has been confirmed on macOS (spike-approvals.mjs 11/11): when one client approves, the other silent client receives the same request, then receives serverRequest/resolved, and the turn completes normally. So "bridge silent = let the person decide" holds.
Two settings that silently break "let the TUI person decide"
Approval requests pass through the user's own codex settings before reaching any client. When either of the following is active, your TUI won't be asked at all, and bridge silence does not equal human decision:
Setting | Location | Effect |
|
| The hook intercepts approval requests. Tested on macOS: when the hook is active, both clients receive no approval requests at all, and files are still written |
|
| Subagent decides automatically based on risk, doesn't ask the human |
These are both reasonable personal settings; this project won't change them. Just be aware: when they are active, the "person" in --approvals tui is actually them. To check which one your machine has, run spike-approvals.mjs — that spike starts its own app-server with --disable hooks -c approvals_reviewer=user to disable both, measuring the protocol itself.
The response formats for various approval requests are inconsistent — only two item/*/requestApproval accept {decision:"decline"}; item/permissions/requestApproval expects an (empty) permission profile, and the older execCommandApproval / applyPatchApproval expect {decision:{denied:{rejection}}}. Returning the wrong shape is a schema error, not a polite rejection. The mapping is in DEFAULT_SERVER_REQUEST_RESPONSES in src/appServerWsClient.mjs.
Why not other approaches
Approach | Problem |
| Captures the TUI's rendered screen: box-drawing characters, spinner, line breaks truncated; judging "reply finished" only by polling for visual changes |
Shared file mailbox (forward direction) | Works but can't see real-time status, and requires manual intervention to trigger |
| Cold start every time, independent session, cannot connect to the TUI you're looking at |
This approach | Structured events; |
The reverse direction still uses a file mailbox — but that's because Claude Code has no socket to attach to, and the Stop hook makes "triggering" automatic.
Security
Listener binds to loopback.
--ws-authonly affects non-loopback, so localhost doesn't need a token.Approvals default to human (
tui), fail-closed on timeout. Non-approval server→client requests (tool calls, MCP elicitation) are always fail-closed — the bridge has no UI to ask the human.
Verified / Unverified
Three spikes, each starting its own app-server (ephemeral port), won't interfere with the thread you're watching:
node scripts/spike-multiclient.mjs # 9/9 兩個 client 共用一條 thread
node scripts/spike-multithread.mjs # 7/7 兩條 thread 同時跑,回覆不串味
node scripts/spike-approvals.mjs # 11/11 於 macOS;Windows 上 3 項 SKIP,見下Actual results on macOS (26.5.1, Node v24.19.0 LTS, codex-cli 0.147.0): all three spikes pass, npm test 21/21, npm run test:integration:mcp-app-server PASS. scripts/serve.mjs --cwd, scripts/talk.mjs list, scripts/inbox.mjs (push / list / peek / hook, including Chinese) were also manually tested on macOS; the inbox hook even runs on Node 20, so the Stop hook doesn't need to pick a Node version.
Additionally, testing with a real TUI (codex --remote) confirmed: messages from Claude Code render as user messages in the TUI, Codex replies normally, and the reply stream flows back to Claude Code.
Clarified (codex-cli 0.147.0):
Notifications always include
threadId, stream types (item/agentMessage/delta) also includeturnId, and theturn.idreturned byturn/startexactly matches the one in the stream. Earlier we thought "notifications don't include threadId", but it was actually a symptom of the client not joining the thread.A thread must be joined via
thread/resumebefore it receives notifications; and a thread is only resumable after the first turn completes.historyMode: "paginated"(thread created by TUI) →thread/readwithincludeTurnsfails (list_turns is not supported yet). The path to fetch history afterward is currently broken; replies rely on real-time streaming.Approvals are broadcast to all clients, first to answer counts (confirmed on macOS). The silent client also receives the request and later receives
serverRequest/resolved, the turn doesn't get stuck. On machines where the OS sandbox helper can't start (some managed enterprise Windows giveShellExecuteExW failed to launch setup helper: 1223), file writing fails before "asking the human", so approval requests are never issued, and those three items are marked SKIP — an environment limitation, not a protocol issue.The user-level
PermissionRequesthook intercepts all approval requests, clients receive none (tested on macOS). Details in the "Approvals" section above.
Not yet verified:
turn/steer(interjecting into an ongoing turn) only read the schema, not tested.-Cnot tested→ Tested: When TUI connects without-C, the thread's cwd is the app-server's cwd, regardless of the directory where you runcodex --remote; with-C <dir>it changes to that directory. (macOS, started TUI with pty, another client readthread/read.)The entire protocol is marked
[experimental], may shift with codex upgrades.
References
Why the reverse direction uses Stop hook instead of other mechanisms: docs/reverse-channel.md
Protocol schema: codex app-server generate-json-schema --out <dir>
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceThe self-hosted MCP bridge between Claude Chat and Claude Code.46AGPL 3.0
- AlicenseNot gradedqualityDmaintenanceBridges Claude Code and Google's Gemini AI models to enable AI-to-AI collaboration for code reviews, brainstorming, and direct questions.5MIT
- AlicenseNot gradedqualityDmaintenanceConnects Claude Desktop and Claude Code, enabling autonomous exchange of messages, files, and code while keeping their context windows separate.4MIT
- AlicenseAqualityAmaintenanceBridges Claude Code and OpenAI Codex CLI for an interactive plan-execute-review workflow, enabling Claude to interview, design, and review while Codex implements code changes.74753MIT
Related MCP Connectors
Stop copy-pasting between Claude Chat and Claude Code.
Real-time chat hub for AI agents — Claude Code, Cursor, Cline, Codex over MCP or REST.
Trade Robinhood through natural language in Claude Code.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ar36planet/claude-codex-bridge-public'
If you have feedback or need assistance with the MCP directory API, please join our Discord server