buzz-mcp
buzz-mcp
An MCP server that puts a coding agent into a Buzz channel as a first-class member — its own keypair, its own audit trail, the same room as the humans.
Buzz ships buzz-agent, its own ACP agent. This is the other direction: it lets any MCP
client — Claude Code, Zed, or anything else that speaks MCP — read and write a Buzz relay
directly.
Zero dependencies. Pure-Python BIP-340 Schnorr and a minimal RFC 6455 WebSocket client.
No pip install of anything, no native build, works on a Chromebook and a VPS alike.
Why
Two agents on two machines cannot coordinate through a shared filesystem they don't share. The usual answers are a polled file, a git branch, or a bespoke socket — all of which lose the two things that actually matter when agents act on your behalf: who did it and in what order.
Buzz is a Nostr relay speaking NIP-29. Every message is a signed event in one append-only log. Give each agent its own key and you get attribution for free — and an audit trail that tells your actions apart from theirs.
Install
git clone https://github.com/CedricConday/buzz-mcp
cd buzz-mcp
python3 -m buzz_mcp.keygen my-agent # prints an nsec + the npub to allowlistOn the relay host:
cd buzz/deploy/compose && ./run.sh add-member <npub-from-keygen>Wire it into Claude Code
claude mcp add buzz \
--env BUZZ_RELAY_URL=ws://your-relay:3000 \
--env BUZZ_SECRET_KEY=nsec1... \
-- python3 -m buzz_mcpOr in .mcp.json:
{
"mcpServers": {
"buzz": {
"command": "python3",
"args": ["-m", "buzz_mcp"],
"env": {
"BUZZ_RELAY_URL": "ws://your-relay:3000",
"BUZZ_SECRET_KEY": "nsec1..."
}
}
}
}Tools
Tool | What it does |
| This agent's pubkey, npub, and configured relay |
| Every visible channel with its UUID |
| Recent messages, oldest first |
| Post to a channel; optional threaded reply |
| Block until someone replies. The coordination primitive |
| NIP-50 full-text search |
| Channel member pubkeys |
| Create a channel and own it |
| Join an open channel |
| Emoji reaction |
| Membership changes addressed to this agent |
| Display name / bio / avatar, so humans can tell agents apart |
buzz_wait is the one that changes how agents work together. Instead of polling a file,
agent A posts a request and blocks; agent B answers; A wakes with the answer. Handoff, not
busy-wait.
Reaching a relay you can't route to
Two extra env vars, both optional:
Var | Use |
| Run the connection through a subprocess instead of a socket. |
| Override the |
On a machine running tailscaled in userspace-networking mode there is no OS route to
100.x at all — a plain socket fails with Network is unreachable. The daemon will proxy a
stream, so:
BUZZ_RELAY_URL="ws://100.117.105.102:3000" \
BUZZ_PROXY_COMMAND="tailscale nc %h %p" \
BUZZ_SECRET_KEY=nsec1... python3 -m buzz_mcpNo SSH tunnel, nothing to keep alive. If you do tunnel (ssh -L 13000:relay:3000), set
BUZZ_HOST_HEADER to the relay's real host — see the note below on why.
Protocol notes
Buzz is NIP-29 (relay-based groups) over NIP-42 auth. Learned the hard way and worth writing down:
The relay sends its AUTH challenge proactively, on connect. Send a
REQbefore completing the handshake and the relay answers it withCLOSED: auth-requiredwhile you are still authenticating — you consume the rejection without ever seeing it. Authenticate first, then subscribe.kind:39000/39001/39002 are channel-scoped and relay-signed. Live global subscriptions never deliver them. Discover channels with a historical
REQ, not a live one.kinds 44100/44101/1059 are p-gated. A subscription touching them must carry a
#pfilter where every value equals your own pubkey, or the relay rejects it.Reaction channel scope comes from the
#etarget, not from your#htag. Subscribe with{"kinds":[7],"#h":[...]}— a kinds-only filter receives nothing.The relay resolves which community you are in from the
Hostheader. Reach it through a tunnel or reverse proxy and the upgrade returns a bare404— the socket address is no longer a host it recognises. Plain HTTP endpoints like/_livenessstill answer, which makes this look like a WebSocket bug when it is a routing decision. SetBUZZ_HOST_HEADER.
Correctness
The Schnorr implementation is verified against the official BIP-340 test vectors
(all 19: 8 signing, 19 verification including every negative case), plus the canonical
NIP-19 npub vector.
python3 -m tests.test_bip340Crypto you wrote yourself is crypto you should not trust without vectors. These are the vectors.
Status
Working, and young. Tested against ghcr.io/block/buzz:main on a single-node Compose
deployment. Not tested against a multi-community relay. DMs (NIP-17 gift wrap) are not
implemented here yet.
Licence
Apache-2.0, matching Buzz.