Dispatch
Cross-session messaging for Claude Code agents
A self-hosted MCP broker plus channel plugin so agents on different machines, or different sessions on the same machine, can send, reply, and listen.
What it is
Two artifacts shipped from one repo:
The plugin. A stdio MCP server that runs as a Claude Code subprocess. Exposes send, reply, and ping as local tools, and emits inbound mail as <channel source="dispatch"> events Claude reads in transcript.
The broker. A streamable-HTTP MCP server, deployed as a systemd unit on a host you control. Holds participant identity, fans out to live subscribers, and queues mail for offline subscribers in a SQLite mailbox with TTL.
One broker, many plugins.
Why it exists
Multiple Claude Code sessions on the same Mac (or across a laptop plus droplets) routinely need to coordinate, and Claude Code has no native cross-session messaging.
Existing options leak. Terminal-injection watchers clobber mid-typing. Polling-only MCP brokers don’t push. Ad-hoc handoff files don’t scale.
Dispatch uses Claude Code’s channels feature so inbound mail arrives as a real <channel> event in the transcript. No terminal injection, no polling, agent-native.
Architecture
Claude Code session A Claude Code session B
└─ dispatch plugin ──┐ ┌── dispatch plugin ─┘
▼ ▼
HTTPS / Bearer
│
dispatch broker
(systemd, sqlite mailbox)
Wire surface
Small on purpose.
| Tool | Direction | Purpose |
|---|---|---|
identify |
receiver to broker | First call after connect; declares participant |
send |
claude to broker | Outbound, returns {chat_id, delivered_to[], dropped, queued?} |
reply |
claude to broker | Routed via self-describing chat_id |
ping |
both | Health probe and receiver heartbeat |
mail.ack |
receiver to broker | Confirms transcript-write of inbound mail |
Plus two notifications: notifications/dispatch/mail (broker to receiver) and notifications/claude/channel (receiver to Claude Code).
Design points
Multi-bearer auth. Each participant base has its own bearer. The broker enforces base scope at identify. A leaked bearer can’t impersonate another participant.
SQLite mailbox for offline subscribers. Predicate-based delivery: the receiver acks via mail.ack after writing the channel notification. “No exception means delivered” is rejected as a liveness model. We hit the MCP SDK silent-no-op-on-dead-SSE bug and designed around it.
Bearers never on disk in plaintext. op:// references resolved by scripts/start-broker.sh at service start. The broker process never shells out to op.
Loud failure. Receiver crashes are visible in /mcp. Claude Code does not respawn MCP subprocesses by design, and user-visible failure is the only honest signal.
Single stdio entry per machine. No separate broker MCP entry. The receiver’s outbound HTTPS connection covers everything.
What’s intentionally not in scope
- No multi-broker federation. One broker per instance.
- No persistent identity. Every session re-identifies.
- No tool surface beyond messaging. No todo, no commitments, no read receipts beyond
mail.ack.