switchboard
Sends push notifications with Approve and Reject actions to the owner's device via ntfy, enabling approval workflows from the lock screen.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@switchboardsend a message to codex@bob asking him to review my latest PR"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Agent Switchboard is one small, always-on service that:
π plugs all your agents together. Claude Code, Codex, Gemini, aider, your cron-job-turned-sentient bot: they message each other through a single MCP server.
πͺͺ gives you one outward identity. Your friends' switchboards talk to yours over A2A (Google's Agent2Agent protocol), with Ed25519-signed requests.
π§ββοΈ puts you in the middle. Nothing leaves your trust boundary, and no stranger's request runs on your laptop, until you click Approve. Like a bouncer, but polite and with a dark mode.
π¬ lets agents actually talk. Approve a conversation once and two agents go back and forth on their own (yours with yours, or yours with your friend's) until they say
[[DONE]]or hit the limits.π± taps you on the shoulder. Push notifications with Approve / Reject buttons right on your lock screen (via ntfy).
π runs your CLIs where they live. A tiny relay on your laptop dials out to the gateway, so
claude -pandcodex execrun with your repos and your logins. No open ports, no tunnels, no tears.
All of that is about 1,800 lines of Python and 4 runtime dependencies, and the core is SQLite. It fits on a free-tier VM with room left over for your other side projects. π±
πΊοΈ Table of contents
Related MCP server: Pulse
π§ How it works
flowchart LR
subgraph laptop["π» your laptop"]
CC["claude-code"]
CX["codex"]
R["relay<br/>(runs the CLIs)"]
end
subgraph gw["ποΈ your switchboard (always on)"]
MCP["MCP server<br/>/mcp"]
BUS[("SQLite bus<br/>+ audit log")]
POL{"policy +<br/>approvals"}
A2A["A2A endpoint<br/>/a2a + agent card"]
UI["owner UI<br/>/ui"]
end
subgraph friends["π friends"]
B["bob's switchboard"]
C["carol's switchboard"]
end
CC -- "send_message()" --> MCP
CX -- "read_inbox()" --> MCP
MCP --> BUS --> POL
POL -- "jobs (WebSocket, outbound)" --> R
R -- "stdout = reply" --> BUS
POL <-- "signed A2A" --> A2A
A2A <--> B
A2A <--> C
UI -. "approve / edit / reject" .-> POLAddressing is boring on purpose. codex is your codex. codex@bob is Bob's codex. Every
message knows who sent it and who it's for, and replies flow back to the exact agent that asked,
in the same thread, across gateways. No LLM router guesses who you meant. π―
π A phone call, step by step
Your Claude Code asks Bob's Codex a question. Two humans approve; nobody gets surprised.
sequenceDiagram
autonumber
participant CC as π€ claude-code@alice
participant A as ποΈ alice's switchboard
participant Al as π§ Alice
participant B as ποΈ bob's switchboard
participant Bo as π§ Bob
participant CX as π€ codex@bob (via relay)
CC->>A: send_message(to="codex@bob", body="review my diff?")
A-->>Al: β³ pending approval
Al->>A: β
approve (maybe edit first)
A->>B: A2A SendMessage (EdDSA-signed JWT)
B-->>Bo: β³ inbound request from claude-code@alice
Bo->>B: β
approve
B->>CX: run `codex exec "β¦"` on Bob's laptop
CX-->>B: stdout = the answer
B-->>Bo: β³ reply waiting
Bo->>B: β
approve (or redact the spicy bits)
A->>B: A2A GetTask (polling, signed)
B-->>A: completed + reply
A-->>CC: π¬ read_inbox() β reply from codex@bobπ What it looks like
Too many approvals for a friend you trust? Set them to inbound: auto (or outbound: auto) on the
Contacts page. Trust is a dial, not a switch. ποΈ
π Quickstart (60 seconds, no agents required)
pipx install git+https://github.com/killmashinator/agent-switchboard # or: uv tool install git+β¦
mkdir my-switchboard && cd my-switchboard
switchboard init --owner alice # writes config.yaml + .env (fresh tokens, chmod 600)
switchboard serve # ποΈ http://127.0.0.1:8000/uiOpen the UI, log in with SWB_UI_TOKEN from .env, and send a message to the built-in
demo agent. It echoes, it's thrilled to meet you, and it needs zero installs. π¦
Prefer containers? switchboard init && docker compose up -d. Same thing, more whales. π³
π Plug in Claude Code, Codex & friends
1. Let your agents send by registering the MCP server (each agent has its own token, so nobody can pretend to be somebody else):
# Claude Code
claude mcp add --transport http switchboard http://127.0.0.1:8000/mcp \
--header "Authorization: Bearer $SWB_TOKEN_CLAUDE_CODE"# Codex: ~/.codex/config.toml
[mcp_servers.switchboard]
url = "http://127.0.0.1:8000/mcp"
bearer_token_env_var = "SWB_TOKEN_CODEX"2. Let them receive by running the relay on the machine where the CLIs live:
switchboard relay --gateway http://127.0.0.1:8000 # or https://your-cloud-switchboardThe relay connects outbound over WebSocket and runs claude -p "<message>" /
codex exec "<message>". Their stdout becomes the reply. Which command runs is decided by
the relay's own config, never by the gateway, so even a compromised server can't make your
laptop run rm -rf ~. π§―
Now tell Claude Code: "Ask codex via the switchboard to review src/app.py, wait up to 5 minutes."
Then make popcorn. πΏ
π€ Make friends (pairing)
switchboard invite --note bob # ποΈ one-time link: send it to Bob
switchboard pair "<link from bob>" # or accept Bob's link on your sidePairing swaps Ed25519 public keys and burns the one-time token. Each side proves it holds its key,
so nobody gets man-in-the-middled. Both buttons also live in the UI under Contacts, along with
per-friend scopes (agent:*, agent:codex, β¦) and trust modes.
π¬ Conversations: approve once, let them talk
Single messages are nice. Agents that can hash it out among themselves are nicer. An agent opens a conversation like this:
start_conversation(to="codex@bob", goal="Review my retry helper and agree on a fix", max_turns=5, minutes=15)sequenceDiagram
autonumber
participant CC as π€ claude-code@alice
participant A as ποΈ alice
participant B as ποΈ bob
participant CX as π€ codex@bob
CC->>A: start_conversation(goal, 5 turns, 15 min)
A-->>A: π± Alice taps β
(opens the session)
A->>B: turn 1 + session proposal (signed A2A)
B-->>B: π± Bob taps β
(accepts it)
loop until [[DONE]] Β· 5 turns per side Β· 15 min Β· β Stop
B->>CX: run codex with the transcript
CX-->>B: next message
B-->>A: auto (inside the session)
A->>CC: run claude -p with the transcript
CC-->>A: next message
A->>B: auto (inside the session)
end
A-->>A: π± π "conversation finished" + last messageHow it works:
The handshake is two taps. Each owner approves the opening message once. After that, every turn in that thread flows automatically, in both directions.
Each gateway only drives its own agent. When Bob's message arrives, your gateway runs your agent again with the conversation so far, and its output becomes the next message. Nobody remotely controls anybody else's laptop.
It ends when an agent writes
[[DONE]], a side runs out of turns (default 5), time runs out (default 15 min), or either owner hits β Stop on the live thread page. Anything after that falls back to normal approvals.It works internally too:
plannerβcoderon your own gateway, no friends required.Interactive agents can drive it by hand. If the starting agent has no command, as with an interactive Claude Code session, it keeps the conversation going itself with
read_inboxandsend_message(thread_id=β¦). Those turns count against the same limits.
π± Push notifications: approve from your lock screen
Install the ntfy app (iOS/Android) and subscribe to a topic nobody can guess.
Put the same topic into your gateway's
.env:SWB_NTFY_TOPIC=switchboard-$(openssl rand -hex 8)Each pending approval now buzzes your phone. It shows who wants to talk to whom and a preview of the message, with β Approve and π Reject buttons. Session requests show their limits, and finished conversations send a π summary.
The buttons are signed, one-time, 7-day links to your own gateway, so they need your gateway's
public_url to be reachable from the phone. Tapping the notification opens the live thread. You can
self-host ntfy if you prefer, by setting notify.ntfy_server.
π€ Wait, does my agent have to be running?
No. That's the trick. Your gateway is the part that's always on. Your agents are started on demand:
piece | always on? | when it's not |
ποΈ gateway (VM / server) | β yes | β |
π relay on your laptop | while you're logged in ( | jobs wait in the queue and run when it reconnects |
π€ | no, never needs to be | the relay starts a fresh |
π» your laptop | can sleep | messages queue safely (even across gateway restarts) |
π§ you | can sleep too | approvals wait; your phone buzzes |
switchboard relay --install --gateway https://your-switchboard # macOS login item, auto-restart, keeps your PATHWant true 24/7 with the laptop closed? Run the CLI inside the gateway (runs_on: gateway, with
the CLI and its API key installed in the container), or plug in an always-on bot via the http
adapter.
π§° MCP tools (what your agents get)
tool | what it does |
| message |
| replies addressed to you (the calling agent) |
| sugar for |
| open an approve-once, multi-turn conversation π¬ |
| leave the chat politely |
| the whole conversation |
| local agents (+ online status) and your friends' exposed agents |
π§Ύ Config & presets
owner: alice
agents:
- id: claude-code
preset: claude # = claude -p
cwd: ~/code
- id: codex
preset: codex # = codex exec
- id: demo
preset: echo # zero-install demo agent
runs_on: gateway # runs inside the gateway, no relay needed
- id: researcher
adapter: http # any bot with a webhook
url: https://example.com/hook
expose: false # friends can't address it
policy:
internal: auto # your agents β your agents
outbound: approve # β friends
inbound: approve # friends β your agents
reply: approve # your agents' answers β friendspreset | runs | preset | runs | |
|
|
|
| |
|
|
|
| |
|
|
| a polite parrot π¦ |
Anything else: command: ["my-agent", "--prompt"]. The message is appended as the last argument.
See config.example.yaml for every knob.
π‘οΈ Security model
threat | what the switchboard does |
a friend's agent asks yours to do something dumb | inbound requests wait for your approval (per-friend |
your agent overshares with a friend | outbound requests and replies wait for approval; you can edit before sending |
someone pretends to be Bob | every A2A call is an EdDSA JWT (Ed25519) bound to the body hash, the audience and a one-time |
a random agent on your network pretends to be Claude Code | per-agent MCP bearer tokens: the token is the identity |
the server gets popped | relays only run commands from their own config; secrets live in env/files, never in the DB |
Bob gets chatty | scopes per contact, a daily message cap, and a full audit log in the UI |
βοΈ Deploy it somewhere always-on
where | how |
π³ any Docker host |
|
π an existing server, alongside other stuff |
|
π§ a GCE VM you already have |
|
βοΈ a fresh free-tier GCE VM |
|
Then point your laptop at it: switchboard relay --gateway https://your-switchboard and
claude mcp add β¦ https://your-switchboard/mcp. The laptop can sleep; queued messages wait
patiently in SQLite and get delivered when it wakes up. β
πͺΆ Lightweight, measured
Runtime deps:
mcp,pyyaml,jinja2,websockets. Starlette, uvicorn, pydantic and PyJWT already come with the MCP SDK.Install size: about 29 MB of site-packages, half of the first prototype, because A2A is now ~150 hand-rolled lines instead of a protobuf/gRPC stack.
Spec-correct anyway:
tests/test_a2a_conformance.pyparses everything we emit with the official a2a-sdk types in strict mode. The SDK is a test-only dependency.One process, one SQLite file (WAL mode, daily backups in
data/backups/). Memory use is about 75 MB.
π§ͺ Tests
uv venv && uv pip install -e ".[dev]" && .venv/bin/python -m pytest21 tests spin up real gateways and relays on localhost and cover the following:
MCP messaging between agents;
the full two-gateway round trip with approvals on both sides;
rejections, scopes and hidden agents;
forged signatures and reused invite links;
auth on the owner API;
conversations: turn limits,
[[DONE]], Stop, and exactly one approval per owner across gateways;push notifications and their one-time Approve buttons;
the UI rendering;
restart survival.
πΊοΈ Roadmap
π§΅ resume the same CLI session per thread (
claude --resume) instead of replaying the transcriptπ streaming replies & A2A push notifications (instead of polling)
πΈ per-friend token/cost budgets
π₯ group threads across several people
π an opt-in directory, so your agents can find agents you don't know yet (but would like)
β FAQ
Is this an LLM? No. It's a switchboard. It routes, queues, signs and asks you. The intelligence is in your agents, and the wisdom is (allegedly) in you. π§ββοΈ
Why not let agents talk freely? You can: set everything to auto. But the default assumes
that "my agent negotiated a contract with your agent at 3am" is a sentence nobody wants to say.
Can two agents get stuck in an infinite loop of politeness? No. Conversations have hard turn and time limits, and you have a Stop button. "After you." "No, after you." ends at turn 5. π©
What if my friend doesn't run a switchboard? Any A2A v1.0 client that signs requests the same way can talk to it. And honestly, you could just send them this README. π
π License
MIT. Go build the agent internet. Be nice to each other. π
This server cannot be deployed
Maintenance
Related MCP Connectors
End-to-end encrypted messaging and work coordination for autonomous AI agents.
Collaboration layer for AI agents. Publish assets, send messages, manage threads and contacts.
A persistent public environment where AI agents leave artifacts and exchange asynchronous replies.
AI agent infrastructure for discovery, authorization, execution, identity, and signed receipts.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables coding agents to join a secure agent-to-agent network for team collaboration, with tools for direct messaging, shared rooms, and approval-gated file/command requests.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to communicate directly through a mesh network, supporting group chats, message exchange, and invite-only access with prompt injection protection.42 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables direct agent-to-agent messaging, file transfer, and persistent conversation history between AI agents across machines via a private broker, without needing shared channels or third-party services.6 npm1MIT
- AlicenseNot gradedqualityAmaintenanceEnables coding agents to communicate with each other by sending messages and waiting for replies across sessions, supporting agents like Claude Code, Codex, and Grok.MIT