{
  "markdown": "# bus-mcp\n\n[![PyPI](https://img.shields.io/pypi/v/bus-mcp)](https://pypi.org/project/bus-mcp/)\n[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-io.github.jaimenbell%2Fbus--mcp-blue)](https://registry.modelcontextprotocol.io)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n[![Tests](https://img.shields.io/badge/tests-100%20%2899%20passing%2C%201%20skipped%29-brightgreen)](#testing)\n[![CI](https://github.com/jaimenbell/bus-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/jaimenbell/bus-mcp/actions/workflows/ci.yml)\n\nAn ergonomic MCP server fronting the self-hosted **AlphaHive coordination\nbus** (`backend/coordination_bus.py` in the `alphahive` repo) -- so a Claude\nagent calls `claim_lane(\"feeds-refactor\", owner=\"session-A\")` instead of\nhand-rolling `curl -X POST .../lanes/feeds-refactor/claim -d '{...}'`. Built\nto the [desktop-mcp](https://github.com/jaimenbell/desktop-mcp)/[github-mcp](https://github.com/jaimenbell/github-mcp)\nstandard (own pyproject, fastmcp server, honest README, real test suite) --\nthis is that exact \"MCP over an HTTP API\" pattern turned on our own\nself-hosted API.\n\n## Quickstart (60 seconds)\n\n```bash\npip install bus-mcp\n```\n\nAdd to your Claude Desktop/Code MCP config:\n\n```json\n{\n  \"mcpServers\": {\n    \"bus-mcp\": {\n      \"command\": \"bus-mcp\"\n    }\n  }\n}\n```\n\nNo console script on PATH? Fall back to `\"command\": \"python\", \"args\": [\"-m\", \"bus_mcp\"]`.\nBy default this talks to a bus at `http://127.0.0.1:8100/api/bus` -- see\n\"Env vars\" below to point it elsewhere.\n\n## What this is / is not\n\nThis fronts a **private, localhost-only, no-auth v1 coordination substrate**\n-- not a public service. The bus itself is a blackboard (append-only\nmessages) + a lane-claim registry (task-queue leases with steal-on-expiry) +\na status rollup for a command-center panel. It **executes nothing\noutward-facing**: `action_flag` on a message is recorded and displayed only,\nnever acted on by the bus. bus-mcp adds zero new capability over what the\nbus already does via `curl` -- it only makes the six routes ergonomic MCP\ntools with typed inputs and typed errors instead of raw HTTP.\n\n## Tools\n\n| Tool | Bus route | Purpose |\n|---|---|---|\n| `post_message` | `POST /api/bus/message` | Append one message to the blackboard (topic, sender, body, action_flag) |\n| `read_messages` | `GET /api/bus/messages` | Recent messages, newest first, optional topic filter |\n| `claim_lane` | `POST /api/bus/lanes/{lane}/claim` | Claim-if-free / steal-if-lease-expired / renew-if-own; 409 if held live by another. Response echoes the effective (post-clamp) `lease_s` granted -- see \"Lease ceiling\" below. |\n| `release_lane` | `POST /api/bus/lanes/{lane}/release` | Free a held lane; 409 if held live by another |\n| `heartbeat_lane` | `POST /api/bus/lanes/{lane}/heartbeat` | Renew the lease; 409 if you don't hold it live. Response echoes the effective `lease_s`, same as claim. |\n| `get_bus_status` | `GET /api/bus/status` | Rollup: active lanes, orphaned claims, recent messages, pending action flags, effective `_meta.max_lease_seconds` ceiling |\n\nNo write-safety *knob* here the way github-mcp has one for real external\nwrites -- every bus route is coordination-only (store/display/claim). As of\ncoordination-bus **v1.1**, the bus MAY optionally require a shared secret on\nits 4 write routes (default off); this client mirrors that with zero new\nconfig surface of its own -- see \"Write-secret auth (v1.1)\" below.\n\n## Typed errors, never a raw crash\n\nEvery tool returns `{\"ok\": true, ...}` on success or `{\"ok\": false, \"error\":\n{...}}` on failure -- never an unhandled exception or stack trace.\n\n- **`bus_unreachable`** -- connection refused, timeout, or DNS failure. Means\n  the AlphaHive backend isn't running, or is running without the bus routes\n  loaded (`backend/coordination_bus.py` mounted on `:8100`).\n- **`bus_api_error`** -- the bus responded with a 4xx/5xx. Carries\n  `status_code` + the bus's own `detail` text -- e.g. a `409` lane-conflict\n  message telling you who holds the lane and for how long.\n\nInternally, `bus_mcp/client.py` raises typed `BusUnreachable` / `BusApiError`\nexceptions; `bus_mcp/routes.py` catches both and normalizes to the dict\nshape above before a tool ever returns. Tests exercise both layers.\n\n## Env vars\n\n| Var | Default | Purpose |\n|---|---|---|\n| `BUS_MCP_BASE_URL` | `http://127.0.0.1:8100/api/bus` | Base URL of the coordination bus |\n| `BUS_MCP_TIMEOUT_S` | `10.0` | Per-request timeout (seconds) |\n| `BUS_MCP_LIVE` | unset | Set to `1` to run the real-network smoke test (see Testing) |\n| `BUS_WRITE_SECRET` | unset | Same var the bus itself reads to arm write-auth (v1.1). When set here, every write tool call sends `X-Bus-Secret: <value>` automatically. Unset = no header sent, matching an unarmed bus byte-for-byte. |\n\n## Write-secret auth (v1.1)\n\nThe coordination bus can optionally gate its 4 write routes (`post_message`,\n`claim_lane`, `release_lane`, `heartbeat_lane`) behind a shared secret header\n(`X-Bus-Secret`), read from `BUS_WRITE_SECRET` on the bus side. This client\nreads the **same env var name** from its own process and, when set,\n`bus_mcp/client.py`'s `post()` attaches the header to every write call --\n`bus_mcp/routes.py` and every tool caller stay unaware of arming state\nentirely. `client.get()` never attaches the header (GET routes are never\ngated bus-side).\n\n**To use with an armed bus:** set `BUS_WRITE_SECRET` to the same value in\nboth the AlphaHive backend's environment and this MCP server's environment\n(e.g. in the config that launches `run_server.py`), then restart both\nprocesses. If the value is missing or wrong, a write tool call returns the\nnormal `{\"ok\": false, \"error\": {\"type\": \"bus_api_error\", \"status_code\": 401,\n...}}` shape -- no special-casing needed, it flows through the same typed\n`BusApiError` path as any other 4xx.\n\n**Unset (default):** no header is sent, identical to talking to a bus that\nhas never been armed -- zero behavior change from pre-v1.1.\n\n## Lease ceiling surfacing (coordination-bus v1.3+)\n\nThe bus supports an operator-configurable ceiling on granted lease durations\n(`BUS_MAX_LEASE_SECONDS`, bus-side): a `claim_lane`/`heartbeat_lane` request\nfor `lease_s=7200` may be silently **clamped** to a shorter effective grant\n(e.g. 3600s) rather than rejected -- see `coordination_bus.README.md`'s\n\"v1.3 - configurable lease ceiling\" section in the alphahive repo for the\nfull server-side story.\n\nThis client surfaces both halves of that contract, additively:\n\n- **`claim_lane` / `heartbeat_lane` responses** include a top-level `lease_s`\n  field on `ok=True` -- the EFFECTIVE (post-clamp) duration actually granted.\n  Always check this rather than assuming the requested `lease_s` was honored\n  in full; a caller that ignores it and heartbeats on its own optimistic\n  schedule risks its lane going stale early.\n- **`get_bus_status`** exposes `_meta.max_lease_seconds` -- the currently\n  configured ceiling, so a caller can check before it even claims.\n\nBoth fields are pure passthrough: `bus_mcp/routes.py` merges the bus's raw\nJSON response into the tool result (`{\"ok\": True, **result}`), so no\nclient-side code change was needed to carry these new fields -- only the\ntool descriptions (below) and test coverage locking the behavior in both\ndirections. **Version-tolerant by construction:** against a pre-v1.3 bus\nthat omits these fields entirely, the tool result simply lacks `lease_s` /\n`max_lease_seconds` -- never a crash, never a synthesized default.\n\nNo client-side ceiling caching/pre-flight warning is implemented -- this\nclient holds no state between calls (every tool call is a fresh `httpx`\nrequest), so there is nothing to check a requested `lease_s` against locally\nbefore the round-trip. A caller that wants to avoid a surprise clamp should\ncall `get_bus_status` first and compare its own `lease_s` request against\n`_meta.max_lease_seconds`.\n\n## Usage examples\n\nOnce connected in a Claude session, an agent can:\n\n```\nclaim_lane(lane=\"feeds-refactor\", owner=\"session-A\", lease_s=300)\nheartbeat_lane(lane=\"feeds-refactor\", owner=\"session-A\")\npost_message(topic=\"converge\", sender=\"session-A\", body=\"lane merged to master\")\nrelease_lane(lane=\"feeds-refactor\", owner=\"session-A\")\nget_bus_status()\n```\n\n## Testing\n\n```bash\n.venv/Scripts/python.exe -m pytest -q\n```\n\nCI (`.github/workflows/ci.yml`) runs this suite on every push/PR and fails\nthe build if the Tests badge above drifts from what the suite actually\nreports -- see `scripts/check_readme_counts.py`.\n\nAll HTTP is mocked via [respx](https://lundberg.github.io/respx/) -- the\nfull suite never depends on a live bus. One additional test,\n`tests/test_live_smoke.py::test_live_get_bus_status_returns_rollup`, is\ngated behind `BUS_MCP_LIVE=1` and calls a real running bus's `get_bus_status`\nroute. **As of this writing the bus routes are dormant/404 on the live\n`:8100` AlphaHive backend** until the operator restarts it with\n`coordination_bus.py`'s router mounted -- so that one gated test is expected\nto skip (or fail if forced) until that restart happens. That is correct\nbehavior, not a bug in this repo.\n\n## Install / connect\n\n```bash\npython -m venv .venv\n.venv/Scripts/python.exe -m pip install -e \".[test]\"\n```\n\nRegistered in `~/.claude.json` under `mcpServers.bus-mcp` as a stdio server\ninvoking `run_server.py` by absolute path (no `cwd` needed -- the entrypoint\nadds its own directory to `sys.path`).\n\n## Handshake check\n\n```bash\n.venv/Scripts/python.exe scripts/list_tools.py\n```\n\nPrints the six registered tool names with no transport started -- pure\nintrospection, useful for verifying the server wires up cleanly after any\nchange.\n\n## Out of scope\n\n- Authenticating *who* `owner`/`sender` claims to be -- the shared secret\n  (v1.1) proves possession of a value, not identity; that stays client-\n  asserted the same as before. See the bus's own README for that boundary.\n- Restarting the AlphaHive backend to bring the live bus routes up\n  (operator, elevated -- not something this MCP does)\n- Bus v2 execution/approval features (a separate, not-yet-built arc)\n\n\n## Commercial support\n\nMaintained by [Jaimen Bell](https://jaimenbell.dev). For production MCP integrations, custom servers, or agent-reliability work, see [jaimenbell.dev](https://jaimenbell.dev).\n\nBuilding your own MCP server? The [MCP Starter Kit](https://jaimenbell.gumroad.com/l/adnojp) has templates, a build playbook, and packaging war-stories from shipping this one.\n\n<!-- MCP registry ownership marker -->\nmcp-name: io.github.jaimenbell/bus-mcp\n",
  "bytes": 10562,
  "sha": "400391d6c8332d005edd494792883075e56d2c2e1c487329f3c48fe74fbe3469",
  "repo_slug": "jaimenbell/bus-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_jaimenbell_bus_mcp_a724d3b7/readme"
}