claude-peers

claude-peers

Lets Claude Code instances discover and message each other across sessions, with reliable delivery via hooks instead of experimental channels.

Category
Visit Server

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 on PostToolUse, Stop, UserPromptSubmit, and SessionStart in 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 queued in 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_message returns a message_id; message_status tells you queued or delivered (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 via nohup) by the first MCP server; self-healed by heartbeats if it dies. Old v1 /poll-messages clients 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 (additionalContext injection, or decision: block on Stop).

Details that bit us (so they're handled)

  • process.kill(pid, 0) throwing EPERM means the process exists (e.g. sandboxed callers) — only ESRCH means dead. Treating any throw as "dead" wipes live peers.
  • lsof -ti :7899 lists clients of the port too, not just the listener. Killing the broker must use -sTCP:LISTEN or 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.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured