desktop-agent-bus
desktop-agent-bus lets multiple AI desktop applications on the samemachine exchange MCP messages without Redis, a database, or a hosted service.
Each application starts the same MCP server with its own role name. Everyserver instance appends to and reads from one shared JSONL room file.
Desktop client A Desktop client B Desktop client C
| | |
+------ desktop-agent-bus ------+
|
v
~/.desktop_agent_bus/room.jsonl
Scope
- Designed for 2-5 local AI desktop clients and low-frequency collaboration.
- Uses only the Python standard library at runtime.
- Supports MCP over stdio and local HTTP.
- Routes work to named roles while retaining broadcast compatibility.
- Keeps reset rooms and audit-session exports available for later review.
- Uses POSIX file locks for cross-process write safety.
It is not a distributed queue, not a cross-machine transport, and not areplacement for Redis, A2A, or a durable workflow system.
Requirements
- Python 3.10+
- macOS or Linux for cross-process file locking
- MCP-capable desktop clients that can launch a local command or call a localHTTP endpoint
Quick start
Clone the source and install it locally:
git clone https://github.com/hemajack57-collab/desktop-agent-bus.git cd desktop-agent-bus python3 -m pip install .Choose one shared room path, for example
${HOME}/.desktop_agent_bus/room.jsonl.Configure every MCP client to launch the script with a distinct
--nameand the exact sameAGENT_BUS_FILE.Restart clients, call
bus_who, then callbus_readwithsince: 0.
Example stdio MCP configuration:
{
"mcpServers": {
"desktop-agent-bus": {
"command": "desktop-agent-bus",
"args": [
"--name",
"reviewer"
],
"env": {
"AGENT_BUS_FILE": "/absolute/path/to/room.jsonl"
}
}
}
}
See docs/INTEGRATIONS.md for configuration guidance anddocs/SECURITY.md for local-file safety boundaries.
MCP tools
| Tool | Purpose |
|---|---|
bus_send |
Send a tagged message to specific roles or broadcast. |
bus_read |
Read newer messages; optionally filter by recipient, sender, or tag. |
bus_wait |
Long-poll for a matching message, addressed to the caller by default. |
bus_history |
List archived rooms with metadata, or replay one by its listed name. |
bus_search |
Search active messages by text, sender, or tag; optionally include archives. |
bus_export |
Export the current or most recently closed audit session as Markdown. |
bus_who |
Show live roles and MCP process counts. |
bus_open |
Open an audit session after stating its measurable ceiling. |
bus_close |
Close the audit session and record an outcome. |
bus_reset |
Archive the current room and begin a new conversation. |
Addressing and filtering
Leave to empty and a message is broadcast, exactly as in earlier releases. Setit and only the named roles receive it when they use bus_wait or opt in torecipient filtering with bus_read:
{"content": "run the backtest", "to": ["executor"], "tags": ["task"]}
bus_wait defaults to for_me: true, so each role is woken only by broadcastsand by messages addressed to it. Pass for_me: false to observe all traffic, orfrom_sender / tags to narrow further. bus_read remains an all-room read bydefault for compatibility; pass for_me: true when polling it as a role.
from_sender matches one exact role name. Supplying one or more tags returnsmessages carrying any of those tags. Records written before 1.4 carry no tofield and are always treated as broadcasts.
{"since": 41, "for_me": true, "from_sender": "executor", "tags": ["result"]}
Addressing is routing, not authorization: clients that share the room file canstill call bus_read with for_me: false. Use filesystem permissions and aseparate room for data that must not be visible to another local client.
Review and archives
bus_reset archives a room rather than deleting it, and the archive staysreadable:
bus_history list archives with counts and time spans
bus_history {"archive": "room.20260812-120000-000000.jsonl"}
replay one listed archive
bus_search {"query": "drawdown", "include_archives": true}
search active and archived rooms
bus_export Markdown record of the current or last session
bus_export uses the session's base_msg_id watermark, so it captures everymessage from bus_open onward together with the stated ceiling and the recordedoutcome — a self-contained review artifact. Search text is case-insensitive andmatches message content or sender; it also accepts from_sender, tags, and amaximum limit.
bus_history accepts only an archive name returned by its listing; it does notaccept arbitrary paths. Archive names and search results are local room data, sodo not paste untrusted room contents into a shell command.
Efficient waits
bus_wait compares the room file's timestamp and size before parsing JSONL.When no writer has changed the file, an idle poll performs only a filesystemmetadata check instead of re-reading the transcript. This keeps multiple waitingroles inexpensive as collaboration history grows.
Local HTTP adapter
For a client that connects to MCP through HTTP:
python3 -m desktop_agent_bus \
--http \
--host 127.0.0.1 \
--port 8765 \
--name reviewer
The endpoint is http://127.0.0.1:8765/mcp. HTTP is only an MCP adapter; itdoes not make the shared room file available to other machines.
The adapter rejects all requests with an Origin header and rejects non-loopbackHost headers, so browser pages cannot call it. It also limits request bodies to1 MiB by default. Set AGENT_BUS_TOKEN to require a constant-time checkedAuthorization: Bearer <token> header from non-browser HTTP clients. Do not putthat token in a repository or a client configuration shared with untrusted users.
Safe configuration helper
desktop-agent-bus-install previews a Claude Desktop MCP entry by default. Wheninstalled, it configures the desktop-agent-bus console command; a sourcecheckout falls back to its local script:
desktop-agent-bus-install --name reviewer
It writes a backup and applies the change only with explicit confirmation:
desktop-agent-bus-install --name reviewer --apply
Development
python3 -m unittest discover -s tests -v
python3 -m pip wheel --no-deps .
License
MIT