Skip to main content
Glama
ar36planet

claude-codex-bridge

by ar36planet

claude-codex-bridge

CI

Permite que Claude Code le hable al Codex TUI que tienes delante — y tú lo ves todo en tiempo real. A la inversa, Codex también puede enviar mensajes a la sesión que está ejecutando Claude Code.

No es captura de pantalla, ni sondeo de archivos, ni un subagente sin interfaz. Ambos están conectados al mismo hilo del codex app-server: el mensaje que Claude Code envía aparece al instante en la interfaz TUI que estás viendo.

English: README.en.md

Para instalarlo, mira aquí: SETUP.md (中文) · SETUP.en.md (English)

Este README trata sobre la justificación del diseño y el registro de validación — por qué se hizo así, qué hechos ya se han medido y cuáles no. Si quieres ponerlo en marcha directamente, SETUP es más rápido.

Entorno de validación: codex-cli 0.147.0, probado tanto en Windows 11 como en macOS 26, ambos ejecutándose en Node 24 LTS (Krypton). El código no está vinculado a la plataforma (las rutas siempre usan node:path, solo la rama de Windows en resolveCodex() es una excepción). Las diferencias entre ambos y los resultados obtenidos se encuentran en la sección "Validado / No validado" más abajo.

El requisito de Node es >=22 (el campo engines de package.json), se recomienda usar directamente v24 LTS. Cada push ejecuta una matriz completa de ubuntu / macOS / Windows × Node 22, 24 en CI.

codex suele ser un paquete global bajo alguna versión de nvm, y esa versión puede ser inferior a 22 — por lo tanto, el node por defecto también se vuelve antiguo. La solución más limpia es mantener node y codex en la misma LTS:

nvm install 24 && nvm alias default 24
nvm reinstall-packages 20        # 把 codex 等全域套件搬過去(20 換成你原本的版本)

(El binario de codex es un shim con #!/usr/bin/env node, que se ejecuta con el node del PATH, no con la versión con la que se instaló.)

Arquitectura

        ┌──────────────────────────────┐
        │  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)

La clave en la dirección directa es la semántica de thread/resume:

If thread_id identifies a running thread, app-server rejoins that thread.

Por lo tanto, el segundo cliente no inicia una nueva conversación ni reproduce un archivo guardado — se une al mismo hilo que ya está ejecutándose. Solo después de unirse se puede recibir el flujo de notificaciones de ese hilo; no basta con conectarse al endpoint.

Related MCP server: Claude-Gemini MCP Integration Server

Uso

Tres ventanas.

1. Servidor compartido (mantenlo abierto, no lo cierres)

node scripts/serve.mjs --cwd C:\path\to\你的專案

--cwd es el directorio donde Codex trabaja realmente. Cuando el hilo no especifica su propio cwd, hereda el del app-server, así que si no se proporciona este parámetro, se detendrá en el directorio donde iniciaste el script (es decir, la carpeta del propio bridge). También se puede usar la variable de entorno CODEX_BRIDGE_CWD. El puerto se define con --port o CODEX_BRIDGE_PORT (por defecto 8787). El endpoint y el workspace se escriben en .bridge.json, y talk.mjs lo lee automáticamente.

2. El Codex TUI que quieres ver

codex --remote ws://127.0.0.1:8787 -C C:\path\to\你的專案

-C fija el workspace de esa ventana; si no se proporciona, se usará el --cwd establecido arriba.

Primero di algo en el TUI y espera a que responda. El hilo debe haber completado su primera ronda de conversación para poder reanudarse; antes de eso, thread/resume devolverá no rollout found for thread id.

Ten en cuenta esta trampa: talk.mjs list ya ve ese hilo antes de eso — el TUI crea el hilo en cuanto se conecta y aparece en thread/loaded/list. Por lo tanto, "list lo ve" no significa que "se pueda enviar un mensaje". Si te saltas este paso, say enviará el mensaje, Codex responderá en el TUI, pero el bridge no recibirá el flujo de respuesta, solo esperará hasta que se agote el tiempo de espera (síntoma probado en macOS).

3. El lado de Claude Code

node scripts/talk.mjs list               # 列出活著的 thread(含各自的 cwd)
node scripts/talk.mjs say "跑一下測試"     # 送話進去,你會在 TUI 看到
node scripts/talk.mjs read               # 讀完整 thread(結構化 JSON)

Cuando solo hay un hilo, say / read lo seleccionan automáticamente; cuando hay varios, hay que especificarlo con --thread <id> — no se adivina con qué sesión estás hablando. list imprime el cwd de cada hilo, lo que ayuda a distinguirlos cuando hay varias ventanas abiertas.

say también acepta --cwd <dir> (cambia el directorio de trabajo solo para esta ronda) y --approvals (ver más abajo).

Interfaz MCP

La CLI sigue siendo utilizable directamente; la interfaz MCP proporciona herramientas estructuradas con las mismas capacidades principales. Ambas direcciones comparten el mismo código, pero el extremo que inicia la conversación ejecuta su propio proceso STDIO:

  • --role claude: Claude Code envía mensajes activamente al hilo de Codex.

  • --role codex: Codex coloca mensajes activamente en el buzón de Claude.

Pasos de instalación

0. Verifica los requisitos previos

  • Node >=22 (consulta la nota sobre versiones al principio).

  • Este repositorio ya ha ejecutado npm install.

  • MCP es solo una interfaz, no una capa de transporte. Igualmente necesita un serve.mjs en ejecución y un TUI conectado para tener algo con lo que hablar — consulta la sección "Uso" más arriba.

cd <這個 repo>
npm install

1. Decide qué lado instalar

Lo que quieres

Instala esto

Solo que Claude Code pueda hablar a Codex

Solo instala --role claude (lado de Claude Code)

Solo que Codex pueda dejar mensajes a Claude Code

Solo instala --role codex (lado de Codex)

Bidireccional

Instala ambos

Si solo necesitas una dirección, no instales ambos lados. La mitad receptora pasiva no depende de MCP — utiliza el app-server/TUI y el Stop hook de Claude respectivamente.

2. Instalación

Lado de 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

Lado de 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 escribe en el .mcp.json de ese proyecto; para usarlo en varios proyectos, cámbialo a --scope user.

Usa rutas absolutas, pero "en qué directorio se inicia" no afecta el resultado — todos los archivos de estado (.bridge.json, .bridge-inbox/, .bridge-output/) se resuelven desde la ubicación del módulo, no desde el cwd. Por lo tanto, una sola instalación es suficiente, no es necesario instalar para cada proyecto.

3. Reinicia

claude mcp add / codex mcp add solo modifican el archivo de configuración, la sesión que ya está ejecutándose no cargará el nuevo servidor MCP. Después de la instalación, cierra y vuelve a abrir esa sesión para que las herramientas estén disponibles.

4. Verifica la instalación

En la sesión reiniciada, llama a bridge_status. Si ok: true y el role son correctos, está bien. Luego, codex_threads_list debería mostrar el hilo de tu TUI (incluyendo su cwd).

Si no quieres abrir una sesión, también puedes verificar la misma ruta desde la línea de comandos:

npm run test:e2e:mcp-send        # 需要 serve + 已跑完第一輪的 TUI

Herramientas

Rol

Herramientas

Compartidas

bridge_status, bridge_output_read

Claude

codex_threads_list, codex_thread_read, codex_message_send

Codex

claude_mailboxes_list, claude_mailbox_peek, claude_message_send

codex_message_send espera todo el turno, por lo que se recomienda aumentar el tiempo de espera en la configuración MCP de Codex y requerir aprobación para las herramientas de escritura:

[mcp_servers.claude-codex-bridge]
tool_timeout_sec = 360
default_tools_approval_mode = "writes"

El modo MCP solo permite CODEX_BRIDGE_APPROVALS=tui (por defecto) o decline, no acepta accept automático. Las respuestas cortas se envían directamente en línea; las que superan los 64 KiB se escriben en .bridge-output/, devolviendo un ID de artifact opaco con TTL, que luego se lee por páginas con bridge_output_read. Una sola captura tiene un máximo predeterminado de 10 MiB, para evitar que una respuesta ilimitada sature el resultado de la herramienta o el heap de Node.

Validación local:

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 + 已跑完第一輪的 TUI

test:e2e:mcp-send es diferente de otros spikes: no inicia un app-server separado, sino que se conecta al TUI que tienes abierto según .bridge.json, creando un turno real en el hilo que estás viendo. Por lo tanto, no está en test:spikes, hay que ejecutarlo manualmente.

Dirección inversa: Codex → Claude Code

Claude Code no tiene un app-server equivalente, no hay un socket al que empujar datos. Lo que tiene es un Stop hook: Claude lo ejecuta antes de finalizar, y si el hook devuelve {"decision":"block","reason":...}, puede evitar que se detenga y usar reason como nueva entrada para continuar.

Por lo tanto, se coloca un buzón en el medio. El buzón tiene nombre — porque puede haber varias sesiones de Claude Code escuchando al mismo tiempo, y si comparten un archivo, la primera que termine se tragaría los mensajes de las demás:

# Codex 那側(或任何地方)留話
node scripts/inbox.mjs push --to bridge "順便幫我看一下 auth 那段"

# 現在有誰在聽(含各自的工作目錄)
node scripts/inbox.mjs list

# 看某個信箱(不消耗)
node scripts/inbox.mjs peek --as bridge

--to es "a quién va dirigido este mensaje", --as es "quién soy yo al leerlo". Ambos por defecto toman $CODEX_BRIDGE_MAILBOX y luego default.

El .claude/settings.json de este repositorio ya tiene el Stop hook configurado (nombre del buzón bridge). Cuando Claude Code finalice su trabajo en este proyecto, vaciará automáticamente el buzón y continuará. Ese archivo está versionado, por lo que si clonas el repositorio y lo abres con Claude Code, este hook entrará en vigor automáticamente — cuando el buzón esté vacío, permanecerá completamente en silencio; si no lo necesitas, elimina .claude/settings.json. Los mensajes se entregan solo una vez: drain() primero renombra y luego lee, por lo que alguien que esté escribiendo al mismo tiempo no leerá datos a medias.

Para más detalles y compensaciones, consulta docs/reverse-channel.md.

Hacer que otras sesiones de Claude Code también usen este puente

El servidor solo necesita iniciarse una vez, las demás sesiones lo comparten. El estado del script (.bridge.json, buzones) se resuelve desde la ubicación del propio módulo, no desde el cwd, por lo que llamarlo con una ruta absoluta desde cualquier directorio funciona correctamente.

Dirección directa (esa sesión → Codex): No requiere configuración, se llama directamente.

$bridge = "C:\path\to\claude-codex-bridge"
node "$bridge\scripts\talk.mjs" list
node "$bridge\scripts\talk.mjs" say --thread <threadId> "..."

Cuando hay múltiples TUI abiertos, asegúrate de usar --threadlist mostrará el cwd de cada hilo para que los identifiques. (O configura CODEX_BRIDGE_URL, para no depender de .bridge.json).

Dirección inversa (Codex → esa sesión): Debes agregar el Stop hook en el .claude/settings.json de ese proyecto y darle un nombre de buzón propio:

{ "hooks": { "Stop": [ { "matcher": "*", "hooks": [
  { "type": "command",
    "command": "node \"C:/path/to/claude-codex-bridge/scripts/inbox.mjs\" hook --as web" }
] } ] } }

Ten en cuenta que no se puede usar $CLAUDE_PROJECT_DIR — eso apuntaría al propio proyecto, no al bridge. La ruta debe estar fija en el bridge. El nombre del buzón (web en el ejemplo) lo eliges tú, uno por sesión.

Luego, desde el lado de Codex, puedes enviar mensajes con nombre:

node scripts/inbox.mjs push --to web "先把 CORS 那條修掉"
node scripts/inbox.mjs list       # 確認名字沒打錯、對方還活著

Los datos de list provienen del auto-registro de cada sesión cada vez que se ejecuta su Stop hook, por lo que esa sesión debe haberse finalizado al menos una vez para aparecer en la lista.

Aprobación (cuando Codex necesita modificar algo)

Si el turno enviado por Claude Code va a ejecutar comandos o modificar archivos, Codex emitirá una solicitud de aprobación. El app-server difunde esta solicitud a todos los clientes conectados, y el primero que responda es el que cuenta (los que no lo consiguen reciben serverRequest/resolved). Por lo tanto, la estrategia predeterminada es tui: el bridge permanece en silencio, dejando que la decisión la tome el indicador de la ventana que tienes delante.

Si nadie responde, todo el turno se queda bloqueado, por lo que hay un seguro: si nadie responde después de CODEX_BRIDGE_APPROVAL_TIMEOUT_MS (predeterminado 300 segundos), el propio bridge rechaza con fail-closed para que el turno pueda continuar.

node scripts/talk.mjs say --approvals decline "..."   # 沒開 TUI 時用
node scripts/talk.mjs say --approvals accept  "..."   # 只用在你已經信任的環境

También se puede usar CODEX_BRIDGE_APPROVALS para establecer el valor predeterminado.

La ruta de difusión ya está confirmada en macOS (spike-approvals.mjs 11/11): un cliente presiona aprobar, otro cliente que permanece en silencio recibe la misma solicitud y luego recibe serverRequest/resolved, y el turno continúa con normalidad. Por lo tanto, "bridge silencioso = dejar que alguien decida" es válido.

Dos configuraciones que pueden hacer que "dejar que la persona del TUI decida" falle silenciosamente

Antes de que la solicitud de aprobación llegue a cualquier cliente, primero pasa por la configuración de codex del usuario. Si alguna de las siguientes está activa, el TUI que tienes delante ni siquiera será preguntado, y el silencio del bridge no significa que la persona esté decidiendo:

Configuración

Ubicación

Efecto

Hook PermissionRequest

~/.codex/hooks.json

El hook intercepta la solicitud de aprobación primero. Probado en macOS: con el hook activo, los dos clientes no reciben ninguna solicitud de aprobación, pero el archivo se escribe igual

approvals_reviewer = "auto_review"

~/.codex/config.toml

Se lo pasa a un subagente que decide automáticamente según el riesgo, sin preguntar a la persona

Ambas son configuraciones personales razonables; este proyecto no las modificará. Solo hay que saber: cuando están activas, la "persona" en --approvals tui son en realidad ellas. Para confirmar en qué estado está tu máquina, ejecuta spike-approvals.mjs — ese spike inicia su propio app-server con --disable hooks -c approvals_reviewer=user para desactivar ambas, midiendo solo el protocolo en sí.

El formato de respuesta para las diversas solicitudes de aprobación no es consistente — solo dos item/*/requestApproval aceptan {decision:"decline"}; item/permissions/requestApproval requiere un perfil de permisos (vacío), y los antiguos execCommandApproval / applyPatchApproval requieren {decision:{denied:{rejection}}}. Responder con la forma incorrecta es un error de esquema, no un rechazo cortés. La tabla de correspondencia está en src/appServerWsClient.mjs en DEFAULT_SERVER_REQUEST_RESPONSES.

Por qué no otras soluciones

Solución

Problema

wezterm cli send-text / get-text

Captura la pantalla renderizada del TUI: caracteres de borde, spinner, saltos de línea truncados; determinar "si ha terminado" solo se puede hacer sondeando los cambios en la pantalla

Buzón de archivos compartidos (dirección directa)

Factible pero no se ve el estado en tiempo real, y la activación requiere intervención manual

Subagente /codex:rescue

Cada inicio en frío, sesión independiente, no se conecta al TUI que tienes delante

Esta solución

Eventos estructurados; turn/steer incluso puede intervenir en un turno en ejecución

La dirección inversa sigue siendo un buzón de archivos — pero eso es porque Claude Code no tiene un socket al que conectarse, mientras que el Stop hook hace que la "activación" no requiera intervención manual.

Seguridad

  • El listener se vincula a loopback. --ws-auth solo tiene efecto en conexiones non-loopback, por lo que en local no se necesita token.

  • La aprobación se delega por defecto a la persona (tui), con fail-closed en caso de tiempo de espera agotado. Las solicitudes de servidor a cliente que no son de aprobación (llamadas a herramientas, MCP elicitation) siempre fallan con fail-closed — el bridge no tiene una interfaz de usuario para preguntar a la persona.

Validado / No validado

Tres spikes, cada uno inicia su propio app-server (puerto efímero), sin afectar el hilo que estás viendo:

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,見下

Resultados de la ejecución real en macOS (26.5.1, Node v24.19.0 LTS, codex-cli 0.147.0): los tres spikes pasaron, npm test 21/21, npm run test:integration:mcp-app-server PASS. scripts/serve.mjs --cwd, scripts/talk.mjs list, y scripts/inbox.mjs (push / list / peek / hook, incluido chino) también se probaron manualmente en macOS; el inbox hook funciona incluso con Node 20, por lo que el Stop hook no necesita una versión específica de Node.

Además, se confirmó con un TUI real (codex --remote): el mensaje enviado por Claude Code se renderiza como un mensaje de usuario en el TUI, Codex responde con normalidad, y el flujo de respuesta vuelve a Claude Code.

Ya aclarado (codex-cli 0.147.0):

  • Todas las notificaciones llevan threadId, las de tipo flujo (item/agentMessage/delta) también llevan turnId, y el turn.id devuelto por turn/start coincide exactamente con el del flujo. Inicialmente se pensó que "las notificaciones no llevan threadId", pero en realidad era un síntoma de que el cliente no se había unido al hilo.

  • El hilo debe unirse primero con thread/resume para recibir notificaciones; y el hilo debe haber completado la primera ronda de conversación para poder reanudarse.

  • historyMode: "paginated" (hilo creado por TUI) → thread/read con includeTurns falla (list_turns is not supported yet). Actualmente, recuperar el historial a posteriori por esta vía está roto; la respuesta depende del flujo en tiempo real.

  • La aprobación se difunde a todos los clientes, y el primero en responder es el que cuenta (confirmado en macOS). El cliente que permanece en silencio también recibe la solicitud y luego recibe serverRequest/resolved, el turno no se bloquea. En máquinas donde el sandbox helper de OS no se inicia (ciertos Windows empresariales gestionados pueden mostrar ShellExecuteExW failed to launch setup helper: 1223), la escritura de archivos falla antes de "preguntar a la persona", por lo que la solicitud de aprobación nunca se emite, y esos tres elementos se marcan como SKIP — una limitación del entorno, no un problema del protocolo.

  • El hook PermissionRequest a nivel de usuario intercepta toda la solicitud de aprobación, el cliente no recibe ninguna (probado en macOS). Para más detalles, consulta la sección "Aprobación" más arriba.

Aún no validado:

  • turn/steer (intervenir en un turno en ejecución) solo se ha leído el esquema, no se ha probado.

  • -C no se probóYa probado: cuando el TUI se conecta sin -C, el cwd del hilo es el cwd del app-server, independientemente del directorio desde el que ejecutes codex --remote; al usar -C <dir>, se cambia a ese directorio. (macOS, usando pty para iniciar el TUI y otro cliente para leer thread/read).

  • Todo el protocolo está marcado como [experimental], y puede cambiar con las actualizaciones de codex.

Referencias

Por qué la dirección inversa usa un Stop hook en lugar de otro mecanismo: docs/reverse-channel.md

Esquema del protocolo: codex app-server generate-json-schema --out <dir>

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    Not graded
    quality
    B
    maintenance
    The self-hosted MCP bridge between Claude Chat and Claude Code.
    46
    AGPL 3.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Connects Claude Desktop and Claude Code, enabling autonomous exchange of messages, files, and code while keeping their context windows separate.
    4
    MIT

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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