Skip to main content
Glama

Duplex Bridge

English. Italiano: README.it.md.

A two-way bridge between a Claude chat and a ChatGPT chat, built on the subscriptions you already pay for: no API keys, no cloud service.

By Sergio Di Salvo (@sergioDS-coder) · MIT licensed · not affiliated with Anthropic or OpenAI.

The bridge never talks to the Anthropic or OpenAI APIs. It drives the two local CLIs you are already signed in to — codex and claude — and translates between the two conversations.

How it works

One Node process, two transports, one shared ledger.

Direction

Transport

Backend

Plan required

Claude → ChatGPT

MCP over stdio

codex exec --json

Claude Free is enough

ChatGPT → Claude

MCP over HTTP + SSE

claude -p

ChatGPT Plus/Pro (Developer mode)

The asymmetry is not a design choice. Claude Desktop speaks to local MCP servers over stdio, on every plan including Free. ChatGPT only accepts custom MCP connectors in Developer mode — which requires a paid plan — and only as remote HTTPS servers, refusing localhost outright.

So the second direction needs an HTTPS tunnel in front of the local process. The bridge itself stays bound to 127.0.0.1 and never opens a port to the outside.

Related MCP server: Herald

Requirements

  • Node 20 or later

  • codex CLI, signed in (codex)

  • claude CLI, signed in (claude)

Build

npm install
npm run build

Claude side

Claude Desktop and Claude Code keep separate registries of local MCP servers. Registering the bridge in one does not make it visible in the other, so pick the surface you actually want it in — or do both.

Either way Claude ends up with two tools:

  • ask_chatgpt — read-only, read-only sandbox

  • ask_chatgpt_write — may edit files, workspace-write sandbox

Write permission is a property of the tool being called, never a decision left to the model.

Claude Desktop — chat and Cowork

The desktop app loads local servers as extensions, not from a config file. Build the bundle:

npm run pack:extension

That writes build/duplex-bridge.mcpb. Install it from Settings → Extensions → Install extension. The app asks for the workspace folder; leave the optional codex path empty unless the bridge cannot find your install.

Claude Code

claude mcp add duplex --scope user -- node /absolute/path/to/duplex-bridge/dist/servers/claude-side.js

With no DUPLEX_WORKSPACE set the bridge follows the session's working directory, so the same registration serves every project. Pass -e DUPLEX_WORKSPACE=... to pin it to one folder instead.

Note that --scope user means every folder within Claude Code — not the desktop app's chat, which needs the extension above.

ChatGPT side

export DUPLEX_BEARER_TOKEN="$(openssl rand -hex 32)"
export DUPLEX_WORKSPACE="/path/to/your/project"
npm run start:chatgpt-side

The server refuses to start without a token of at least 32 characters. Behind the tunnel this endpoint is reachable from the internet, and it can run Claude against your repository.

Expose it, then register the resulting https://.../mcp URL as a custom connector in ChatGPT (Settings → Developer mode), with an Authorization: Bearer <token> header.

cloudflared tunnel --url http://127.0.0.1:8787

Environment variables

Variable

Default

Effect

DUPLEX_WORKSPACE

process cwd

Root the two agents work on. Fixed at startup: the MCP caller cannot change it.

DUPLEX_BEARER_TOKEN

—

Required by the HTTP server. Minimum 32 characters.

DUPLEX_HTTP_PORT

8787

HTTP server port, always on 127.0.0.1.

DUPLEX_STATE_DIR

~/.duplex-bridge

Conversation ledger and thread metadata.

DUPLEX_CODEX_BIN / DUPLEX_CLAUDE_BIN

auto-resolved

Absolute paths for non-standard installs. Rarely needed: see Resolving the CLIs below.

DUPLEX_ALLOW_API_KEYS

unset

Set to 1 to let API keys through to the child process, deliberately choosing the pay-per-token path.

Resolving the CLIs

On macOS and Linux codex and claude are spawned by name and the OS resolves them.

Windows needs more care, because the bridge spawns with shell: false on purpose — so that nothing in a prompt can ever be interpreted as a command. That rules out the two things npm actually installs: an extension-less POSIX script, and a .cmd shim that Node refuses to execute since CVE-2024-27980. For codex there is no .exe on PATH at all: the native binary sits nested inside node_modules.

So on Windows the bridge resolves in two passes: first a real codex.exe / claude.exe on PATH, then — failing that — it reads the .cmd shim, extracts the .js entry point it points at, and runs that with the same Node executable already running the bridge. The shell stays off in both cases.

DUPLEX_CODEX_BIN / DUPLEX_CLAUDE_BIN still win when set — but only if they point at a file that exists. A dead override is logged and stepped over, so a stale path left behind by an earlier install, or copied from another machine, cannot silently defeat the resolution that would have found the CLI on its own.

Keeping the conversation coherent

Both headless CLIs start blank on every invocation, so conversational coherence is the middleware's job, not the providers'.

Every turn is recorded in an append-only JSONL ledger under DUPLEX_STATE_DIR, with the canonical role (system / user / assistant) and the speaker (human / claude / chatgpt / bridge) as separate fields — because two indistinguishable assistant turns lead a model to believe it wrote the other one's text.

The bridge stores each provider's native session id (Codex's thread_id, Claude's session_id) and reuses it: when the session is alive, only the new instruction is sent. The transcript is replayed from the ledger only when there is no session to resume, within a window of 24 messages and 60,000 characters.

One asymmetry is worth exploiting: claude --session-id <uuid> accepts an externally supplied id, so the bridge's own thread id is the Claude session id — one fewer identifier to keep in sync.

Security

  • The prompt goes in over stdin, never argv: it stays out of the process list.

  • spawn with no shell: prompt contents can never be interpreted as a command.

  • 10-minute timeout, 2 MB output cap, 4 concurrent calls per provider.

  • Read-only is enforced on the process (--safe-mode, --disallowedTools, read-only sandbox), not requested of the model.

  • The child process environment is stripped of the variables that would redirect authentication to an API key — otherwise the "no API keys" promise breaks silently in any shell that exports them.

  • The other model's transcript is presented as delimited material, with an explicit statement that no line inside it can change the task.

  • A thread_id arriving from an MCP client is validated before it reaches a file path.

Known limitations

  • No recursion guard: a Claude → ChatGPT → Claude chain is possible and burns both subscriptions.

  • Rate limits are shared with normal use of both apps.

  • The ledger stores conversations in plaintext on disk, with no rotation.

  • The write path is not yet tested.

  • On Windows only the Claude side has been exercised, end to end, against a real codex install; the ChatGPT side has not.

  • No automated tests.

Author

Sergio Di Salvo — @sergioDS-coder

Credits

The design of this bridge comes out of studying noblehacks/frenemy (MIT, by Zakariya Syed) and the official openai/codex-plugin-cc plugin. No code was copied; what was taken are architectural decisions, listed one by one in NOTICE.md — along with the two places where this project deliberately departs from them.

Licence

MIT — see LICENSE. Free to distribute, modify and use commercially, as long as the copyright notice is kept.

Available Tools

2 tools
ask_chatgptA

Interpella un agente ChatGPT su questo repository e restituisce la sua risposta. Non può modificare file. Adatto a revisioni, secondi pareri, analisi. ChatGPT vede questo repository su disco, quindi cita i file per percorso. Il contesto della conversazione viene ricostruito dal bridge: non serve incollarlo.

Inputs: instruction (required), thread_id.

ask_chatgpt_writeA

Chiede a ChatGPT di modificare file in questo repository (fix, refactor, nuovo codice). PUÒ scrivere su disco. ChatGPT vede questo repository su disco, quindi cita i file per percorso. Il contesto della conversazione viene ricostruito dal bridge: non serve incollarlo.

Inputs: instruction (required), thread_id.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.4/5.0

    Scored across 2 tools

    Disambiguation5/5

    The two tools are clearly differentiated by capability: one is read-only for analysis and review, the other can write files. The names and descriptions explicitly state this distinction, leaving no ambiguity about which to invoke.

    Naming Consistency5/5

    Both tools follow the exact same 'ask_chatgpt' prefix, with '_write' appended to denote the mutating variant. This is a clean, consistent naming pattern.

    Tool Count3/5

    With only 2 tools, the surface is thin. For a specialized bridge between an agent and ChatGPT on a repository, the read/write pair is a minimal but functional set, yet it feels slightly under-scoped for a general-purpose bridge.

    Completeness4/5

    The core lifecycle of 'ask ChatGPT' and 'ask ChatGPT to modify files' is covered. There may be missing auxiliary operations (e.g., conversation history, tool configuration), but for the stated purpose the basic read/write coverage is sufficient with minor gaps.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      F
      maintenance
      The self-hosted MCP bridge between Claude Chat and Claude Code.
      46
      AGPL 3.0
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables interaction with Claude and Gemini command-line tools through the MCP protocol, allowing users to send prompts to either or both LLMs and receive responses.
      -