Agent MCP Server
by mann13072
README.md
# WebSocket Agent Hub
> **Author:** [Mann D. Shah](https://github.com/mann13072) · Financial Controller & Systems Architect
> **Portfolio & Related Repositories:** [Luna Reminder](https://github.com/mann13072/luna-reminder) · [MCP Excel Scraper](https://github.com/mann13072/mcp-excel-scraper) · [Full Portfolio](https://github.com/mann13072)
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:
1. Confirm which project directory the user wants the agents to work in.
2. Confirm port `8080` is either free or already serving this hub.
3. Use unique agent IDs for every simultaneously active pair.
4. Keep both agents in the same hub project and set each agent's `AGENT_PEER_ID` to the other agent.
5. Use the same `JWT_SECRET` to start the hub and issue agent tokens.
6. Never start two clients with the same agent ID. A newer connection replaces the older one.
7. Put the complete task, relevant paths, constraints, and acceptance criteria in the delegation `context`; chat history is not automatically shared between models.
8. For a clean verification, run one bounded delegation before starting long work.
Current local paths:
| Purpose | Path |
| --- | --- |
| Hub repository | `C:\Users\13072\Desktop\Python\websoket Agent communication` |
| Supply-chain launcher | `C:\Users\13072\Desktop\Python\Supply-chain-SIMulator-main\open-multi-agent-pair.bat` |
| Cash-flow project | `C:\Users\13072\Desktop\Python\project-cash-flow-risk-simulator` |
## How the components connect
```mermaid
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.mjs` speaks MCP over standard input/output to the AI client.
- The adapter uses `agent-instance.mjs` to speak the hub's authenticated protocol over `ws://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:
```powershell
$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 $project
```
That `.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:
```powershell
& "C:\path\to\your-project\open-agent-pair.bat" --diagnose
```
Then double-click it and enter a pair name, or provide one directly:
```powershell
& "C:\path\to\your-project\open-agent-pair.bat" inventory_review
```
To open a separate hub console with live logs for a newly started pair:
```powershell
& "C:\path\to\your-project\open-agent-pair.bat" --show-hub inventory_review
```
The 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:
1. Derives a stable project ID from the project folder name and a hash of its absolute path.
2. Reuses port `8080` only after both health and administrative-authentication probes pass.
3. Otherwise starts the loopback-only in-memory hub, hidden by default or visible with `--show-hub`.
4. Idempotently creates the project, agents, memberships, and fresh one-hour JWTs.
5. Opens two isolated Codex terminals and waits until both MCP agents are online.
6. Cleans up terminals and a launcher-owned hub if any startup stage fails.
7. 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:
```text
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 |
| --- | --- |
| `delegate_task` | Send a bounded task and receive routing IDs; use `wait_for_task_result` for a late result. |
| `wait_for_task_result` | Resume waiting for a previously delegated result. |
| `cancel_task` | Request authoritative cooperative cancellation of an active task. |
| `wait_for_task_cancellation` | Resume waiting for cancellation to be surfaced or confirmed. |
| `wait_for_task` | Wait for delegated work, an update/cancellation, or a peer's `send_message`. |
| `send_task_result` | Return completed, blocked, or failed work using the exact routing IDs. |
| `send_update` | Send a correction or added instruction for an active task. |
| `wait_for_update_read` | Resume waiting for the peer to read an update. |
| `check_for_updates` | Surface unread updates or cancellation at a safe worker checkpoint. |
| `confirm_task_cancelled` | Confirm that the worker stopped starting new work. |
| `send_message` | Send a non-blocking informational message to the peer. |
| `wait_for_message` | 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_task` is interrupted immediately by a peer's `send_message` and gets back `status: "message_received"`. Handle it and call `wait_for_task` again right away to resume listening for both tasks and further messages.
- Any other queued peer message is attached as a `pendingPeerMessages` array 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 next `delegate_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 the `pendingPeerMessages` drain 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.mjs` writes it to stderr and (on Windows, unless `AGENT_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:
1. Work on the main task.
2. Call `delegate_task` only for a useful bounded subtask, parallel investigation, or independent review.
3. Preserve its `correlationId` and `requestMessageId`; use `send_update` for mid-task corrections and `wait_for_task_result` for the result.
4. Use `cancel_task` when the user cancels, then `wait_for_task_cancellation` if the peer has not surfaced it yet.
5. Supply enough context for the other model to work without the coordinator's chat history.
6. Wait for and verify the returned evidence.
7. Incorporate the result into the final answer.
8. Before ending the turn, check whether the last tool result carried a `pendingPeerMessages` field; if so, read those messages, since they are the only opportunity to see a worker's post-task `send_message` before the turn ends.
9. 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 the worker sent while idle. A `timeout` result there is normal and means nothing arrived; do not mention it to the user unless it returns an actual message.
Worker behavior:
1. Call `wait_for_task({"timeoutSeconds":900})`.
2. If it returns `task_received`, continue in the same turn and execute the task immediately.
3. During the task, call `check_for_updates` at 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.
4. If it returns an update, apply it before continuing. If it returns `task_cancellation_requested`, stop starting new work and call `confirm_task_cancelled` with the returned `cancelMessageId`.
5. Call `send_task_result` with the exact `requestMessageId`, `correlationId`, and `toAgentId` returned by `wait_for_task`. If it returns `task_result_deferred`, apply the update and retry with the same routing IDs.
6. Call `wait_for_task` again after the task is complete or cancelled.
7. If a wait returns `timeout`, simply call it again when continued; the WebSocket connection itself is not limited to 900 seconds.
8. If `wait_for_task` returns `message_received` instead of a task, that is a non-task message from the coordinator, not work; handle it and call `wait_for_task` again immediately. Also check any tool result for a `pendingPeerMessages` field 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:
```powershell
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 start
```
Check it from another terminal:
```powershell
Invoke-RestMethod http://127.0.0.1:8080/health
```
Expected result:
```json
{"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:
```text
POST /projects
POST /agents (once for each identity)
POST /projects/:projectId/agents (once for each membership)
```
Administrative requests require:
```http
x-admin-token: development-admin-token
```
For local testing, issue two tokens after the TypeScript hub has been built:
```powershell
$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:
```json
{
"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:
```powershell
claude mcp list
```
You 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:
```powershell
$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"
claude
```
Give it this role instruction:
```text
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:
```powershell
$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"
claude
```
Give it this role instruction:
```text
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:
```text
Agent A: mixed_pair1_codex
Agent B: mixed_pair1_claude
Project: project_mixed_demo
```
Provision both identities and memberships as described above, then issue their tokens:
```powershell
$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.
```toml
[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 = 920
```
Open a terminal in the target project and launch Codex with the coordinator identity:
```powershell
$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:
```text
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:
```powershell
$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"
claude
```
Approve the project MCP server if prompted, confirm it with `/mcp`, and give Claude this worker instruction:
```text
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:
```text
User -> Codex A -> delegate_task -> hub -> Claude B
Claude B -> performs work -> send_task_result -> hub -> Codex A
Codex A -> verifies and answers user
```
While 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:
1. Provision its agent ID and project membership through the REST API.
2. Issue a JWT using the same secret as the running hub.
3. Configure a local stdio MCP server with command `node` and argument `agent-mcp-server.mjs`.
4. Supply all required environment variables listed below.
5. Instruct the client to use `wait_for_task` as a worker, call `check_for_updates` at safe checkpoints, or use `delegate_task` as a coordinator.
Required adapter environment:
| Variable | Meaning |
| --- | --- |
| `AGENT_HUB_URL` | Hub WebSocket URL. |
| `AGENT_TOKEN` | JWT for this exact identity. |
| `AGENT_ID` | Unique registered agent ID. |
| `AGENT_PROJECT_ID` | Shared project ID. |
| `AGENT_PEER_ID` | The other member of this pair. |
Optional environment:
| Variable | Default |
| --- | --- |
| `AGENT_NAME` | Agent ID/client default |
| `AGENT_TYPE` | `codex` |
| `AGENT_MCP_REQUEST_TIMEOUT_MS` | `900000` in the launchers |
| `AGENT_RECONNECT_MAX_MS` | `30000` |
| `AGENT_HUB_MAILBOX_DIR` | `.mailbox/` next to `agent-mcp-server.mjs` |
| `AGENT_HUB_NOTIFY` | `true` (set to `false` to disable the Windows toast) |
## 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_task` again.
- 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:
1. Repository context: files such as this README and `AGENTS.md`; future agents can read these after a restart.
2. Model conversation context: private to each Codex or Claude session; it is not shared automatically.
3. Hub message context: the `task` and `context` fields 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:
```powershell
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:
```powershell
npm install
npm run build
```
Validate the project:
```powershell
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 --diagnose
```
Health and diagnostics:
```powershell
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 |
| --- | --- |
| `src/server.ts` | Starts the Fastify/WebSocket hub. |
| `src/app.ts` | REST endpoints and WebSocket registration. |
| `src/sdk/agent-client.ts` | TypeScript client SDK. |
| `agent-instance.mjs` | Portable WebSocket client and Codex pair/window launcher. |
| `agent-mcp-server.mjs` | Stdio MCP-to-WebSocket adapter used by AI clients. |
| `create-demo-agent-tokens.mjs` | Issues two local development JWTs. |
| `ensure-local-secrets.mjs` | Creates the ACL-restricted local launcher secrets file. |
| `launch-project-agent-pair.ps1` | Canonical validation, provisioning, launch, and cleanup implementation. |
| `open-codex-pair.bat` | Original seeded Codex demo launcher. |
| `open-multi-agent-pair.bat` | Thin wrapper that can be copied as the only launcher file in any project. |
| `prisma/schema.prisma` | Persistent data model. |
| `.env.example` | Hub configuration template. |
## Upstream client documentation
- [OpenAI: Model Context Protocol in Codex](https://learn.chatgpt.com/docs/extend/mcp.md)
- [Anthropic: Connect Claude Code to tools via MCP](https://code.claude.com/docs/en/mcp)
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=prisma` with PostgreSQL and Redis.
- Replace `JWT_SECRET` and `ADMIN_TOKEN` with strong secrets.
- Put TLS in front of the hub, use `wss://`, and set `TLS_TERMINATED_UPSTREAM=true` only for that protected deployment.
- Set an explicit `CORS_ORIGINS` allowlist if browser origins need REST access; cross-origin access is disabled by default.
- Authenticate `/metrics` with 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
ActivityMaintained
ResponsivenessNo issues