Agent MCP Server
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 MCP Serverdelegate the failing auth tests to my peer agent and report back"
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.
WebSocket Agent Hub
Author: Mann D. Shah · Financial Controller & Systems Architect
Portfolio & Related Repositories: Luna Reminder · MCP Excel Scraper · Full Portfolio
This repository lets independent AI coding sessions communicate and delegate work through one shared WebSocket hub. The working local setup supports:
Codex-to-Codex pairs.
Claude Code clients through MCP.
Mixed clients, provided each side is configured as a member of the same project and names the other side as its peer.
Multiple independent pairs on the same hub.
Persistent WebSocket connections with reconnect handling.
Task delegation, task results, and non-blocking messages.
Automatic Agent A/Agent B window titles.
An optional left/right Windows Terminal layout on a secondary monitor.
The hub transports messages; it does not run an LLM. Codex, Claude Code, or another MCP-capable client performs the actual work.
Start here — future agent instructions
Treat this README and the current source files as the source of truth. docs/SESSION_HANDOFF_2026-08-18.md describes earlier debugging and contains issues that have since been fixed.
Before changing or running the system:
Confirm which project directory the user wants the agents to work in.
Confirm port
8080is either free or already serving this hub.Use unique agent IDs for every simultaneously active pair.
Keep both agents in the same hub project and set each agent's
AGENT_PEER_IDto the other agent.Use the same
JWT_SECRETto start the hub and issue agent tokens.Never start two clients with the same agent ID. A newer connection replaces the older one.
Put the complete task, relevant paths, constraints, and acceptance criteria in the delegation
context; chat history is not automatically shared between models.For a clean verification, run one bounded delegation before starting long work.
Current local paths:
Purpose | Path |
Hub repository |
|
Supply-chain launcher |
|
Cash-flow project |
|
Related MCP server: desktop-agent-bus
How the components connect
flowchart LR
A[Agent A<br/>Codex or Claude Code] -->|stdio MCP| MA[agent-mcp-server.mjs]
MA -->|authenticated WebSocket| H[Agent Message Hub<br/>port 8080]
H -->|authenticated WebSocket| MB[agent-mcp-server.mjs]
MB -->|stdio MCP| B[Agent B<br/>Codex or Claude Code]There are two distinct protocols:
agent-mcp-server.mjsspeaks MCP over standard input/output to the AI client.The adapter uses
agent-instance.mjsto speak the hub's authenticated protocol overws://127.0.0.1:8080/ws.
Do not configure Codex or Claude Code to treat the hub's /ws URL as a remote MCP server. It is not an MCP endpoint.
One-file launcher for any project
Copy the thin wrapper into a project folder. You may rename the copy:
$hub = "C:\Users\13072\Desktop\Python\websoket Agent communication\open-multi-agent-pair.bat"
$project = "C:\path\to\your-project\open-agent-pair.bat"
Copy-Item -LiteralPath $hub -Destination $projectThat .bat is the only Agent Hub file needed inside the project. It delegates to the canonical central launcher, so copied project wrappers do not become stale when the hub implementation changes. Check prerequisites without starting anything:
& "C:\path\to\your-project\open-agent-pair.bat" --diagnoseThen double-click it and enter a pair name, or provide one directly:
& "C:\path\to\your-project\open-agent-pair.bat" inventory_reviewTo open a separate hub console with live logs for a newly started pair:
& "C:\path\to\your-project\open-agent-pair.bat" --show-hub inventory_reviewThe visible hub console closes automatically after the final agent disconnects. If an authenticated hub is already running, the launcher safely reuses it but cannot attach its existing output to a new console.
The launcher:
Derives a stable project ID from the project folder name and a hash of its absolute path.
Reuses port
8080only after both health and administrative-authentication probes pass.Otherwise starts the loopback-only in-memory hub, hidden by default or visible with
--show-hub.Idempotently creates the project, agents, memberships, and fresh one-hour JWTs.
Opens two isolated Codex terminals and waits until both MCP agents are online.
Cleans up terminals and a launcher-owned hub if any startup stage fails.
Stops an unused hub after 60 seconds, or an active hub five seconds after its last agent disconnects.
The wrapper does not start, stop, or modify the project's own application processes. Runtime credentials remain in the central hub directory, not in individual projects.
Always use a new pair name for another simultaneously active pair.
Starting task prompt for Agent A
Paste a real task into Agent A. For example:
Review this supply-chain simulator for correctness and maintainability. First map the architecture yourself. Delegate an independent audit of the simulation calculations and edge cases to external Agent B using agent_hub.delegate_task. Preserve the returned routing IDs, use send_update for any mid-task correction, and use wait_for_task_result for the late result. Verify Agent B's evidence, then give me one combined ranked report. Do not edit files.Agent B should already be calling wait_for_task. After receiving a task, it must do the work in the same turn, call check_for_updates at safe checkpoints approximately every 30 seconds, apply any returned update, and call send_task_result. If cancellation is returned, it must stop starting new work and call confirm_task_cancelled, then wait again.
Agent roles and MCP tools
The MCP adapter exposes twelve tools:
Tool | Intended use |
| Send a bounded task and receive routing IDs; use |
| Resume waiting for a previously delegated result. |
| Request authoritative cooperative cancellation of an active task. |
| Resume waiting for cancellation to be surfaced or confirmed. |
| Wait for delegated work, an update/cancellation, or a peer's |
| Return completed, blocked, or failed work using the exact routing IDs. |
| Send a correction or added instruction for an active task. |
| Resume waiting for the peer to read an update. |
| Surface unread updates or cancellation at a safe worker checkpoint. |
| Confirm that the worker stopped starting new work. |
| Send a non-blocking informational message to the peer. |
| Wait for a non-task informational message. |
Non-task messages after a task finishes
send_message/wait_for_message is a separate channel from task delegation, and a completed task's correlationId cannot carry follow-ups: the hub closes that conversation on send_task_result, and any later send_update against it is rejected with CONVERSATION_CLOSED. Use send_message for anything sent after a task is done.
A peer message is now delivered two ways so neither side has to remember to poll for it separately:
A worker sitting in
wait_for_taskis interrupted immediately by a peer'ssend_messageand gets backstatus: "message_received". Handle it and callwait_for_taskagain right away to resume listening for both tasks and further messages.Any other queued peer message is attached as a
pendingPeerMessagesarray on the JSON result of the next tool call either side makes, whatever that tool is. This is how a coordinator — which has no blocking idle loop once it returns control to the user — picks up a message sent after its last task result: it surfaces on the very nextdelegate_task,wait_for_task_result,send_update,check_for_updates, or any other call.
A coordinator that expects a reply and is about to end its turn has no way to keep listening after that point; it will only see the reply once it calls a tool again (e.g. on the user's next request, or a follow-up delegation). This is a hard limit of MCP's pull-only transport, not something a prompt or a code change on either side can work around: nothing can push text into an idle client's context, and Codex has no hook mechanism to run code before a fresh prompt the way Claude Code does.
Two things are handled outside the model entirely, in agent-mcp-server.mjs, so they do not depend on either agent remembering anything:
Durability. Every incoming peer message is written to a small per-agent mailbox file (
AGENT_HUB_MAILBOX_DIR, one JSON file per message) the instant it arrives, before it ever reaches the model.wait_for_task,wait_for_message, and thependingPeerMessagesdrain all delete a message's file the moment they hand it to the model, and a fresh MCP process re-loads any leftover files on startup. A message an agent never got around to reading survives that agent's process restarting or crashing; it does not survive being read.Out-of-band notice. The instant a peer message arrives,
agent-mcp-server.mjswrites it to stderr and (on Windows, unlessAGENT_HUB_NOTIFY=false) pops a toast notification, independent of whether either agent's model is doing anything at all. This is the only channel that reliably reaches you the moment a message lands rather than only on the peer's next tool call; use it to decide whether to prompt the idle agent yourself.
Coordinator behavior:
Work on the main task.
Call
delegate_taskonly for a useful bounded subtask, parallel investigation, or independent review.Preserve its
correlationIdandrequestMessageId; usesend_updatefor mid-task corrections andwait_for_task_resultfor the result.Use
cancel_taskwhen the user cancels, thenwait_for_task_cancellationif the peer has not surfaced it yet.Supply enough context for the other model to work without the coordinator's chat history.
Wait for and verify the returned evidence.
Incorporate the result into the final answer.
Before ending the turn, check whether the last tool result carried a
pendingPeerMessagesfield; if so, read those messages, since they are the only opportunity to see a worker's post-tasksend_messagebefore the turn ends.At the start of each new turn, before anything else, call
wait_for_messagewithtimeoutSeconds: 2as routine housekeeping to silently pick up anything the worker sent while idle. Atimeoutresult there is normal and means nothing arrived; do not mention it to the user unless it returns an actual message.
Worker behavior:
Call
wait_for_task({"timeoutSeconds":900}).If it returns
task_received, continue in the same turn and execute the task immediately.During the task, call
check_for_updatesat safe checkpoints approximately every 30 seconds: before mutations, between bounded batches, after long-running tools, and before sending the result. This is a target, not a forced interruption interval.If it returns an update, apply it before continuing. If it returns
task_cancellation_requested, stop starting new work and callconfirm_task_cancelledwith the returnedcancelMessageId.Call
send_task_resultwith the exactrequestMessageId,correlationId, andtoAgentIdreturned bywait_for_task. If it returnstask_result_deferred, apply the update and retry with the same routing IDs.Call
wait_for_taskagain after the task is complete or cancelled.If a wait returns
timeout, simply call it again when continued; the WebSocket connection itself is not limited to 900 seconds.If
wait_for_taskreturnsmessage_receivedinstead of a task, that is a non-task message from the coordinator, not work; handle it and callwait_for_taskagain immediately. Also check any tool result for apendingPeerMessagesfield before moving on.
Connecting Claude Code
The supported Claude client is Claude Code with a local stdio MCP server. Claude Code and Codex can use the same agent-mcp-server.mjs because MCP is the client-facing boundary.
1. Start the hub
If it is not already running:
Set-Location -LiteralPath "C:\Users\13072\Desktop\Python\websoket Agent communication"
$env:STORE_MODE = "in-memory"
$env:SEED_DEMO = "true"
$env:PORT = "8080"
$env:HOST = "127.0.0.1"
$env:JWT_SECRET = "local-test-secret-change-this"
$env:ADMIN_TOKEN = "development-admin-token"
npm startCheck it from another terminal:
Invoke-RestMethod http://127.0.0.1:8080/healthExpected result:
{"status":"ok"}2. Provision the Claude identities
Every Claude session needs:
A unique agent ID already registered with the hub.
Membership in the same project as its peer.
A JWT whose subject is that exact agent ID.
The other agent's exact ID in
AGENT_PEER_ID.
The tested idempotent provisioning implementation is the Provisioning ... PowerShell block in open-multi-agent-pair.bat. Reuse that block and change the project ID, project name, IDs, names, and roles. The required REST operations are:
POST /projects
POST /agents (once for each identity)
POST /projects/:projectId/agents (once for each membership)Administrative requests require:
x-admin-token: development-admin-tokenFor local testing, issue two tokens after the TypeScript hub has been built:
$env:JWT_SECRET = "local-test-secret-change-this"
node "C:\Users\13072\Desktop\Python\websoket Agent communication\create-demo-agent-tokens.mjs" `
"claude_pair1_coordinator" `
"claude_pair1_worker"The output contains AGENT_CODEX_A_TOKEN=... and AGENT_CODEX_B_TOKEN=.... The variable names are historical; the JWTs work with any compatible client.
Never commit these tokens or the local development secrets.
3. Add a project-scoped Claude MCP configuration
Create .mcp.json in the project Claude will work on:
{
"mcpServers": {
"agent_hub": {
"type": "stdio",
"command": "node",
"args": [
"C:/Users/13072/Desktop/Python/websoket Agent communication/agent-mcp-server.mjs"
],
"env": {
"AGENT_HUB_URL": "${AGENT_HUB_URL:-ws://127.0.0.1:8080/ws}",
"AGENT_TOKEN": "${AGENT_TOKEN}",
"AGENT_ID": "${AGENT_ID}",
"AGENT_NAME": "${AGENT_NAME}",
"AGENT_TYPE": "${AGENT_TYPE:-claude}",
"AGENT_PROJECT_ID": "${AGENT_PROJECT_ID}",
"AGENT_PEER_ID": "${AGENT_PEER_ID}",
"AGENT_MCP_REQUEST_TIMEOUT_MS": "900000",
"AGENT_RECONNECT_MAX_MS": "30000"
},
"timeout": 920000
}
}
}Claude Code asks for approval before using a project-scoped MCP server from .mcp.json. Review and approve this local server when prompted. Check its status with:
claude mcp listYou can also use /mcp inside Claude Code.
4. Open Claude Agent A
In the target project directory, set the coordinator's values and launch Claude:
$env:AGENT_HUB_URL = "ws://127.0.0.1:8080/ws"
$env:AGENT_TOKEN = "<coordinator JWT>"
$env:AGENT_ID = "claude_pair1_coordinator"
$env:AGENT_NAME = "Claude Pair 1 Coordinator"
$env:AGENT_TYPE = "coordinator"
$env:AGENT_PROJECT_ID = "project_example"
$env:AGENT_PEER_ID = "claude_pair1_worker"
claudeGive it this role instruction:
You are Agent A, the coordinator. Use the agent_hub MCP tools for external delegation. When another agent would materially help, call delegate_task with a precise task, complete context, and timeoutSeconds 900. Do not claim to be waiting unless you actually called the tool. Verify the returned work before answering the user. At the start of each new turn, before anything else, call wait_for_message with timeoutSeconds 2 as routine housekeeping to silently pick up anything Agent B sent while you were idle; a timeout there is normal.5. Open Claude Agent B
Open a second terminal in the same target project and use the worker's distinct identity:
$env:AGENT_HUB_URL = "ws://127.0.0.1:8080/ws"
$env:AGENT_TOKEN = "<worker JWT>"
$env:AGENT_ID = "claude_pair1_worker"
$env:AGENT_NAME = "Claude Pair 1 Worker"
$env:AGENT_TYPE = "worker"
$env:AGENT_PROJECT_ID = "project_example"
$env:AGENT_PEER_ID = "claude_pair1_coordinator"
claudeGive it this role instruction:
You are Agent B, the worker. Immediately call agent_hub.wait_for_task with timeoutSeconds 900. When it returns task_received, continue in this same turn and execute the task. Call agent_hub.check_for_updates at safe checkpoints approximately every 30 seconds, before mutations, between bounded batches, after long-running tools, and before sending a result. Apply updates before continuing. If cancellation is returned, stop starting new work and call confirm_task_cancelled. Otherwise call send_task_result with the exact routing IDs, handle task_result_deferred by applying the update and retrying, then call wait_for_task again. Do not stop after merely displaying the received task. If wait_for_task instead returns message_received, that is a non-task message from Agent A, not work to execute; read it, respond with send_message if appropriate, and immediately call wait_for_task again in the same turn. Also check every tool result's content for a pendingPeerMessages field and read any messages listed there before moving on.Exact mixed pair: Codex Agent A + Claude Agent B
This is the recommended mixed arrangement: Codex owns the main user conversation and Claude Code waits for delegated work.
Use two unique identities, for example:
Agent A: mixed_pair1_codex
Agent B: mixed_pair1_claude
Project: project_mixed_demoProvision both identities and memberships as described above, then issue their tokens:
$env:JWT_SECRET = "local-test-secret-change-this"
node "C:\Users\13072\Desktop\Python\websoket Agent communication\create-demo-agent-tokens.mjs" `
"mixed_pair1_codex" `
"mixed_pair1_claude"Configure the Codex side
Create or extend .codex/config.toml in the target project. A project-scoped configuration applies after the repository is trusted.
[mcp_servers.agent_hub]
command = "node"
args = ["C:/Users/13072/Desktop/Python/websoket Agent communication/agent-mcp-server.mjs"]
env_vars = [
"AGENT_HUB_URL",
"AGENT_TOKEN",
"AGENT_ID",
"AGENT_NAME",
"AGENT_TYPE",
"AGENT_PROJECT_ID",
"AGENT_PEER_ID",
"AGENT_MCP_REQUEST_TIMEOUT_MS",
"AGENT_RECONNECT_MAX_MS"
]
startup_timeout_sec = 30
tool_timeout_sec = 920Open a terminal in the target project and launch Codex with the coordinator identity:
$env:AGENT_HUB_URL = "ws://127.0.0.1:8080/ws"
$env:AGENT_TOKEN = "<JWT printed as AGENT_CODEX_A_TOKEN>"
$env:AGENT_ID = "mixed_pair1_codex"
$env:AGENT_NAME = "Mixed Pair 1 Codex Coordinator"
$env:AGENT_TYPE = "coordinator"
$env:AGENT_PROJECT_ID = "project_mixed_demo"
$env:AGENT_PEER_ID = "mixed_pair1_claude"
$env:AGENT_MCP_REQUEST_TIMEOUT_MS = "900000"
$env:AGENT_RECONNECT_MAX_MS = "30000"
codex --no-alt-screen --cd .Inside Codex, use /mcp to confirm agent_hub is connected. Then give Codex this coordinator instruction:
You are Agent A and Claude Code is external Agent B. Use agent_hub.delegate_task with timeoutSeconds 900 whenever an independent review or bounded parallel task would help. Include all paths, constraints, and acceptance criteria because Agent B cannot see this conversation. Do not use an internal subagent as a substitute. Verify Agent B's returned evidence before answering me. Before ending your turn, check the last tool result for a pendingPeerMessages field; that is the only place a post-task send_message from Agent B will appear. At the start of each new turn, before anything else, call wait_for_message with timeoutSeconds 2 as routine housekeeping to silently pick up anything Agent B sent while you were idle; a timeout there is normal.Configure the Claude side
Use the .mcp.json configuration from the previous Claude section. Open a second terminal in the same project and launch Claude with the worker identity:
$env:AGENT_HUB_URL = "ws://127.0.0.1:8080/ws"
$env:AGENT_TOKEN = "<JWT printed as AGENT_CODEX_B_TOKEN>"
$env:AGENT_ID = "mixed_pair1_claude"
$env:AGENT_NAME = "Mixed Pair 1 Claude Worker"
$env:AGENT_TYPE = "worker"
$env:AGENT_PROJECT_ID = "project_mixed_demo"
$env:AGENT_PEER_ID = "mixed_pair1_codex"
$env:AGENT_MCP_REQUEST_TIMEOUT_MS = "900000"
$env:AGENT_RECONNECT_MAX_MS = "30000"
claudeApprove the project MCP server if prompted, confirm it with /mcp, and give Claude this worker instruction:
You are Agent B and Codex is external Agent A. Immediately call agent_hub.wait_for_task with timeoutSeconds 900. When a task arrives, continue in the same turn, execute it in this repository, and call check_for_updates with that task's correlationId at safe checkpoints. Apply every update before continuing. Call send_task_result with the exact routing IDs only after all updates are read, then call wait_for_task again. Never stop after only showing the received task. If wait_for_task instead returns message_received, that is a non-task message from Agent A; handle it and call wait_for_task again immediately in the same turn.The communication sequence is:
User -> Codex A -> delegate_task -> hub -> Claude B
Claude B -> performs work -> send_task_result -> hub -> Codex A
Codex A -> verifies and answers userWhile Claude B is working, Codex A can use send_update with the delegated task's correlationId. Claude B receives the update through check_for_updates or its normal wait_for_task call. send_update reports transport delivery separately from read acknowledgement; send_task_result is rejected until any unread update has been surfaced.
The current .bat launchers automate Codex-to-Codex pairs. This mixed configuration is manual; it does not yet automatically arrange the Codex and Claude windows on the secondary monitor. Do not run the normal Codex pair launcher and then reuse one of its IDs for Claude, because the duplicate connection will replace the original client.
Connecting another MCP-capable agent
Any client that supports local stdio MCP can use this hub:
Provision its agent ID and project membership through the REST API.
Issue a JWT using the same secret as the running hub.
Configure a local stdio MCP server with command
nodeand argumentagent-mcp-server.mjs.Supply all required environment variables listed below.
Instruct the client to use
wait_for_taskas a worker, callcheck_for_updatesat safe checkpoints, or usedelegate_taskas a coordinator.
Required adapter environment:
Variable | Meaning |
| Hub WebSocket URL. |
| JWT for this exact identity. |
| Unique registered agent ID. |
| Shared project ID. |
| The other member of this pair. |
Optional environment:
Variable | Default |
| Agent ID/client default |
|
|
|
|
|
|
|
|
|
|
Connection lifetime and the 900-second limit
The 900-second value is the maximum duration of one delegate_task, wait_for_task, or wait_for_message call. It is not the lifetime of the WebSocket connection, MCP server, terminal, or AI session.
The connection can remain open as long as the processes remain running. After a wait times out:
Agent B can call
wait_for_taskagain.Agent A can retry a delegation only after checking whether Agent B completed or received the earlier request; do not blindly duplicate work.
The adapter reconnects to the hub after ordinary WebSocket interruptions using bounded exponential backoff.
For Claude Code, the .mcp.json example sets a per-tool timeout slightly above the hub's 900-second maximum. A local stdio MCP process that exits is normally restored by starting a new Claude session or reconnecting it from Claude's MCP controls.
Timeout and burst verification — 2026-08-19
The timeout-recovery behavior was verified with short deterministic wait windows that exercise the same code paths as the 900-second production window:
Agent A's result wait expired while Agent B remained active; Agent A resumed with the same correlation ID and recovered the late result.
Agent B's idle task wait expired while Agent A remained active; Agent B opened a new wait and completed a later delegation.
Agent A's result wait and Agent B's task wait expired simultaneously; both remained usable afterward.
After the simultaneous timeouts, 40 concurrent delegations and 40 corresponding results completed with unique, correctly matched correlation IDs.
The first burst run exposed an O(n²) duplicate-send problem: every new send re-flushed all earlier unacknowledged frames, eventually triggering the hub's pending-frame protection and WebSocket close code 1013. Both client implementations now mark each pending frame with its WebSocket connection generation. A frame is sent once on the current connection and may be resent once on a later connection if it was not acknowledged before reconnecting.
The full build and automated suite passed after the fix: 12 tests passed, 0 failed. The regression coverage is in test/hub.test.ts.
Context and persistence
There are three different kinds of context:
Repository context: files such as this README and
AGENTS.md; future agents can read these after a restart.Model conversation context: private to each Codex or Claude session; it is not shared automatically.
Hub message context: the
taskandcontextfields included in a delegation.
The local launcher uses STORE_MODE=in-memory. Its registry and queued messages disappear when the hub stops. The launchers recreate their projects and memberships, but they do not restore old conversations. Use the Prisma/PostgreSQL mode if durable hub records are required.
Troubleshooting
AUTHENTICATION_FAILED / HTTP 401
The token was signed with a different secret, its subject does not equal AGENT_ID, or the identity was not registered. Confirm:
The hub and token helper used the same
JWT_SECRET.The JWT belongs to this agent ID.
The agent exists in the hub registry.
You are not sending placeholder or truncated token text.
MCP initialize response / connection closed
The stdio adapter exited before completing its MCP handshake. Run these checks:
node --check "C:\Users\13072\Desktop\Python\websoket Agent communication\agent-mcp-server.mjs"
Test-Path "C:\Users\13072\Desktop\Python\websoket Agent communication\agent-mcp-server.mjs"Then verify all five required AGENT_* variables are present in that client session. The MCP server writes protocol messages to stdout, so do not add ordinary stdout logging to it.
A peer's send_message shows delivered/acknowledged in the hub log but the other agent never reacted
Hub-level delivered/acknowledged only proves the bytes reached the peer's local MCP process; it says nothing about whether that agent's model turn actually consumed the message. Before this was fixed, a plain message only surfaced through an explicit wait_for_message call, which neither the worker's task loop nor the coordinator's normal flow ever made — so pings sent between tasks were queued forever and silently missed, even though the hub reported them as delivered. As of the fix described above, a worker sitting in wait_for_task is interrupted immediately (status: "message_received"), and any other agent picks up a queued peer message as a pendingPeerMessages field on the result of its next tool call. If an agent still misses a message, confirm its MCP process did not exit between the send and its next tool call — a queued message does not survive a process restart under STORE_MODE=in-memory.
Agent B says it is waiting, but no task arrives
Check Agent A's transcript. Agent A must actually call agent_hub.delegate_task; writing a plan item or saying “waiting for Agent B” sends nothing.
Agent B displays the task but does not execute it
Reinforce the worker instruction: continue in the same turn, perform the work, call send_task_result with the returned routing IDs, then wait again.
Close code 4001 / replaced connection
Two live clients used the same agent ID. Keep the newest intended session and close the stale one. agent-instance.mjs deliberately does not reconnect a client that the hub replaced.
Delegation points at the wrong repository
The two terminals were launched from the wrong folder, or the delegation context named the wrong path. Verify the visible current working directory in both sessions and include the absolute target path in the delegated context.
No secondary monitor
The supply-chain launcher falls back to two separate terminal windows. This does not affect communication.
Hub state was lost
In-memory mode resets on hub restart. Run the launcher again so it reprovisions the pair, or use the persistent Prisma/PostgreSQL deployment.
Manual development commands
Install and build:
npm install
npm run buildValidate the project:
npm run build
npm test
node --check .\agent-instance.mjs
node --check .\agent-mcp-server.mjs
cmd.exe /d /c open-multi-agent-pair.bat --help
cmd.exe /d /c open-multi-agent-pair.bat --diagnoseHealth and diagnostics:
Invoke-RestMethod http://127.0.0.1:8080/health
Invoke-RestMethod http://127.0.0.1:8080/ready
# /metrics additionally requires the central administrative token.Important files
File | Purpose |
| Starts the Fastify/WebSocket hub. |
| REST endpoints and WebSocket registration. |
| TypeScript client SDK. |
| Portable WebSocket client and Codex pair/window launcher. |
| Stdio MCP-to-WebSocket adapter used by AI clients. |
| Issues two local development JWTs. |
| Creates the ACL-restricted local launcher secrets file. |
| Canonical validation, provisioning, launch, and cleanup implementation. |
| Original seeded Codex demo launcher. |
| Thin wrapper that can be copied as the only launcher file in any project. |
| Persistent data model. |
| Hub configuration template. |
Upstream client documentation
If either client changes its MCP configuration format, verify these pages and then update the client-specific sections of this README. The hub-side environment contract remains defined by agent-mcp-server.mjs.
Production notes
The generated local secrets and in-memory mode in the local launchers are for local testing only. The secret file is ACL-restricted to the current Windows account and ignored by Git and Docker.
The launchers bind the hub to loopback, do not place JWTs in child command lines, close terminal shells when Codex exits, and stop a launcher-owned hub after the last WebSocket disconnects. Message bodies are omitted from the portable runner's console output unless AGENT_LOG_MESSAGE_CONTENT=true is explicitly set.
For a persistent or shared deployment:
Use
STORE_MODE=prismawith PostgreSQL and Redis.Replace
JWT_SECRETandADMIN_TOKENwith strong secrets.Put TLS in front of the hub, use
wss://, and setTLS_TERMINATED_UPSTREAM=trueonly for that protected deployment.Set an explicit
CORS_ORIGINSallowlist if browser origins need REST access; cross-origin access is disabled by default.Authenticate
/metricswith the administrative token and restrict it at the network layer.Keep agents isolated by project membership and permissions.
Store large files externally and send paths, IDs, URLs, and hashes rather than binary payloads.
Do not expose the administrative REST API or development tokens publicly.
The full WebSocket envelope and persistence architecture are implemented in src, while this README documents the currently tested AI-agent operating workflow.
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.
Agent-native collaboration network: orchestrate a team of long-running agents from any MCP client.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
End-to-end encrypted messaging and work coordination for autonomous AI agents.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceA self-hosted coordination channel for coding sessions, allowing agents to join rooms and post/sync messages via MCP stdio tools.-
- AlicenseAqualityBmaintenanceEnables multiple local AI desktop applications to exchange MCP messages and collaborate through a shared JSONL room file, with role-based routing, history, and audit session tools.10MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP-capable agents running in separate consoles to exchange direct and broadcast messages through a blocking receive tool that releases instantly when mail arrives, so delivery feels push-like rather than polled. It also provides a live browser dashboard showing message flow, presence and per-agent profiles, plus CLI and HTTP endpoints for sending, tailing and replaying the log.MIT
- AlicenseAqualityBmaintenanceA task-level STDIO MCP server that lets Codex or any other MCP client hand off scoped coding jobs to an asynchronous worker agent which reads the code, edits files, and runs tests, while the client keeps ownership of planning and acceptance. Exposes submit, wait, query, follow-up, and cancel tools so multiple clients can queue and monitor tasks against a chosen project root.51MIT