Agent Orchestrator
A shared MCP hub that enables multiple coding-agent CLIs to register, discover, and communicate asynchronously via a standalone daemon. It provides MCP tools for agent registration, session management, non-blocking messaging, and cursor-based event reading.
README
Agent Orchestrator
A shared MCP (Model Context Protocol) hub that lets multiple coding-agent CLIs register themselves and talk to each other — bidirectional, non-blocking, no fixed ports to remember.
Ships with adapters for OpenCode and for Claude Code itself (via a companion claude-channel plugin, since Claude Code has no server of its own); the adapter interface is designed so support for other agent CLIs (Codex, Pi, Antigravity, ...) can be added without touching the core.
Why This Exists
Bridges that spawn a subprocess, wait for it to finish, and collect the result don't let two agents actually converse — one side always blocks. Agent Orchestrator instead runs as a small standalone daemon that both agents connect to as MCP clients:
- Either side sends a message at any time with
agent_send. Delivery is live for every adapter — the target starts processing it in its active turn immediately, the same way a human's message would. Nothing is queued. - The reply comes back the same way: as the other agent's own
agent_sendcall targeting you. There is no polling step in the middle —agent_readexists separately, for pulling a target's own past history (e.g. what it did during a long async turn), not for noticing new inbound messages. - Registration is explicit and per-instance: you register only the specific agent window you want exposed, so a "working" window you never register stays completely invisible to the orchestrator.
- Every registration also names a
session— an orchestrator-level group, agreed out of band with whoever you want to reach. Agents in different sessions can't see or address each other even if both are registered, which is what stops two unrelated conversations from crossing wires (a real failure mode this project hit and fixed — see Troubleshooting).
Architecture
Claude Code ── stdio ──► claude-channel plugin ──┐
(this session) (local :dynamic, REST+SSE)│
├── HTTP (streamable MCP) ──► Agent Orchestrator daemon
opencode window A (registered) ────────────────────┘ :8765/mcp (registry + per-agent adapter)
│
opencode window B ───────────────────────────────────────────── REST + SSE (per adapter)
(unregistered, never touched) ▼
opencode A's own
HTTP API (:port), or
claude-channel's (:dynamic)
- The daemon (
agent-orchestrator) is a long-running process exposing one MCP endpoint over Streamable HTTP. Claude Code and every opencode window connect to it as independent clients (stateful MCP sessions,mcp-session-idheader). - The registry tracks every registered agent (
{agentId, agentType, session, host, port, label}) and, per agent, holds one persistent event stream plus a per-backend-session ring buffer (last 500 events, monotonic cursor — reading history never silently stalls once one passes the cap). Every lookup is scoped bysession: an agentId that exists but belongs to a different session is treated as not found. - Adapters translate the registry's generic calls into a specific backend's protocol. The
opencodeadapter talks to opencode's own REST + SSE API (/session,/session/:id/prompt_async,/event). Theclaudeadapter talks the same REST + SSE shape, but to a localclaude-channelcompanion process (see below), not to Claude Code directly.
Reaching Claude Code
Claude Code, unlike opencode, has no server of its own to adapt to — so it can't be registered directly. claude-channel (src/channel-plugin/claude-channel.ts) closes that gap: it's a Claude Code "channel" plugin (research preview), spawned by Claude Code itself as a stdio MCP subprocess when the session starts with --channels/--dangerously-load-development-channels. It exposes a small local REST+SSE surface that mirrors the opencode adapter's shape, so the claude adapter can register and talk to it exactly like any other agent — inbound text gets pushed straight into the live session via the claude/channel notification (real push, not polling), and Claude's replies (via the plugin's reply tool) come back out over SSE.
The port is dynamic (OS-assigned, 0 by default) specifically so multiple Claude Code windows can each run their own instance without colliding — there's no fixed address to hardcode. Since there's nothing external to detect a not-yet-running process's port from, the instance reports its own to the model that owns it: baked into that session's connect-time system prompt, and re-checkable anytime via the plugin's whoami tool. Every live instance's {pid, port, host, startedAt} is also on disk at runtime/claude-channel-instances/<pid>.json, garbage-collected on each new startup (a file whose pid is no longer alive is a previous instance that crashed or was force-killed without cleanup, not a conflict — a dynamic port can't collide by construction). See skills/agent-orchestrator/references/claude-channel.md for setup.
With more than one claude-type agent registered in the same session (one per window), list_agents won't tell them apart beyond the label each was given at registration — always check it before assuming an agentId is yours. Windows on genuinely unrelated tasks should use different session names instead, so they can't see each other at all.
Prerequisites
- Node.js >= 18.0.0
- OpenCode CLI installed and authenticated (
npm install -g opencode-ai, thenopencode login)
Installation
npm install
npm run build
Running the daemon
npm start
# Agent Orchestrator listening on http://127.0.0.1:8765/mcp
It needs to be running before either Claude Code or opencode connects to it. Run it in its own terminal, or set it up as a background service — there's no auto-start built in on purpose, since it's a shared resource independent of any one agent's lifecycle.
Environment variables
| Variable | Default | Description |
|---|---|---|
AGENT_ORCHESTRATOR_HOST |
127.0.0.1 |
Host the MCP endpoint binds to |
AGENT_ORCHESTRATOR_PORT |
8765 |
Port the MCP endpoint listens on |
Connecting Claude Code
claude mcp add --transport http agent-orchestrator -s user http://127.0.0.1:8765/mcp
Connecting opencode
opencode mcp add
# follow the prompt: transport = http, url = http://127.0.0.1:8765/mcp
Registering an agent instance
This is the deliberate, per-instance step that keeps unregistered windows invisible to the orchestrator — there's no automatic/background registration, no plugin hook, no polling for new instances. You decide, per window, when it joins.
The full operational guide (how to detect your own port safely, the complete tool reference, a worked example) lives in skills/agent-orchestrator/SKILL.md — written to be followed by any agent CLI, not just Claude Code. AGENTS.md at the repo root points opencode (and other AGENTS.md-aware CLIs) at it automatically.
MCP Tools
Every tool below takes a mandatory session — an orchestrator-level group name, not a backend chat id (that's backendSessionId, a separate thing). Only agents sharing the same session can see or address each other; agree on a name with whoever you want to reach before registering.
register_agent
Register a running agent instance. Input: { agentType, session, port, host?, label? }. Output: the stored AgentRecord. The first registration with a new session name creates it.
unregister_agent
Input: { agentId, session }. Stops its event stream and drops its buffers. Returns false if that agentId isn't registered in that session — even if it exists in a different one.
list_agents
Input: { session }. Returns every agent registered in that session — nothing from any other.
agent_create_backend_session
Creates a chat thread on the target's own backend (e.g. a fresh opencode session) — not related to the orchestrator session beyond needing it to find the agent. Input: { agentId, session, title?, agent? }. Output: { backendSessionId, title }.
agent_list_backend_sessions
Input: { agentId, session }. Returns every chat thread known to that agent's backend (not just ones created through the orchestrator — opencode's session store is shared across all its own instances).
agent_send
Input: { agentId, session, backendSessionId, text, agent?, system? }. Delivers live into the target's active turn for every adapter — this call itself returns as soon as the backend accepts it ({ accepted: true }), not once the target replies, but the reply itself also arrives live, as the target's own agent_send call back to you. Don't follow it with an agent_read loop; there's nothing there to wait for. Fails if agentId isn't registered in session.
agent_read
Input: { agentId, session, backendSessionId, sinceCursor? }. Output: { events, cursor, hasMore, droppedEvents? }. Retrieves a target's past event history — useful for checking what an agent did during a long async turn, not for receiving new messages (those arrive live via agent_send, see above). droppedEvents appears if your cursor pointed at history already evicted from the 500-event ring buffer.
agent_status
Input: { agentId, session }. Returns the agent's record plus a live reachable health check.
reply (claude-channel only, not the orchestrator daemon)
A separate MCP server — the local claude-channel process, not agent-orchestrator itself. Input: { text }. Writes to that channel's own local transcript, for a human directly watching it (e.g. via /events). It does not reach another orchestrator agent; to answer whoever messaged Claude Code, call agent_send targeting their agentId, same as any other agent.
Adding a new agent type
Implement the AgentAdapter interface (src/adapters/types.ts) for the new backend and register it in src/adapters/index.ts. The registry, event buffering, cursor logic, and every MCP tool are already generic — they don't know or care which adapter they're talking to.
Troubleshooting
Port already in use — set AGENT_ORCHESTRATOR_PORT to something else and re-register both clients against the new URL.
register_agent fails with "Could not verify a reachable agent" — the port doesn't respond, or it's not actually opencode. Re-run the port-detection step from inside the target window; ports change every time opencode restarts.
Sent a message but the other agent never seemed to receive it — check you called agent_send, not agent_read (there's nothing to receive by polling) or, on the Claude Code side, the channel's own reply tool (that only writes to its local transcript, never to another agent).
Task finished but no completion report came back — reporting back isn't automatic. The receiving agent has no guarantee it consulted the skill before acting, so its default is to answer wherever it normally would (its own console/human), not through the channel. Ask for the report explicitly in the message: "reply via agent_send to <your agentId> when done."
A reply landed on the wrong agent, or agent_send/list_agents claims an agentId doesn't exist even though it was just registered — almost always a session mismatch: either the two sides agreed on different names, or one side is reusing a memorized agentId from an earlier, unrelated session instead of re-checking list_agents for the current one. This is exactly the isolation working as intended (see Why This Exists) — re-confirm both sides are using the same session string, not just guessing.
Logs — runtime/orchestrator.log (created next to wherever you run the daemon from).
Development
npm run typecheck
npm run lint
npm test
npm run build
Tests are network-free: adapter SSE parsing and the registry's cursor/ring-buffer logic are both tested against fakes, no real opencode instance required.
License
MIT. See LICENSE for details.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。