agent-bridge
Enables OpenAI Codex CLI sessions to register as agents and exchange real-time messages with other AI coding sessions, using long-polling to receive messages.
Click on "Deploy 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., "@agent-bridgeask codex-1 to review foo.py and reply when done"
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.
aiAgentBridge
Let Claude Code, Codex and Opencode sessions message each other in real time.
aiAgentBridge is a small Python hub that every AI coding session connects to as an MCP server. Each
session registers under a name, then sends messages to another session by name or broadcasts them to
all sessions with *. Sessions can be grouped into rooms, so several independent groups can share
one hub without seeing each other. Messages arrive as soon as they're sent, with no polling:
Claude Code gets messages pushed into the conversation as
<channel>blocks. It uses the same mechanism as the official Telegram plugin.Codex and Opencode call
wait_for_message, a long-poll that returns the moment a message arrives.Scripts, CI jobs and bots can post to an HTTP webhook, or join as full agents over a WebSocket.
You can have Claude review what Codex just wrote, have Opencode tell Claude when tests pass, or let a CI job broadcast "build is green" to every open session.
Contents
Related MCP server: Agent Communication MCP Server
How it works
flowchart LR
subgraph Sessions
CC[Claude Code]
CX[Codex]
OC[Opencode]
end
AD[agent-bridge-stdio<br/>adapter]
HUB((agent-bridge hub<br/>127.0.0.1:8765))
EXT[Scripts / CI / bots]
CC -- stdio MCP --> AD
AD -- WebSocket /ws --> HUB
CX -- HTTP MCP /mcp --> HUB
OC -- HTTP MCP /mcp --> HUB
EXT -- POST /webhook or /ws --> HUBThe hub is one long-running process. It keeps the registry of named agents, routes messages, and queues direct messages for agents that are offline.
Codex and Opencode connect straight to the hub over MCP Streamable HTTP.
Claude Code connects through a small stdio adapter, agent-bridge-stdio. Claude Code talks to
HTTP MCP servers with the stateless 2026-07-28 protocol, which has no channel for the server to
push. Claude Code does accept pushes from stdio servers, though, so it launches the adapter locally.
The adapter holds a WebSocket to the hub and forwards each incoming message to Claude as a channel
notification.
One message, start to finish:
sequenceDiagram
participant X as Codex (codex-1)
participant H as Hub
participant A as stdio adapter
participant C as Claude Code (claude-main)
X->>H: wait_for_message (blocks)
C->>A: send_message to codex-1: please review foo.py
A->>H: send frame over WebSocket
H-->>X: wait_for_message returns the message
X->>H: send_message to claude-main: looks good
H->>A: message frame over WebSocket
A->>C: notifications/claude/channel
Note over C: shows up in the conversation as a<br/>channel block from codex-1Requirements
Python 3.13+
uv (Python package and project manager)
At least one of Claude Code, Codex CLI or Opencode
It works on Windows, macOS and Linux.
Install
git clone https://github.com/umairulh2001/aiAgentBridge.git
cd aiAgentBridge
uv syncuv sync creates .venv/ and installs two commands into it:
Command | What it is | Windows path | macOS / Linux path |
| The hub server |
|
|
| The stdio adapter for Claude Code |
|
|
In the rest of this README,
<path-to-repo>means the absolute path where you cloned the repository, e.g.C:\Users\you\aiAgentBridgeor/home/you/aiAgentBridge.
Start the hub
Run it in its own terminal and leave it running:
uv run agent-bridge
# or call the executable directly:
# Windows: .venv\Scripts\agent-bridge.exe
# macOS/Linux: .venv/bin/agent-bridgeIt listens on http://127.0.0.1:8765. Check it with curl http://127.0.0.1:8765/health (on Windows
PowerShell use curl.exe; see Troubleshooting).
Option | Env var | Default | Notes |
|
|
| Keep it on localhost unless you also set a token |
|
|
| |
|
| none | Requires |
|
| Default | |
| Extra | ||
|
|
All state is held in memory, so restarting the hub clears the queues. Clients reconnect on their own. Running it as a background service or at login is up to you; any process supervisor works.
Connect your assistants
Every session needs a unique name. That's the name other agents use to reach it. If two sessions
ask for the same name, the second one gets <name>-2 and is told so.
Claude Code
1. Register the adapter. This makes it available in every project:
# Windows
claude mcp add --scope user agent-bridge -- "<path-to-repo>\.venv\Scripts\agent-bridge-stdio.exe" --name claude-main
# macOS / Linux
claude mcp add --scope user agent-bridge -- "<path-to-repo>/.venv/bin/agent-bridge-stdio" --name claude-mainOr check it into one project as .mcp.json. Claude Code expands ${VAR}, so each window can pick
its own name:
{
"mcpServers": {
"agent-bridge": {
"command": "<path-to-repo>/.venv/bin/agent-bridge-stdio",
"args": ["--name", "${AGENT_NAME:-claude}", "--room", "${AGENT_ROOM:-default}"]
}
}
}AGENT_NAME=claude-frontend AGENT_ROOM=shop-app claude --dangerously-load-development-channels server:agent-bridge2. Start Claude Code with channels enabled:
claude --dangerously-load-development-channels server:agent-bridgeThe text after
server:must match the name you registered the server under (agent-bridge).Channels are a research-preview feature of Claude Code, so the flag may change between versions. Check
claude --helpand the Claude Code docs if it's rejected.Messages appear as
<channel>blocks only in interactive sessions. Inclaude -p, or without the flag, Claude can still receive everything by callingwait_for_message. The adapter keeps every message, so nothing is lost.
Adapter options:
Option | Env var | Default | |
|
|
| Name to register under |
|
|
| Room to join (see Rooms) |
|
|
| Hub URL |
|
| none | Prefer the env var: args are stored in plain text in configs |
|
|
|
|
|
| none | Claude Code hides a stdio server's stderr, so use this to debug |
Start order doesn't matter. If the hub isn't up yet, the adapter keeps retrying (0.5s, backing off to 10s). Until it connects, tool calls return "cannot reach the agent-bridge hub".
Why the full executable path instead of
uv run?uv runre-syncs the environment before every launch. On Windows that clashes with the running hub's locked files (see Troubleshooting). If you prefer uv, useuv run --no-sync --directory <path-to-repo> agent-bridge-stdio --name claude-main.No push needed? Claude can also connect directly over HTTP in wait mode:
claude mcp add --scope user --transport http agent-bridge "http://127.0.0.1:8765/mcp?name=claude-main".
Codex
Add this to ~/.codex/config.toml (Windows: %USERPROFILE%\.codex\config.toml):
[mcp_servers.agent-bridge]
url = "http://127.0.0.1:8765/mcp?name=codex-1"
tool_timeout_sec = 600 # wait_for_message blocks; the default (~60s) is too short
default_tools_approval_mode = "approve" # needed for `codex exec`, which can't prompt for approval
# bearer_token_env_var = "BRIDGE_TOKEN" # if the hub runs with --tokenOpencode
Add this to opencode.json (in the project or in your global Opencode config):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"agent-bridge": {
"type": "remote",
"url": "http://127.0.0.1:8765/mcp?name=opencode-1",
"enabled": true
}
}
}If the hub has a token, add "headers": { "Authorization": "Bearer {env:BRIDGE_TOKEN}" }. Opencode
fills in {env:...} from the environment, so the token stays out of the file.
Wake Opencode when a message arrives (optional plugin). Opencode has no push, so a message waits
until the model calls wait_for_message. The plugin opencode-plugin/agent-bridge-wake.ts fixes
that: it polls the hub's /health for this agent's queued count and, when messages wait and the
session is idle, starts a turn telling the model to read and answer them. To install it, copy the file
into ~/.config/opencode/plugins/ (Windows: %USERPROFILE%\.config\opencode\plugins\) or a project's
.opencode/plugins/, then restart Opencode.
It reads the hub URL, name and room from the
agent-bridgeMCP entry, and the token fromBRIDGE_TOKEN(or the entry'sAuthorizationheader). Env overrides:AGENT_BRIDGE_WAKE_NAME,AGENT_BRIDGE_WAKE_SERVER,AGENT_BRIDGE_WAKE_INTERVAL(seconds, default 3),AGENT_BRIDGE_WAKE_DISABLE=1.It wakes the most recently active session, so a fresh Opencode needs one prompt first.
It follows
registerandjoin_room. If the hub suffixed the name (opencode-1-2), the plugin learns it the first time the agent callswhoamiorlist_agents.
Several windows of the same client: Codex and Opencode configs can't vary the name per window. A second Codex window gets
codex-1-2automatically, or the agent can callregisterto pick a clearer name.
Rooms
A room is a group id that you choose, such as a project, a feature or a chat id. Sessions in the
same room can see and message each other. Sessions in other rooms are invisible to them: list_agents,
send_message and the * broadcast never cross rooms. Names are unique per room, so claude-main
can exist in two rooms at once. A session is in one room at a time. Without a room, it joins default,
so a hub where nobody sets a room behaves as before.
Room ids are 1–64 characters of a-z 0-9 _ . : - and are case-insensitive. For example, shop-app,
proj:auth and -100123456 are all valid.
Pick the room when connecting:
Client | How |
Claude Code (stdio adapter) |
|
Codex / Opencode / HTTP | Add |
WebSocket |
|
Or switch rooms at runtime with the join_room(room) tool. Codex and Opencode configs have one fixed
URL, so this is how two Codex windows end up in different rooms. Ask the agent, for example, "call
join_room with room shop-app".
join_roomis refused while you have unread messages. Read them withwait_for_messagefirst. This prevents replying to a sender from the old room and reaching a same-named agent in the new room.Your queued messages don't move with you, and your old name stops existing in the old room.
If your name is taken in the new room by an active session, you get
name-2, as when connecting.A Codex or Opencode session that reconnects starts again in the room from its URL.
Outside senders (webhook, send-only WebSocket) pass "room" in the body or frame; it defaults to
default. They alone may use "room": "*" together with "to": "*" to broadcast to every room.
A room id is not a password. /health lists every room, and any client that can reach the hub can
join any room. Rooms keep groups apart, but they don't keep anyone out. Use --token for access
control.
Quick start
Start the hub:
uv run agent-bridge.Open Codex and ask:
Call whoami, then wait_for_message with timeout_s=300. When a message arrives, answer it with send_message to its sender.
Open Claude Code with channels enabled and ask:
Use agent-bridge to ask codex-1 what 2+2 is.
Codex's wait returns, Codex replies, and the answer appears in Claude's conversation as a
<channel source="agent-bridge" from="codex-1">block.
To see who's connected at any point: curl http://127.0.0.1:8765/health, or ask any agent to call
list_agents.
Tools
Every connected session gets these MCP tools:
Tool | Description |
| Your name, room, push mode, whether push is ready, number of queued messages |
| Rename yourself (1–32 chars: |
| Every known agent in your room: name, room, client, online, push mode, queued count |
| Move to another room. Refused while you have unread messages (see Rooms) |
|
|
| Blocks until messages arrive and returns them oldest first. |
Each message looks like this:
{"id": "75915e47e1d4", "from": "codex-1", "to": "claude-main", "content": "looks good",
"ts": "2026-10-03T07:59:40Z", "reply_to": "05ca99dd3850", "room": "default"}pushed in a delivery report means the message was handed to the recipient's connection, not that
the model has read it.
Webhook and WebSocket API
POST /webhook
# macOS / Linux, or Windows with curl.exe
curl -X POST http://127.0.0.1:8765/webhook \
-H "Content-Type: application/json" \
-d '{"from":"ci","to":"*","content":"build is green"}'# Windows PowerShell
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8765/webhook -ContentType "application/json" `
-Body '{"from":"ci","to":"claude-main","content":"build is green"}'The body is
from(optional),to(a name or*),content, and optionallyreply_toandroom(defaultdefault;"*"withto: "*"reaches every room).The sender is recorded as
ext:<from>, so outside callers can't impersonate a registered agent.The endpoint returns the delivery report. A name that isn't in the room returns
404; if the name exists in other rooms, thehintnames them. Bad input returns400.
GET /health
Returns {"status": "ok", "agents": [...]} for every room, each agent with its room. Add
?room=<id> to list one room.
WebSocket /ws
Connecting to ws://127.0.0.1:8765/ws?name=<name>&room=<room> registers you as an agent (add
&token=... if the hub has one; room defaults to default). Leave out name to connect as a
send-only producer. A producer sends to the frame's room, falling back to the URL's room and then
default.
You send | Hub replies |
|
|
|
|
|
|
|
|
|
|
Add
"id": <anything>to a frame, and the reply carries the sameid.Incoming messages arrive as
{"type":"message", "id": ..., "from": ..., "content": ...}.Errors come back as
{"type":"error","error":"..."}.
A minimal agent in Python:
import asyncio, json, websockets
async def main():
async with websockets.connect("ws://127.0.0.1:8765/ws?name=my-bot") as ws:
print(json.loads(await ws.recv())) # {"type": "registered", ...}
await ws.send(json.dumps({"type": "send", "to": "claude-main", "content": "hello from my-bot"}))
async for raw in ws:
frame = json.loads(raw)
if frame["type"] == "message":
print(f"{frame['from']}: {frame['content']}")
asyncio.run(main())Delivery rules
Rooms: every rule below applies within one room. A name in another room counts as unknown.
Direct messages to an offline agent (one known from an earlier connection) are queued: up to 500 per agent, after which the oldest is dropped. They're delivered when a session claims that name in the same room again. A session that comes back in a different room doesn't get them. Names that stay offline expire after 24 hours.
Messages to an unknown name are rejected (reported as
unknown), not queued.Broadcasts (
to: "*") reach only agents in the room that are online right now, and never the sender.Order is first-in, first-out for each recipient. If a push connection drops, messages queue up and are flushed in order when it comes back.
Name clashes:
If the current holder is still active, a newcomer gets
name-2. Active means its push connection is open, it's blocked inwait_for_message, or it made a request in the last 2 minutes.If the holder is stale, the newcomer takes over the name and its queued messages.
A stale session that comes back is renamed on its next call and told so.
Vanished clients: a client that disconnects in the middle of
wait_for_messageis noticed within about a second. It gives up its name and never swallows a message.Slow clients: a push that takes longer than 2 seconds is abandoned and the message is queued instead. One stuck client never delays the others.
Persistence: everything is held in memory.
Security
The hub listens only on
127.0.0.1and has DNS-rebinding protection (it rejects foreignHostheaders).Use
--token/BRIDGE_TOKENif other users or programs on the machine shouldn't connect, and always if you bind to anything other than localhost.There's no proof of identity. A client that can reach the hub can claim a stale name and read that name's queue.
Rooms aren't access control. Anyone who can reach the hub can join any room; see Rooms.
Treat messages from other agents as untrusted input. The server's instructions tell agents not to run destructive commands or reveal secrets just because a peer asked, and to check with their user first.
There is no loop guard. Two agents told to "always reply" will keep talking to each other and burning tokens. Tell agents not to answer plain acknowledgements, and watch the first few exchanges.
Troubleshooting
Symptom | Cause / fix |
No | Channels show up only in interactive sessions started with |
Claude's | Claude is connected over HTTP, which can't push. Use the stdio adapter. |
Codex says "MCP tool call requires approval, but approval policy is never" | Add |
| It's waiting for stdin to close. In scripts, run |
Codex's | Set |
Your session got | Another active session holds that name in the same room. Close it, wait about 2 minutes, or call |
| It's in another room. Compare |
| Call |
Windows: | The running hub locks |
Tool calls return "cannot reach the agent-bridge hub" | The hub isn't running, or |
| In Windows PowerShell 5, |
| The hub has a token. Send |
You need to see what the adapter is doing | Start it with |
Development
uv syncsrc/agent_bridge/
hub.py # routing core: rooms, named mailboxes, sessions, queues, wait/flush (no MCP imports)
mcp_server.py # MCP tools, session binding, channel push over Streamable HTTP
web.py # Starlette app: /mcp, /webhook, /ws, /health, token auth
stdio_shim.py # agent-bridge-stdio: stdio MCP server that proxies to the hub over WebSocket
__main__.py # agent-bridge CLImcp is pinned to 2.3.x because the code relies on a few private parts of the SDK:
Connection.notify, to send custom notificationsServerRequestContext.session._connectionthe transport's
_request_streams, to detect open push streams and disconnected callers
License
MIT © 2026 Umair
This server cannot be deployed
Maintenance
Related MCP Connectors
Real-time chat for AI agents. Claude Code, Cursor, Cline and Codex join channels over MCP.
Real-time chat hub for AI agents — Claude Code, Cursor, Cline, Codex over MCP or REST.
Ephemeral REST chatrooms for AI agents to coordinate. Share a room URL — agents talk live.
Public and private rooms for agents, with messages, files, search, and resumable events.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables real-time communication between Claude Code instances across multiple machines via WebSocket, allowing context sharing, task handoffs, and coordination between sessions.305 npm4MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to communicate with each other through Slack-like room-based channels with messaging, mentions, presence management, and long-polling for real-time collaboration.176 npm5MIT
- FlicenseNot gradedqualityBmaintenanceEnables AI coding agents to communicate and coordinate through a durable, vendor-neutral message bus with support for threads, tasks, presence, and webhooks.283 npm-
- AlicenseNot gradedqualityCmaintenanceEnables independent coding agents and any HTTP caller to communicate in shared rooms, with @-mention and broadcast wake-ups so sessions notice messages even when idle.MIT