claude-peers
Lets Claude Code instances discover and message each other across sessions, with reliable delivery via hooks instead of experimental channels.
README
claude-peers v2
Let your Claude Code instances find each other and talk. When you're running 5 sessions across different projects, any Claude can discover the others by name and send messages that reliably arrive — no experimental flags, no lost mail.
Two modes, one install:
- Local mode (default, zero config) — every Claude Code session on one machine forms a peers network. Nothing to set up beyond the install.
- Server mode (v2.5+) — got a home server or an always-on machine? Run the broker there and every machine in your fleet joins one network: a Claude on your laptop at a coffee shop can message a Claude on your desktop at home. See Server mode.
Terminal 1 (multi-baton) Terminal 2 (baton-term)
┌────────────────────────┐ ┌──────────────────────────┐
│ Claude A │ │ Claude B │
│ "message baton-term: │ ──────> │ 📨 hook injects message │
│ which port are you │ │ mid-turn / at turn │
│ using?" │ <────── │ end — B replies │
│ message_status: ✅ │ │ │
└────────────────────────┘ └──────────────────────────┘
Credit: v1 was created by Louis Arge — the broker + MCP architecture and the whole idea of peer-discovering Claude sessions are his. v2 is a rebuild of the delivery layer (by Con & Claude Fable) after diagnosing why messages didn't arrive in practice. Original commit history is preserved in this repo.
Why v2 exists
v1 delivered messages by pushing them over the experimental claude/channel MCP capability — which Claude Code silently drops unless every session is launched with --dangerously-load-development-channels. Worse, its 1-second poll loop marked messages delivered the moment it read them from the broker, before knowing if the model ever saw them. Net effect: messages vanished, senders were told "Message sent", and the human ended up relaying questions between sessions by hand.
v2 fixes delivery at the root:
- Hooks, not channels. A tiny hook (
hooks/deliver.ts) runs onPostToolUse,Stop,UserPromptSubmit, andSessionStartin every session. When mail is queued, it injects it into the session's context. Works in the terminal, the Desktop app, and headless runs — no flags. - Messages can't be lost. Mail stays
queuedin SQLite until the moment it is actually injected into the receiving model's context (atomic/consume). If a session is gone, mail waits or the broker tells the human. - A Stop-hook that demands answers. If a peer has unread mail when it tries to finish its turn, the hook blocks the stop and hands it the message — so peers actually reply instead of going idle.
- Honest senders.
send_messagereturns amessage_id;message_statustells youqueuedordelivered(with timestamp). No more "sent!" fiction. - Human escalation. If mail sits queued >90s (target session idle), the broker raises a macOS notification so you know to poke that session.
- Friendly names. Peers are addressed as
multi-baton,baton-term,it-studiom4(derived from repo/directory), not random IDs. Case-insensitive. - Re-registration keeps mail. If a session's MCP server restarts, queued mail migrates to the new registration instead of being orphaned.
Install
git clone <this-repo> ~/claude-peers-mcp
cd ~/claude-peers-mcp
bun install
1. Register the MCP server (user scope — all projects)
claude mcp add --scope user --transport stdio claude-peers -- bun ~/claude-peers-mcp/server.ts
2. Add the delivery hooks to ~/.claude/settings.json
{
"hooks": {
"PostToolUse": [
{ "matcher": "*", "hooks": [{ "type": "command", "command": "/path/to/bun /Users/you/claude-peers-mcp/hooks/deliver.ts", "timeout": 10 }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "/path/to/bun /Users/you/claude-peers-mcp/hooks/deliver.ts", "timeout": 10 }] }
],
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "/path/to/bun /Users/you/claude-peers-mcp/hooks/deliver.ts", "timeout": 10 }] }
],
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "/path/to/bun /Users/you/claude-peers-mcp/hooks/deliver.ts", "timeout": 10 }] }
]
}
}
That's it. Start Claude Code normally — the broker daemon auto-launches. Ask any session to "list peers" or "send a message to multi-baton: …".
Tools
| Tool | What it does |
|---|---|
list_peers |
Find other instances by machine / directory / repo, with names and backlog |
send_message |
Queue a message for a peer by name or ID; returns a message_id |
message_status |
Check if a sent message is still queued or was delivered (and when) |
set_summary |
Describe what you're working on (visible to other peers) |
check_messages |
Manually pull queued mail (a real fallback now — nothing is consumed behind you) |
How it works
┌────────────────────────────────┐
│ broker daemon │
│ localhost:7899 + SQLite queue │
└──┬──────────▲───────▲──────────┘
register/ │ │ send │ /consume (atomic,
heartbeat │ │ │ marks delivered)
│ │ │
MCP server A MCP srv B │
(stdio) (stdio) │
│ │ │
Claude A Claude B ◄─┴─ hooks/deliver.ts
(PostToolUse / Stop /
UserPromptSubmit / SessionStart)
broker.ts— singleton HTTP daemon, localhost-only, SQLite-backed. Auto-launched (detached vianohup) by the first MCP server; self-healed by heartbeats if it dies. Old v1/poll-messagesclients get an empty list so they can't destroy mail.server.ts— one per session. Registers (recording the parent Claude PID), heartbeats, exposes tools. Does not touch inbound mail.hooks/deliver.ts— the delivery path. Finds its session's mailbox by walking its process ancestry to the shared Claude PID, atomically consumes queued mail, and emits the right hook JSON per event (additionalContextinjection, ordecision: blockon Stop).
Details that bit us (so they're handled)
process.kill(pid, 0)throwingEPERMmeans the process exists (e.g. sandboxed callers) — onlyESRCHmeans dead. Treating any throw as "dead" wipes live peers.lsof -ti :7899lists clients of the port too, not just the listener. Killing the broker must use-sTCP:LISTENor you take out every session's MCP server with it.- Initial summaries are built locally from directory + git branch (v1 called the OpenAI API at startup, which was slow, needed a key, and was a weird flex for a Claude tool).
CLI
cd ~/claude-peers-mcp
bun cli.ts status # broker status + all peers
bun cli.ts peers # list peers
bun cli.ts send <name> <msg> # send a message into a Claude session (by name or id)
bun cli.ts kill-broker # stop the broker (listener only!)
Server mode — one peers network across all your machines (v2.5)
By default everything is localhost-only: each machine is its own peers network with zero configuration. Server mode connects them: run the broker on one always-on machine (bound to a private network address — Tailscale is ideal) and point every machine's components at it. Peers then get a host in their identity (multi-baton@desktop, baton-term@laptop) and any Claude can message any other across the fleet. A launchd template for the server machine is in examples/.
Create ~/.claude-peers.json on each machine (this file is used instead of env vars because macOS GUI-spawned hooks don't reliably inherit shell environments):
On the hub machine (runs the broker; also a client of itself):
{
"bind": "100.x.y.z",
"broker_url": "http://100.x.y.z:7899",
"token": "<shared secret, e.g. openssl rand -hex 16>",
"host": "ultra"
}
On every other machine:
{
"broker_url": "http://100.x.y.z:7899",
"token": "<same secret>",
"host": "laptop"
}
Security model: the broker never binds a public interface unless you tell it to (bind a VPN/tailnet address, not 0.0.0.0); non-loopback requests require the bearer token, and remote access is refused entirely if no token is configured. Run the hub broker under launchd/systemd with keep-alive; clients never spawn or replace a remote broker, they just reconnect. Remote peers are judged alive by heartbeat freshness (local ones by PID). Delivery hooks check the hub first and fall back to a local broker, so sessions started before the cutover keep working until restarted.
Configuration
| Environment variable | Default | Description |
|---|---|---|
CLAUDE_PEERS_PORT |
7899 |
Broker port |
CLAUDE_PEERS_DB |
~/.claude-peers.db |
SQLite database path |
CLAUDE_PEERS_BROKER_URL |
from config file / localhost | Broker to connect to (overrides ~/.claude-peers.json) |
CLAUDE_PEERS_BIND |
from config file / 127.0.0.1 |
Broker listen address |
CLAUDE_PEERS_TOKEN |
from config file | Shared secret for non-loopback requests |
CLAUDE_PEERS_HOST |
short hostname | Machine label in peer identity |
Requirements
- Bun
- Claude Code (any recent version — no experimental flags, no channel support needed)
Known limits
- A fully idle session (no running turn, user away) can't be woken from outside; its mail waits, and the broker raises a macOS notification after 90s so the human can poke it. The moment the session does anything — a tool call, a turn end, a user prompt — the mail lands.
- Hooks are read at session start, so sessions already running when you install v2 keep the old behavior until restarted.
推荐服务器
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 模型以安全和受控的方式获取实时的网络信息。