claude-codex-bridge
claude-codex-bridge
Позволяет Claude Code общаться с тем самым Codex TUI, который у вас перед глазами — и вы всё видите. Наоборот, Codex может отправлять сообщения в сессию, в которой работает Claude Code.
Это не захват экрана, не опрос файлов, не headless subagent. Оба подключены к одному и тому же потоку codex app-server: сообщение, отправленное Claude Code, мгновенно появляется в TUI, который вы видите.
English: README.en.md
Инструкция по установке: SETUP.md (中文) · SETUP.en.md (English)
В этом README описаны обоснование дизайна и записи проверок — почему выбран такой подход, какие факты уже измерены, а какие ещё нет. Если хотите сразу запустить, быстрее будет через SETUP.
Среда проверки: codex-cli 0.147.0, протестировано на Windows 11 и macOS 26, оба работают на Node 24 LTS (Krypton). Код не привязан к платформе (пути везде через node:path, resolveCodex() — единственное исключение для Windows). Различия и результаты для обеих платформ см. в разделе «Проверено / не проверено».
Требования к Node: >=22 (engines в package.json), рекомендуется v24 LTS. При каждом push в CI запускается полная матрица ubuntu / macOS / Windows × Node 22, 24.
codex часто является глобальным пакетом под какой-то версией nvm, и эта версия может быть ниже 22 — тогда node по умолчанию тоже устаревает. Самый чистый способ — держать node и codex в одном LTS:
nvm install 24 && nvm alias default 24
nvm reinstall-packages 20 # 把 codex 等全域套件搬過去(20 換成你原本的版本)(Бин codex — это shim с #!/usr/bin/env node, он запускается с node из PATH, не привязан к версии при установке.)
Архитектура
┌──────────────────────────────┐
│ 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)Ключевой момент в прямом направлении — семантика thread/resume:
If thread_id identifies a running thread, app-server rejoins that thread.
Таким образом, второй клиент не открывает новый диалог и не воспроизводит сохранённое — он присоединяется к тому же самому выполняющемуся потоку. Только после присоединения он сможет получать поток уведомлений этого потока; просто подключиться к endpoint недостаточно.
Related MCP server: Claude-Gemini MCP Integration Server
Использование
Три окна.
1. Общий сервер (держите открытым, не закрывайте)
node scripts/serve.mjs --cwd C:\path\to\你的專案--cwd — это рабочая директория, где фактически работает Codex. Если поток не указывает свою cwd, он использует cwd app-server, поэтому без этого параметра он остановится в директории, откуда вы запустили скрипт (то есть в папке самого bridge). Можно также использовать переменную окружения CODEX_BRIDGE_CWD.
Порт задаётся через --port или CODEX_BRIDGE_PORT (по умолчанию 8787). Endpoint и workspace записываются в .bridge.json, talk.mjs читает их автоматически.
2. Codex TUI, который вы хотите видеть
codex --remote ws://127.0.0.1:8787 -C C:\path\to\你的專案-C фиксирует workspace этого окна; если не указан, используется --cwd из пункта выше.
Сначала скажите что-нибудь в TUI и дождитесь ответа. Поток становится возобновляемым только после завершения первого раунда диалога; до этого thread/resume вернёт no rollout found for thread id.
Обратите внимание на ловушку: talk.mjs list видит этот поток ещё до этого — как только TUI подключается, поток создаётся и появляется в thread/loaded/list. Так что «видно в list» не означает «можно отправлять сообщения». Если пропустить этот шаг, say отправится, Codex ответит в TUI, но bridge не получит поток ответа и просто дождётся таймаута (на macOS этот симптом проверен).
3. Со стороны Claude Code
node scripts/talk.mjs list # 列出活著的 thread(含各自的 cwd)
node scripts/talk.mjs say "跑一下測試" # 送話進去,你會在 TUI 看到
node scripts/talk.mjs read # 讀完整 thread(結構化 JSON)Если поток только один, say / read выберут его автоматически; если несколько, нужно указать --thread <id> — не угадываем, с какой сессией вы говорите. list выводит cwd каждого потока, что помогает различать их при нескольких открытых.
say также принимает --cwd <dir> (меняет рабочую директорию только для этого раунда) и --approvals (см. ниже).
MCP-интерфейс
CLI по-прежнему можно использовать напрямую; MCP-интерфейс предоставляет структурированные инструменты с той же основной функциональностью. Оба направления используют один и тот же код, но каждый инициатор запускает свой STDIO-процесс:
--role claude: Claude Code активно отправляет сообщения в поток Codex.--role codex: Codex активно помещает сообщения в почтовый ящик Claude.
Шаги установки
0. Проверьте предварительные условия
Node
>=22(см. примечание о версиях в начале).В этом репозитории уже выполнен
npm install.MCP — это только интерфейс, не транспортный уровень. Для работы ему всё равно нужен запущенный
serve.mjsи подключённый TUI, чтобы было с чем общаться — см. «Использование» выше.
cd <這個 repo>
npm install1. Решите, какую сторону устанавливать
Что вы хотите | Что устанавливать |
Только Claude Code может отправлять сообщения в Codex | Только |
Только Codex может оставлять сообщения для Claude Code | Только |
Двусторонняя связь | Установите обе |
Если нужна только односторонняя связь, не устанавливайте обе стороны. Пассивная приёмная сторона не использует MCP — она работает через app-server/TUI и Stop hook от Claude соответственно.
2. Установка
Со стороны Claude Code (--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 claudeСо стороны Codex (--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 запишет в .mcp.json этого проекта; для общего использования между проектами замените на --scope user.
Путь должен быть абсолютным, но «в какой директории запускать» не влияет на результат — все файлы состояния (.bridge.json, .bridge-inbox/, .bridge-output/) разрешаются относительно расположения модуля, а не cwd. Так что одной установки достаточно, не нужно устанавливать отдельно для каждого проекта.
3. Перезапустите
claude mcp add / codex mcp add только изменяют файлы конфигурации, уже запущенная сессия не загрузит новый MCP-сервер. После установки закройте и снова откройте эту сессию, чтобы инструменты стали доступны.
4. Проверьте установку
В перезапущенной сессии вызовите bridge_status. Если ok: true и role правильный, всё в порядке.
Затем codex_threads_list должен показать ваш поток TUI (включая его cwd).
Если не хотите открывать сессию, можно проверить тот же путь из командной строки:
npm run test:e2e:mcp-send # 需要 serve + 已跑完第一輪的 TUIИнструменты
Роль | Инструменты |
Общие |
|
Claude |
|
Codex |
|
codex_message_send ожидает завершения всего раунда, поэтому для MCP-конфигурации Codex рекомендуется увеличить таймаут и требовать подтверждения для write-инструментов:
[mcp_servers.claude-codex-bridge]
tool_timeout_sec = 360
default_tools_approval_mode = "writes"В режиме MCP допускается только CODEX_BRIDGE_APPROVALS=tui (по умолчанию) или decline, автоматическое accept не принимается.
Короткие ответы отправляются inline; если превышают 64 KiB, записываются в .bridge-output/, возвращается opaque artifact ID с TTL, который затем читается постранично через bridge_output_read. Один capture по умолчанию не более 10 MiB, чтобы не помещать неограниченный ответ в один tool result или кучу Node.
Локальная проверка:
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 отличается от других spike: он не запускает отдельный app-server, а подключается к вашему текущему TUI через .bridge.json и создаёт реальный раунд в потоке, который вы видите. Поэтому он не входит в test:spikes и запускается отдельно.
Обратное направление: Codex → Claude Code
У Claude Code нет аналога app-server, нет сокета, в который можно что-то отправить. У него есть Stop hook: он запускается перед тем, как Claude завершает работу; если hook возвращает {"decision":"block","reason":...}, он заставляет Claude не останавливаться и продолжает работу с reason в качестве нового ввода.
Поэтому мы используем почтовый ящик. Почтовый ящик именованный — потому что может быть несколько сессий Claude Code, слушающих одновременно; если использовать общий файл, кто первый завершится, тот и проглотит чужие письма:
# Codex 那側(或任何地方)留話
node scripts/inbox.mjs push --to bridge "順便幫我看一下 auth 那段"
# 現在有誰在聽(含各自的工作目錄)
node scripts/inbox.mjs list
# 看某個信箱(不消耗)
node scripts/inbox.mjs peek --as bridge--to — «кому предназначено это письмо», --as — «кто я, читающий», оба по умолчанию берут $CODEX_BRIDGE_MAILBOX, затем default.
В этом репозитории .claude/settings.json уже содержит Stop hook (имя почтового ящика bridge). Когда Claude Code завершает работу в этом проекте, он автоматически очищает почтовый ящик и продолжает работу. Этот файл находится под контролем версий, поэтому если вы клонируете репозиторий и откроете его в Claude Code, этот hook будет активен — когда почтовый ящик пуст, он полностью молчалив; если не нужен, просто удалите .claude/settings.json. Сообщение доставляется только один раз: drain() сначала переименовывает, затем читает, так что одновременная запись не приведёт к чтению наполовину.
Подробности и компромиссы см. в docs/reverse-channel.md.
Как использовать этот мост с другими сессиями Claude Code
Сервер запускается один раз, остальные сессии используют его совместно. Состояние скрипта (.bridge.json, почтовые ящики) разрешается относительно расположения самого модуля, а не cwd, поэтому вызов с абсолютным путём из любой директории корректен.
Прямое направление (эта сессия → Codex): настройка не требуется, вызывайте напрямую.
$bridge = "C:\path\to\claude-codex-bridge"
node "$bridge\scripts\talk.mjs" list
node "$bridge\scripts\talk.mjs" say --thread <threadId> "..."При нескольких открытых TUI обязательно используйте --thread — list покажет cwd каждого потока для идентификации.
(Или установите CODEX_BRIDGE_URL, чтобы не зависеть от .bridge.json.)
Обратное направление (Codex → эта сессия): нужно добавить Stop hook в .claude/settings.json этого проекта и дать ему своё имя почтового ящика:
{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [
{ "type": "command",
"command": "node \"C:/path/to/claude-codex-bridge/scripts/inbox.mjs\" hook --as web" }
] } ] } }Обратите внимание: нельзя использовать $CLAUDE_PROJECT_DIR — он укажет на сам проект, а не на bridge. Путь должен быть жёстко прописан до bridge. Имя почтового ящика (в примере web) выбираете сами, по одному на сессию.
После этого со стороны Codex можно отправлять письма по имени:
node scripts/inbox.mjs push --to web "先把 CORS 那條修掉"
node scripts/inbox.mjs list # 確認名字沒打錯、對方還活著Данные list берутся из саморегистрации Stop hook каждой сессии при каждом выполнении, поэтому сессия должна завершиться хотя бы один раз, чтобы появиться в списке.
Подтверждения (когда Codex хочет что-то изменить)
Если раунд, отправленный Claude Code, требует выполнения команд или изменения файлов, Codex отправляет запрос на подтверждение. app-server рассылает такой запрос всем подключённым клиентам; кто первый ответит, тот и считается (остальные получают serverRequest/resolved). Поэтому стратегия по умолчанию — tui: bridge молчит, решение принимает подсказка в окне, которое вы видите.
Если никто не ответит, весь раунд зависнет, поэтому есть защита: если по истечении CODEX_BRIDGE_APPROVAL_TIMEOUT_MS (по умолчанию 300 секунд) никто не ответил, bridge сам отклоняет (fail-closed), и раунд продолжается.
node scripts/talk.mjs say --approvals decline "..." # 沒開 TUI 時用
node scripts/talk.mjs say --approvals accept "..." # 只用在你已經信任的環境Можно также установить значение по умолчанию через CODEX_BRIDGE_APPROVALS.
Маршрутизация рассылки подтверждена на macOS (spike-approvals.mjs 11/11): один клиент нажимает подтверждение, другой молчаливый клиент получает тот же запрос, затем получает serverRequest/resolved, раунд завершается нормально. Таким образом, «молчание bridge = решение за человеком» работает.
Две настройки, которые незаметно отключают «передачу решения человеку в TUI»
Прежде чем запрос на подтверждение достигнет любого клиента, он сначала проходит через настройки codex самого пользователя. Если активно любое из следующих двух, ваш TUI вообще не получит запрос, и молчание bridge не будет означать, что решение принимает человек:
Настройка | Местоположение | Эффект |
Hook |
| Hook перехватывает запрос на подтверждение. На macOS проверено: при активном hook оба клиента не получают ни одного запроса на подтверждение, файлы всё равно записываются |
|
| Передаётся subagent, который автоматически решает на основе риска, не спрашивая человека |
Обе настройки являются разумными личными настройками; этот проект не будет их изменять; просто нужно знать: когда они активны, «человек» в --approvals tui — это на самом деле они. Чтобы проверить, какая ситуация на вашей машине, запустите spike-approvals.mjs — этот spike запускает свой собственный app-server с --disable hooks -c approvals_reviewer=user, отключая обе настройки, и измеряет сам протокол.
Форматы ответов на различные запросы подтверждения не единообразны — только два item/*/requestApproval принимают {decision:"decline"}; item/permissions/requestApproval требует (пустой) профиль разрешений, старые execCommandApproval / applyPatchApproval требуют {decision:{denied:{rejection}}}. Неправильная форма — это ошибка схемы, а не вежливый отказ. Таблица соответствия в DEFAULT_SERVER_REQUEST_RESPONSES в src/appServerWsClient.mjs.
Почему не другие подходы
Подход | Проблема |
| Захватывает отрендеренный TUI: символы рамок, спиннеры, обрывы строк; определить, «закончил ли ответ», можно только опросом изменений экрана |
Общий файловый почтовый ящик (прямое направление) | Работоспособно, но не видно состояния в реальном времени, и требуется ручное вмешательство для триггера |
Subagent | Каждый раз холодный старт, отдельная сессия, не подключается к тому TUI, который вы видите |
Данное решение | Структурированные события; |
Обратное направление по-прежнему использует файловый почтовый ящик — но потому что у Claude Code нет сокета для подключения, а Stop hook делает «триггер» автоматическим, без ручного вмешательства.
Безопасность
Listener привязан к loopback.
--ws-authдействует только на non-loopback, поэтому на локальной машине токен не требуется.Подтверждения по умолчанию передаются человеку (
tui), при таймауте fail-closed. Запросы от сервера к клиенту, не связанные с подтверждением (вызов инструментов, MCP elicitation), всегда fail-closed — у bridge нет UI, чтобы спрашивать человека.
Проверено / не проверено
Три spike, каждый запускает свой собственный app-server (ephemeral port), не затрагивая ваш текущий поток:
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,見下На macOS (26.5.1, Node v24.19.0 LTS, codex-cli 0.147.0) результаты: все три spike пройдены, 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, включая китайский) также протестированы вручную на macOS;
inbox hook работает даже на Node 20, так что Stop hook не требует определённой версии node.
Кроме того, проверено на реальном TUI (codex --remote): сообщение, отправленное Claude Code, отображается в TUI как сообщение пользователя, Codex нормально отвечает, поток ответа возвращается в Claude Code.
Выяснено (codex-cli 0.147.0):
Уведомления всегда содержат
threadId, потоковые (например,item/agentMessage/delta) дополнительно содержатturnId, иturn.id, возвращаемыйturn/start, полностью совпадает с тем, что в потоке. Ранее считалось, что «уведомления не содержат threadId», на самом деле это был симптом того, что клиент не присоединился к потоку.Поток должен быть сначала присоединён через
thread/resume, чтобы получать уведомления; а поток становится возобновляемым только после завершения первого раунда диалога.historyMode: "paginated"(поток, созданный TUI) →thread/readсincludeTurnsзавершается ошибкой (list_turns is not supported yet). Получение истории постфактум в настоящее время не работает; ответы получаются через поток в реальном времени.Подтверждения рассылаются всем клиентам, засчитывается ответ первого (подтверждено на macOS). Молчаливый клиент также получает запрос, затем получает
serverRequest/resolved, раунд не зависает. На машинах, где не запускается OS sandbox helper (на некоторых управляемых корпоративных Windows может появлятьсяShellExecuteExW failed to launch setup helper: 1223), запись файла завершается ошибкой до того, как будет задан вопрос человеку, и запрос на подтверждение вообще не отправляется, поэтому эти три пункта будут помечены как SKIP — ограничение среды, не проблема протокола.Пользовательский hook
PermissionRequestполностью перехватывает запросы на подтверждение, клиент не получает ни одного (проверено на macOS). Подробности см. в разделе «Подтверждения» выше.
Не проверено:
turn/steer(вмешательство в выполняющийся раунд) — только читал схему, не тестировал.-Cне тестировал→ уже протестировано: когда TUI подключается без-C, cwd потока — это cwd app-server, независимо от того, в какой директории вы запустилиcodex --remote; с-C <dir>cwd меняется на указанную директорию. (macOS, с запуском TUI через pty, другой клиент читаетthread/read.)Весь протокол помечен как
[experimental], может измениться при обновлении codex.
Ссылки
Почему обратное направление использует Stop hook, а не другой механизм:
docs/reverse-channel.md
Схема протокола: 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