Skip to main content
Glama

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.

Related MCP server: claude-mesh

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.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/conradium-gif/claude-peers-v2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server