让 MCP 服务器主动找你:events 事件客户端与 webhook 投递实测可用

你写了一个监控脚本,让 agent 每十分钟去问一次“有新工单吗”。大部分时候答案是“没有”。你付了十次模型调用的钱,只为得到十个“没有”——而真正需要处理的那一条,最多要等十分钟才被发现。
MCP 的 events 扩展想解决的就是这件事:让服务器在事情发生的那一刻主动把消息推给 agent,而不是让 agent 不停去问。Hermes 已经实现了其中的 webhook 投递模式,PR #140121 在 2026 年 10 月 22 日合并。它目前是实验特性,只在主干上,但链路是完整的:从订阅、验证握手,到事件触发一次真正的 agent 运行。
为什么这件事以前做不成
Hermes 其实早就有大部分零件。网关里的 webhook 监听器能验证 Standard Webhooks 的 whsec_ 签名,也能动态加载路由,路由还能把结果投递到任意平台。缺的是 MCP 这一侧:events/list、events/subscribe、events/unsubscribe 三个方法,以及授权到期前的刷新循环。
缺了这些会怎样?如果你手工把一个 webhook 路由注册给某个支持事件的服务器,验证会失败:服务器会发一条签名的 {"type":"verification"} 请求过来,而 Hermes 把它当成普通事件处理——直接起一次 agent 运行、回一个 202,却没有把服务器发的挑战码原样回显。服务器判定回调不可用,返回 challenge_failed,订阅就此作废。
三步打开
mcp_servers:
ambush:
url: "https://example.com/mcp"
events: true # 为这个服务器注册事件工具
platforms:
webhook:
enabled: true
extra:
public_url: "https://hermes.example.com"
三件事必须同时成立:
events: true是逐服务器的开关。没有它,三个事件工具不会出现。- webhook 平台要开着,因为事件是通过它进来的。
public_url必须是 HTTPS。服务器会拒绝明文 HTTP 回调,所以你要么在前面挂一层终止 TLS 的反向代理,要么用隧道工具把端口 8644 暴露出去。
三个工具怎么用
打开开关后,这个服务器会多出三个工具:
list_event_types:服务器支持哪些事件类型,以及你当前订阅了哪些。先问再订,别猜名字。subscribe_event:订阅一个事件。参数包括事件自己的参数、到达时要做什么(instructions,最多 2000 字符),以及结果投递到哪(deliver、deliver_chat_id)。每一次订阅都会要你审批——因为订阅等于一条长期存在的自动触发,之后会有无人值守的运行。审批界面会把 instructions 和投递目标完整显示出来。unsubscribe_event:退订。
instructions 是这套设计里最需要想清楚的东西。它不是“收到事件后通知我”,而是“收到事件后按这段话去做”。写得含糊,你会得到一个每次都被触发、但每次都做不准的 agent。
底层:一条动态 webhook 路由
每个订阅在 Hermes 里就是一条动态 webhook 路由,用 hermes webhook list 能看到。它带自己的 whsec_ 签名密钥;而授权凭证和重放游标存在旁边的 mcp_event_subscriptions.json 里——所以只要你不用新的 instructions 或新的投递目标重新订阅,路由本身不会变。
运行时的几个细节:
- 验证握手会被正确回答,不起 agent 运行。
- 按
eventId去重,而不是按每次投递尝试的webhook-id。同一个事件重发两次,只会触发一次运行。 - 授权刷新:连接还在的时候,会在服务器授予的生命周期结束前刷新订阅;重连时也会重新订阅一遍。
- 服务器主动结束订阅(比如权限被收回),Hermes 会删掉这条路由并通知
deliver目标。 - 刷新被永久拒绝(访问被撤销、事件类型被移除、参数不再合法)会删除路由;其他失败退避重试。
事件载荷会完整传给这次运行,JSON 最多 32,000 字符,被截断时会附一条说明。事件属于外部数据,所以事件触发的运行使用的是和其他 webhook 路由一样的受限工具集。
会掉的几种情况
这套机制不是“订阅一次,永久有效”。已知的失效路径至少有五条:
- 连接必须在线。如果你给这个服务器设了
lazy: true或者idle_timeout_seconds,连接不是常开的,订阅可能失效,直到下一次连上才被重新订阅。 - 网关必须跑着。
hermes gateway run不在,事件不会到达。这是最容易在测试时误判成“功能坏了”的一条。 - 刷新失败有退避,但永久性拒绝就直接删路由。
- 限流会丢事件。监听器的每路由限流(
platforms.webhook.extra.rate_limit,默认每分钟 30 条)同样作用于事件投递;服务器未必会重投被拒的投递,所以busy 的事件类型要主动调高。 - 在 webhook 面板里把路由关掉,订阅就会失效;再打开会重新订阅。
明确不支持的部分
官方文档写得很直白,别在这上面浪费时间:
poll和push两种投递模式没有实现,只做了 webhook。gateway.multiplex_profiles下的次级 profile 不能订阅。动态路由是从启动 profile 的 home 加载的。
另外,整个设计依赖的 MCP events 设计草案本身还是工作组草稿,协议形状可能变。这也是它被标成实验特性的原因。
什么时候值得用
说实话,如果你的需求是“每小时看一眼某个网页有没有变”,继续用 cron 更省事——它不需要公网地址,也不会因为网关没跑就悄悄失效。
MCP 事件真正合适的场景是上游有自己的事件流,而且延迟重要:工单系统的新单、部署流水线的失败、订阅源的新条目。这些场景里,“被通知”和“每十分钟问一次”是两种完全不同的体验,前者还能省掉大量空转的模型调用。
如果你还在纠结“推送还是轮询”,我们之前写过 webhook 通知实战,可以对着看两种方案的取舍;另外本站的 MCP 工具成本与预算 讲的是同一批 MCP 配置里的另一组新键,配置的时候容易一起碰到。
版本状态
这个特性目前只在 main 上,2026 年 10 月 22 日凌晨合并,不在已经发布的 v0.22.4 里,也没有进入任何 tag。想试就跟着主干跑,并且做好“下个版本可能改协议”的心理准备。