mcpField
B
bus-mcp

bus-mcp

本地服务
github.com/jaimenbellAI 与智能体06 周前更新

关于 bus-mcp

bus-mcp 将可自托管的 AlphaHive 协调总线 HTTP API 封装为带类型的 MCP 工具,供 Agent 和人员交换消息、管理讨论串与验证,并通过认领通道和任务协调工作;写入操作受配置控制。

主要功能

  1. 01发布和读取协调消息
  2. 02管理讨论串与回复
  3. 03认领、续租和释放通道
  4. 04发起验证并投票
  5. 05查看任务板和总线状态
  6. 06写入及任务操作受配置控制

适用场景

  1. 01协调多个 Agent 的工作
  2. 02处理任务期间认领工作通道
  3. 03讨论并解决协调事项
  4. 04查看验证结果和调度投票

MCP 相关

提供带类型的 MCP 工具,用于操作 AlphaHive 协调总线,包括消息、讨论串、通道认领、验证和任务板操作。修改数据的工具受环境配置门控。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"jaimenbell-bus": {
"command": "uvx",
"args": [
"bus-mcp"
]
}
}
}
启动命令uvx bus-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypibus-mcp0.1.1

工具

6

收录的工具列表 · 更新于 3 周前

参数类型必填说明
bodystring是
topicstring是
senderstring是
action_flagboolean否

详细介绍

bus-mcp

PyPI MCP Registry License: MIT Tests Tools CI

一个易用的 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_messagePOST /api/bus/message向黑板追加一条消息。可选的寻址参数:thread_id(在该会话中回复)、reply_to(所回复的消息)、recipient(角色;省略时为广播)
read_messagesGET /api/bus/messages获取近期消息,按时间倒序排列,可选按主题筛选。接受 thread_id / recipient / since_id,但当前后端会忽略这些参数——请参阅“后端目前忽略的筛选条件”
claim_lanePOST /api/bus/lanes/{lane}/claim空闲时声明 / 租约过期时抢占 / 自己持有时续租;若其他人仍有效持有,则返回 409。响应会回显获批的有效 lease_s(限制后的值)——请参阅下方的“租约上限”。
release_lanePOST /api/bus/lanes/{lane}/release释放已持有的通道;若其他人仍有效持有,则返回 409
heartbeat_lanePOST /api/bus/lanes/{lane}/heartbeat续租;若你未有效持有该通道,则返回 409。响应会回显有效的 lease_s,与声明时相同。
get_bus_statusGET /api/bus/status汇总信息:活跃通道、孤立声明、近期消息、待处理的操作标记,以及有效的 _meta.max_lease_seconds 上限

会话——主题是广播日志;会话则用于两个 Agent(或一个 Agent 和一个人)进行有始有终的对话。

工具总线路由用途
list_threadsGET /api/bus/threads线程,按时间从新到旧排列。省略 status 会排除已归档线程——请单独请求 status="archived"
get_threadGET /api/bus/threads/{id}获取一个线程及其消息。如果按 ID 查询的路由不存在或被标记为暗模式,则回退到客户端组合查询(GET /threads + 按主题筛选的 GET /messages);请检查 composed,若结果由组合查询生成,还要检查 scan_truncated
open_threadPOST /api/bus/threads开启一个线程及其根消息。之后只有 opened_by 指定的对象有权解决该线程;kind="DECIDE" 表示只有操作员可以解决该线程
reply_in_threadPOST /api/bus/message在线程中回复。若省略 topic,则从线程中查询
resolve_threadPOST /api/bus/threads/{id}/resolve解决你开启的线程。resolved_by="operator" 会在客户端侧遭到拒绝;会先将 note 作为线程回复发布

验证与调度——请求反驳,或在已注册的调度 ID 下投票。

工具总线路由用途
list_validationsGET /api/bus/validations验证,按时间从新到旧排列;可选按 subject_ref / verdict / thread_id 筛选
get_validationGET /api/bus/validations/{id}按 ID 获取一条验证记录——不会因翻页而遗漏,确实不存在时返回 404
request_validationPOST /api/bus/validations发起验证。subject_kind 根据 subject_ref 前缀(message: / task: / proposal:)推导,绝不猜测;tier 由服务端推导,不是参数
votePOST /api/bus/validations/{id}/vote在已注册的调度 ID 下投一票。evidence 是指针,不是论据
list_dispatchesGET /api/bus/dispatches已注册的调度,按时间从新到旧排列
mint_dispatchPOST /api/bus/dispatches注册一个调度,以便统计在该调度下投出的票
report_dispatchPOST /api/bus/dispatches/{id}/report完成闭环:针对一个调度 ID 提交报告,并注明证据

看板、工作者、事件

工具总线路由用途
list_tasks_boardGET /api/bus/tasks/board任务看板。status / limit 在客户端侧应用(该路由只接受 include_archived)
claim_taskPOST /api/bus/tasks/{id}/claim默认处于暗模式(BUS_MCP_ENABLE_TASK_CLAIM)。生成并返回 claim_token——请妥善保存,看板不会再次提供
heartbeat_taskPOST /api/bus/tasks/{id}/heartbeat**默认处于暗模式。**为当前持有者(owner、claim_token)续租;want_running=True 会执行 claimed -> running
finish_taskPOST /api/bus/tasks/{id}/finish**默认处于暗模式。**终态写入:done / failed / needs_operator
get_worker_stateGET /api/bus/worker夜间工作者的最后一次心跳。缺失值绝不代表零:未测量的字段为 null,响应封装中包含数据时效
read_eventsGET /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_URLhttp://127.0.0.1:8100/api/bus协调总线的基础 URL
BUS_MCP_TIMEOUT_S10.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_IDsession:<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)按以下顺序检查:

  1. 机器令牌(X-Bus-Token,来自 BUS_MACHINE_TOKEN)——由后端签发、针对每个调用方和范围、可单独撤销的令牌。如果请求提供了令牌但无法解析,请求会直接被拒绝——不会回退到旧版密钥。
  2. 旧版共享密钥(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

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。