关于 bus-mcp
bus-mcp 将可自托管的 AlphaHive 协调总线 HTTP API 封装为带类型的 MCP 工具,供 Agent 和人员交换消息、管理讨论串与验证,并通过认领通道和任务协调工作;写入操作受配置控制。
主要功能
- 01发布和读取协调消息
- 02管理讨论串与回复
- 03认领、续租和释放通道
- 04发起验证并投票
- 05查看任务板和总线状态
- 06写入及任务操作受配置控制
适用场景
- 01协调多个 Agent 的工作
- 02处理任务期间认领工作通道
- 03讨论并解决协调事项
- 04查看验证结果和调度投票
MCP 相关
提供带类型的 MCP 工具,用于操作 AlphaHive 协调总线,包括消息、讨论串、通道认领、验证和任务板操作。修改数据的工具受环境配置门控。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
6收录的工具列表 · 更新于 3 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| body | string | 是 | |
| topic | string | 是 | |
| sender | string | 是 | |
| action_flag | boolean | 否 |
详细介绍
bus-mcp
一个易用的 MCP 服务,为可自托管的 AlphaHive 协调总线(alphahive 仓库中的 backend/coordination_bus.py)提供接口——Claude Agent 可以调用 claim_lane("feeds-refactor", owner="session-A"),而无需手动拼接 curl -X POST .../lanes/feeds-refactor/claim -d '{...}'。本项目遵循 desktop-mcp/github-mcp 标准(自有 pyproject、fastmcp 服务、内容翔实的 README、真实的测试套件),将这种“基于 HTTP API 的 MCP”模式用于我们自己的可自托管 API。
快速开始(60 秒)
pip install bus-mcp
将以下内容添加到 Claude Desktop/Code MCP 配置中:
{
"mcpServers": {
"bus-mcp": {
"command": "bus-mcp"
}
}
}
PATH 中没有控制台脚本?可改用 "command": "python", "args": ["-m", "bus_mcp"]。
默认情况下,它会连接到 http://127.0.0.1:8100/api/bus 上的总线——如需更改地址,请参阅下方的“环境变量”。
这个项目是什么 / 不是什么
本项目为仅限 localhost 访问、无需身份验证的私有 v1 协调基础设施提供接口——它不是公共服务。总线本身是一个黑板(仅追加消息)+ 通道声明注册表(支持过期后抢占的任务队列租约)+ 用于指挥中心面板的状态汇总。它不会执行任何对外操作:消息中的 action_flag 只会被记录和显示,总线绝不会据此采取行动。与通过 curl 使用总线现有功能相比,bus-mcp 不会增加任何新能力——它只是将路由封装成带有类型化输入和错误的易用 MCP 工具,而非原始 HTTP。它有意封装了比总线更少的功能:请参阅下方的“永远不会封装的功能”。
工具
共 24 个工具。写入操作受 BUS_MCP_ENABLE_WRITE 控制;读取操作不受限制。
消息和通道
| 工具 | 总线路由 | 用途 |
|---|---|---|
post_message | POST /api/bus/message | 向黑板追加一条消息。可选的寻址参数:thread_id(在该会话中回复)、reply_to(所回复的消息)、recipient(角色;省略时为广播) |
read_messages | GET /api/bus/messages | 获取近期消息,按时间倒序排列,可选按主题筛选。接受 thread_id / recipient / since_id,但当前后端会忽略这些参数——请参阅“后端目前忽略的筛选条件” |
claim_lane | POST /api/bus/lanes/{lane}/claim | 空闲时声明 / 租约过期时抢占 / 自己持有时续租;若其他人仍有效持有,则返回 409。响应会回显获批的有效 lease_s(限制后的值)——请参阅下方的“租约上限”。 |
release_lane | POST /api/bus/lanes/{lane}/release | 释放已持有的通道;若其他人仍有效持有,则返回 409 |
heartbeat_lane | POST /api/bus/lanes/{lane}/heartbeat | 续租;若你未有效持有该通道,则返回 409。响应会回显有效的 lease_s,与声明时相同。 |
get_bus_status | GET /api/bus/status | 汇总信息:活跃通道、孤立声明、近期消息、待处理的操作标记,以及有效的 _meta.max_lease_seconds 上限 |
会话——主题是广播日志;会话则用于两个 Agent(或一个 Agent 和一个人)进行有始有终的对话。
| 工具 | 总线路由 | 用途 |
|---|---|---|
list_threads | GET /api/bus/threads | 线程,按时间从新到旧排列。省略 status 会排除已归档线程——请单独请求 status="archived" |
get_thread | GET /api/bus/threads/{id} | 获取一个线程及其消息。如果按 ID 查询的路由不存在或被标记为暗模式,则回退到客户端组合查询(GET /threads + 按主题筛选的 GET /messages);请检查 composed,若结果由组合查询生成,还要检查 scan_truncated |
open_thread | POST /api/bus/threads | 开启一个线程及其根消息。之后只有 opened_by 指定的对象有权解决该线程;kind="DECIDE" 表示只有操作员可以解决该线程 |
reply_in_thread | POST /api/bus/message | 在线程中回复。若省略 topic,则从线程中查询 |
resolve_thread | POST /api/bus/threads/{id}/resolve | 解决你开启的线程。resolved_by="operator" 会在客户端侧遭到拒绝;会先将 note 作为线程回复发布 |
验证与调度——请求反驳,或在已注册的调度 ID 下投票。
| 工具 | 总线路由 | 用途 |
|---|---|---|
list_validations | GET /api/bus/validations | 验证,按时间从新到旧排列;可选按 subject_ref / verdict / thread_id 筛选 |
get_validation | GET /api/bus/validations/{id} | 按 ID 获取一条验证记录——不会因翻页而遗漏,确实不存在时返回 404 |
request_validation | POST /api/bus/validations | 发起验证。subject_kind 根据 subject_ref 前缀(message: / task: / proposal:)推导,绝不猜测;tier 由服务端推导,不是参数 |
vote | POST /api/bus/validations/{id}/vote | 在已注册的调度 ID 下投一票。evidence 是指针,不是论据 |
list_dispatches | GET /api/bus/dispatches | 已注册的调度,按时间从新到旧排列 |
mint_dispatch | POST /api/bus/dispatches | 注册一个调度,以便统计在该调度下投出的票 |
report_dispatch | POST /api/bus/dispatches/{id}/report | 完成闭环:针对一个调度 ID 提交报告,并注明证据 |
看板、工作者、事件
| 工具 | 总线路由 | 用途 |
|---|---|---|
list_tasks_board | GET /api/bus/tasks/board | 任务看板。status / limit 在客户端侧应用(该路由只接受 include_archived) |
claim_task | POST /api/bus/tasks/{id}/claim | 默认处于暗模式(BUS_MCP_ENABLE_TASK_CLAIM)。生成并返回 claim_token——请妥善保存,看板不会再次提供 |
heartbeat_task | POST /api/bus/tasks/{id}/heartbeat | **默认处于暗模式。**为当前持有者(owner、claim_token)续租;want_running=True 会执行 claimed -> running |
finish_task | POST /api/bus/tasks/{id}/finish | **默认处于暗模式。**终态写入:done / failed / needs_operator |
get_worker_state | GET /api/bus/worker | 夜间工作者的最后一次心跳。缺失值绝不代表零:未测量的字段为 null,响应封装中包含数据时效 |
read_events | GET /api/bus/events | 通过游标轮询仅追加事件日志。行按 ID 升序返回 |
此处封装的所有总线路由都仅用于协调(存储 / 显示 / 认领)。
截至 coordination-bus v1.1,总线可能要求在写入路由上提供共享密钥(默认关闭);此客户端无需新增任何配置即可与之匹配——请参阅下文的“写入密钥身份验证(v1.1)”。此外,该服务还有自己的本地写入闸门(BUS_MCP_ENABLE_WRITE,默认关闭),以及一个范围更窄的任务认领闸门。
哪些操作永远不会封装成工具
三条总线操作路径由操作员授权,因此不会在此提供工具。这是有意为之。
tests/test_rails_pins.py 通过源代码搜索和已注册工具的内省来强制执行这一缺失要求,因此这是一条测试规则,而非承诺。
| 路径 | 原因 |
|---|---|
| validation decide 路径 | 需要通过第二个 X-Bus-Operator-Secret 进行身份验证,而本服务并未持有该密钥。此客户端可以请求并投票参与法定人数,但不能做出决定,这正是隔离的全部意义 |
| task minting 路径 | 用于创建可执行任务;其 auto 类需要操作员身份验证。创建任务仍通过命令行对人工已阅读过的暂存文件执行,作为固定流程 |
| task sweep 路径 | 会永久放弃其他申请者的条目——不是针对单项任务的操作,也无法撤销 |
身份(BUS_MCP_AGENT_ID)
总线不会对 sender / owner / opened_by 进行身份验证:它只会检查字符串格式。两个共享写入密钥的 MCP 会话在日志中无法区分。
本服务会声明一个一致的身份——若设置了 BUS_MCP_AGENT_ID,则使用该值;否则会在导入时计算一次 session:<hostname>:<pid>,并将其清理为总线自身的角色格式(可打印 ASCII 字符、不含空白字符,长度为 1-64 个字符)。调用方未提供 sender、owner、opened_by、requested_by、voter、minted_by 和 resolved_by 时,均默认使用该身份;每个工具结果都会在 agent_id 下回显所用的值——包括错误和拒绝载荷,因为调用方最需要知道哪个身份遭到了拒绝。
**这不是身份验证,而回显才是诚实之处。**任何持有写入密钥的人都可以声明任意身份。这种做法带来的是一致性,以及对“你把什么身份作为我的身份发送到线上了”这一问题的直接答复。
默认禁用任务认领(BUS_MCP_ENABLE_TASK_CLAIM)
除非 BUS_MCP_ENABLE_TASK_CLAIM 的值为真,否则 claim_task / heartbeat_task / finish_task 都会拒绝执行,并说明该开关和原因。
这不是 BUS_MCP_ENABLE_WRITE 的重复开关。写入开关用于确认本服务是否可以向总线写入;此开关则用于确认由 MCP 驱动的会话是否为看板任务的注册认领者——这是另一个问题,而目前答案是否。两个开关会叠加,写入开关位于外层,因此禁用写入的服务会答复“写入已禁用”,启用这个范围更窄的开关绝不会绕过范围更广的开关。
后端目前会忽略的筛选条件
read_messages 接受 thread_id、recipient 和 since_id,并将它们作为查询参数发送。当前后端会忽略这三项:其消息读取接口只声明了 topic 和 limit,而 FastAPI 会丢弃路由未声明的查询参数。传入这些参数不会改变返回内容。
不过我们仍会按设计接入这些参数:服务端筛选需要单独修改后端;该修改上线后,这些参数便会自动生效,无需在此处改动,也无需协商版本。get_thread 的主要路径不再需要这种处理方式(自 0.2.1 起,它会直接调用总线的 GET /threads/{id} 路由),但当该路由不可用时,其 FALLBACK 路径仍会以完全相同的方式在客户端筛选。因此,组合结果会报告 scanned 和 scan_truncated,而不会暗示它查看了整个线程。
类型化错误,不会直接崩溃
每个工具在成功时都会返回 {"ok": true, ...},失败时则返回 {"ok": false, "error": {...}}——绝不会抛出未处理的异常或堆栈跟踪。
bus_unreachable——连接被拒绝、超时或 DNS 失败。这表示 AlphaHive 后端未运行,或运行时未加载总线路由(将backend/coordination_bus.py挂载到:8100)。bus_api_error——总线返回了 4xx/5xx。包含status_code和总线自身的detail文本——例如,409通道冲突消息会告知你谁占用了该通道以及占用时长。
在内部,bus_mcp/client.py 会抛出类型化的 BusUnreachable / BusApiError 异常;bus_mcp/routes.py 会捕获这两种异常,并在工具返回前将其规范化为上述字典格式。测试会覆盖这两个层。
环境变量
| 变量 | 默认值 | 用途 |
|---|---|---|
BUS_MCP_BASE_URL | http://127.0.0.1:8100/api/bus | 协调总线的基础 URL |
BUS_MCP_TIMEOUT_S | 10.0 | 每个请求的超时时间(秒) |
BUS_MCP_LIVE | 未设置 | 设置为 1 以运行真实网络冒烟测试(参见“测试”) |
BUS_MCP_ENABLE_WRITE | 未设置(关闭) | 本地写入开关。在此变量为真之前,每个修改数据的工具都会以类型化的 policy_refusal 拒绝操作——这与 BUS_WRITE_SECRET 不同,后者用于通过网络对总线进行写入认证 |
BUS_MCP_ENABLE_TASK_CLAIM | 未设置(关闭) | 用于 claim_task / heartbeat_task / finish_task 的第二道、更严格的开关。参见“任务认领默认关闭” |
BUS_MCP_AGENT_ID | session:<hostname>:<pid> | 此服务在写入时声明的身份,并作为 agent_id 回传。参见“身份” |
BUS_WRITE_SECRET | 未设置 | 总线自身读取以启用写入认证的同一变量(v1.1)。在此处设置后(且未设置 BUS_MACHINE_TOKEN),每次写入工具调用都会自动发送 X-Bus-Secret: <value>。未设置 = 不发送标头,与未启用认证的总线逐字节一致。 |
BUS_MACHINE_TOKEN | 未设置 | 由 AlphaHive 后端签发、按调用方和作用域划分且可撤销的机器令牌(<token_id>.<secret>),通过 X-Bus-Token 发送。同时设置两者时,优先使用此变量而不是 BUS_WRITE_SECRET。参见下文的“写入认证”。 |
写入身份验证(v1.1 共享密钥 + v1.2 机器令牌)
协调总线可以选择要求 4 个写入路由(post_message、
claim_lane、release_lane、heartbeat_lane)提供以下两种凭据之一。这些凭据由总线的 require_write_auth 依赖项(alphahive 仓库中的 backend/auth/dependency.py)按以下顺序检查:
- 机器令牌(
X-Bus-Token,来自BUS_MACHINE_TOKEN)——由后端签发、针对每个调用方和范围、可单独撤销的令牌。如果请求提供了令牌但无法解析,请求会直接被拒绝——不会回退到旧版密钥。 - 旧版共享密钥(
X-Bus-Secret,来自BUS_WRITE_SECRET)——原始的 v1.1 凭据:一个共享值,范围为通配符,且不包含单独的调用方身份。
此客户端严格遵循相同的优先级,并从自身进程中读取相同的两个环境变量名。bus_mcp/client.py 的 post() 每次调用最多附加一个请求头:
- 设置了
BUS_MACHINE_TOKEN→ 仅发送X-Bus-Token: <value>。即使同时配置了旧版密钥,也不会一并发送——发送两者会让人误以为服务器在机器令牌无效时会回退到密钥,但实际上不会(只要提供了machine_token,服务器就会完全忽略legacy_secret)。 - 未设置
BUS_MACHINE_TOKEN,但设置了BUS_WRITE_SECRET→ 发送X-Bus-Secret: <value>(保持原始 v1.1 行为不变)。 - 两者均未设置 → 不发送身份验证请求头,与访问从未启用身份验证的总线完全相同。
bus_mcp/routes.py 和所有工具调用方都不需要了解配置了哪种凭据、是否配置了凭据,或选择了哪个请求头。client.get() 从不附加这两个请求头(总线端的 GET 路由从不要求身份验证)。
**用于已启用身份验证的总线:**使用旧版密钥时,在 AlphaHive 后端的环境和此 MCP 服务的环境中设置相同的 BUS_WRITE_SECRET 值,然后重启两个进程。使用机器令牌时,后端不会读取环境变量:在后端签发令牌(后端只存储其哈希值),并且仅在此服务的环境中将 BUS_MACHINE_TOKEN 设置为签发的令牌值(例如,在启动 run_server.py 的配置中),然后重启此服务。如果值缺失或错误,写入工具调用会返回常规的 {"ok": false, "error": {"type": "bus_api_error", "status_code": 401, ...}} 结构——无需特殊处理,错误会沿用与其他任何 4xx 错误相同的类型化 BusApiError 路径。
**未设置(默认):**不发送请求头,与访问从未启用身份验证的总线完全相同——与 v1.1 之前相比,行为不会有任何变化。
租约上限信息展示(coordination-bus v1.3+)
总线支持由运维人员配置已授予租约时长的上限(总线端的 BUS_MAX_LEASE_SECONDS):claim_lane/heartbeat_lane 请求的 lease_s=7200 可能会被静默钳制为更短的实际授予时长(例如 3600s),而不是被拒绝——有关完整的服务端说明,请参阅 alphahive 仓库中 coordination_bus.README.md 的“v1.3 - configurable lease ceiling”部分。
此客户端以增量方式展示了这份契约的两个方面:
claim_lane/heartbeat_lane响应在ok=True时包含顶层lease_s字段,表示实际授予的有效时长(钳制后)。请始终检查此字段,而不要假设请求的lease_s已完整获准;忽略该字段并按乐观估算的时程发送心跳,可能导致通道提前过期。get_bus_status会公开_meta.max_lease_seconds,表示当前配置的上限,因此调用方甚至可以在申请前进行检查。
这两个字段都只是直接透传:bus_mcp/routes.py 会将总线返回的原始 JSON 响应合并到工具结果中({"ok": True, **result}),因此无需修改客户端代码即可传递这些新字段——只需更新工具描述(如下)并添加测试,确保这两种情况的行为均得到验证。**天生兼容不同版本:**对于完全省略这些字段的 v1.3 之前版本总线,工具结果只会缺少 lease_s / max_lease_seconds——既不会崩溃,也不会合成默认值。
客户端未实现上限缓存或请求前警告——此客户端不会在调用之间保留状态(每次工具调用都会发起新的 httpx 请求),因此无法在往返请求前于本地检查请求的 lease_s 是否超限。如果调用方想避免意外钳制,应先调用 get_bus_status,再将自己的 lease_s 请求与 _meta.max_lease_seconds 比较。
使用示例
连接到 Claude 会话后,Agent 可以:
claim_lane(lane="feeds-refactor", owner="session-A", lease_s=300)
heartbeat_lane(lane="feeds-refactor", owner="session-A")
post_message(topic="converge", sender="session-A", body="lane merged to master")
release_lane(lane="feeds-refactor", owner="session-A")
get_bus_status()
也可以展开对话,让一项主张得到反驳,而不是被直接采信:
open_thread(topic="converge", title="feeds-refactor is ready", kind="DECIDE",
body="suite green on a clean checkout, one skip. Merge?")
list_threads(status="open")
reply_in_thread(thread_id=4, body="re-ran it on a clean checkout: same result")
get_thread(thread_id=4)
resolve_thread(thread_id=4, note="merged at a1b2c3d")
mint_dispatch(lane="feeds-refactor", repo="bus-mcp", purpose="verify the claim")
request_validation(subject_ref="message:24047", evidence_refs="junit.xml")
vote(validation_id=7, dispatch_id="a1b2c3d4e5f6", verdict="refuted",
evidence="tests/out.xml line 88")
get_validation(validation_id=7)
测试
.venv/Scripts/python.exe -m pytest -q
CI(.github/workflows/ci.yml)会在每次推送/PR 时运行此测试套件;如果上方的 Tests 徽章与测试套件的实际报告不一致,就会使构建失败——请参阅 scripts/check_readme_counts.py。
所有 HTTP 请求都通过 respx 模拟——完整测试套件从不依赖正在运行的总线,而一个 autouse fixture 会强制执行这一点,而非仅仅约定如此:任何未模拟的请求都会触发异常,而不会让进程继续运行。(选择性启用模拟会在最关键的地方静默失效——拒绝测试中,这些测试会断言某个调用不会发生。)另有一项测试 tests/test_live_smoke.py::test_live_get_bus_status_returns_rollup,需通过 BUS_MCP_LIVE=1 启用,并会调用真实运行中的总线的 get_bus_status 路由。除非设置该变量,否则此测试会跳过,这就是测试套件报告一次跳过的原因。
总线路由在运行中的后端上可用——将 BUS_MCP_BASE_URL 指向你的后端即可通过该测试。如果其中某个路由返回 404,并不代表路由有误:这表示后端运行的构建版本早于该路由,而工具结果会明确说明这一点,并以与其他响应相同的类型化 bus_api_error 格式携带 status_code: 404。请更新后端,或使用其当前构建版本提供的工具。
安装 / 连接
python -m venv .venv
.venv/Scripts/python.exe -m pip install -e ".[test]"
已在 ~/.claude.json 的 mcpServers.bus-mcp 下注册为 stdio 服务,通过绝对路径调用 run_server.py(无需 cwd——入口点会将自身目录添加到 sys.path)。
**合并新版本后,已经运行的 Claude Code 会话仍在与旧的服务进程通信。**请运行 /mcp 重新连接(或启动新会话),然后再依赖新版本更改的任何行为——否则,修复后的工具可能看起来仍未修复,只是因为进程从未重启。
握手检查
.venv/Scripts/python.exe scripts/list_tools.py
打印所有已注册工具的名称,不启动任何传输——纯粹用于自检,可在任何更改后验证服务是否正常接线。tests/test_check_readme_counts.py 会检查打印的数量是否与上方的 Tools 徽章一致;该脚本还会在某个已注册工具未出现在上方表格中时失败——数量匹配并不代表覆盖完整。
不在范围内
- 验证
owner/sender声称的身份——共享密钥(v1.1)证明的是持有某个值,而非身份;身份仍由客户端声明,现在通过BUS_MCP_AGENT_ID以一致的方式声明。有关这一界限,请参阅上方的“身份”以及总线自身的 README。 - 任何具有操作员权限的路由:决定验证结果、创建任务、清理看板。请参阅“本项目永远不会封装的内容”。
- 重启 AlphaHive 后端以启用实时总线路由(需要操作员权限且权限较高——本 MCP 不会执行此操作)
- Bus v2 的执行/审批功能(这是另一个尚未构建的开发方向)
商业支持
由 Jaimen Bell 维护。如需生产环境 MCP 集成、定制服务或 Agent 可靠性方面的支持,请访问 jaimenbell.dev。
正在构建自己的 MCP 服务?MCP Starter Kit 提供模板、构建指南,以及本项目发布过程中的实战经验。
mcp-name: io.github.jaimenbell/bus-mcp
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。