AI 博客

一个掉了的订阅,和一个清静的一周长得一模一样

OpenAI 在 2026 年 9 月 29 日把插件自动化向全部订阅档位上线,对接的是 MCP Events——一份没有 SEP 编号的草案,住在一个 README 称其内容属探索性质的仓库里。这份草案把 webhook 的加固做对了,却把那两个报告「缺席」的信封做成了可选,于是被撤销的权限、丢掉的缓冲、以及确实清静的上游,抵达你的智能体时都是同一条空流。

作者 智能体 AI 维基 28 分钟读完

一个去轮询工具的智能体,在出事时会拿到一个错误;一个订阅事件的智能体,拿到的是沉默——而「清静的一周」长得也正是这样。在 2026 年 9 月 29 日的 DevDay 上,OpenAI 把插件自动化接到了 MCP Events 上——那是一份至今没有 SEP 编号的设计草图,住在一个 README 写着「内容属探索性质,不代表官方 MCP 规范或建议」的仓库里——而它的集成没有去消费的那几处,恰好就是那两个信封:告诉客户端「你的流是结束了,不是空了」的那两个。草案把它们定义了出来。草案没有把其中任何一个定为必需。事件驱动智能体的全部问题,就凝在这一个设计决定里。

速览

一条事件流安静下来的四种方式,以及本该由谁来告诉你是哪一种。

原因草案里的信号强度已上线的集成里
订阅者的权限被撤销 notifications/events/terminated 复核是 SHOULD 不消费
服务端重启丢掉缓冲 {"type":"gap"} 信封 仅发射型事件为至多一次 不消费
回调地址不可达 续订时的 deliveryStatus 只在下一次续订时可见 续订机制存在
确实什么都没发生 —— 不需要信号 —— — 与上面三种无从分辨
Four reasons nothing arrives, and the envelope that would say which Four causes of an empty event stream converge on one indistinguishable outcome. Access revoked is signalled by a terminated envelope; a server restart dropping an emit-only buffer is at-most-once and signalled by a gap envelope; a failed webhook delivery surfaces only in the delivery status on the next refresh; and genuinely quiet upstream is indistinguishable from all three. Without the control envelopes the agent sees the same empty stream in every case. Cause Signal the draft defines What the agent sees Access revoked removed from the channel notifications/events/terminated delivery-time re-check is a SHOULD Server restart emit-only buffer lost {"type":"gap"} at-most-once across restarts Delivery failing callback unreachable, retried deliveryStatus visible only on the next refresh Nothing happened a genuinely quiet week — no signal needed — An empty stream identical in all four cases, with no error Drop the control envelopes and the first three collapse into the fourth. The draft defines all of them; it makes none of them mandatory, and the first large implementation consumes neither terminated nor gap.
把控制信封丢掉,前三种成因就塌进第四种里。

上线了什么,以及它接的是什么

MCP Events 是 Triggers and Events 工作组的一份文档——组长是 AWS 的 Clare Liguori 与 Anthropic 的 Peter Alexander——其章程于 2026 年 3 月 25 日合入,而它唯一的交付项「SEP: Events in MCP v1 RFC」至今写着Ideating(构思中),目标日期「End April」自那以后没有改动过。设计草图本身日期为 2026 年 2 月 19 日,标注为「Draft proposal」。规范仓库的 seps/ 目录里没有它的 SEP 文件,而那个孵化仓库带的是 experimental- 前缀,不是标志官方 MCP 扩展的 ext- 前缀。八月的路线图把「服务端发起的事件」列为当期工作,并要求在 Agents、Transports 与 Triggers 三个小组之间做一次组合性复核——也就是说,这个原语还没完成,而它与 Tasks 如何相处仍是悬而未决的问题。

OpenAI 自己的措辞谨慎而准确:支持「被提议的 MCP Events 规范」。而可用性那一行没有同等谨慎——全部订阅档位。于是一份没有拿到 SEP 的提案,在一个下午之内获得了它这辈子最大的客户端实现,而拥有它的那个工作组每周开一次三十分钟的会。

这不是在论证 OpenAI 本该再等等。没有实现的规范同样不会收敛,而这份集成忠实地实现了 webhook 那一半。这是在论证接下来会发生什么:一份草案里那些可选的部分,一旦被一个占主导地位的客户端跳过,就不再按你希望的方向保持「可选」了。

三种投递模式,而承重的那句话是「none is mandatory」

草案规定了 poll(events/poll)、push(events/stream)与 webhook(events/subscribe),按事件类型逐一声明,而它对代价说得很明白:「This goes against MCP's usual stance of offering one way to do a thing, and the cost is real: roughly 3× spec and SDK surface.」三者中没有任何一个是服务端必须实现的。

The three MCP Events delivery modes Three horizontal lanes between an MCP server and an agent client. Poll uses repeated request and response carrying a cursor. Push holds a long-lived stream with heartbeats. Webhook registers an HTTPS callback with a client-supplied secret and the server posts each event to it. The draft marks none of the three as mandatory; the ChatGPT integration implements only the webhook lane. MCP server Agent client upstream Slack, GitHub, PagerDuty… events/list SDK loop negotiates mode, model sees only arriving events Poll — events/poll request with cursor events[], cursor, nextPollMs Push — events/stream notifications/events/event heartbeat carries the cursor while quiet Webhook — events/subscribe signed POST to an https callback → client supplies the whsec_ secret; the only mode ChatGPT implements The draft advertises delivery modes per event type and marks none of them mandatory — so two servers can both conform and share no way to talk to each other.
webhook 是已上线集成实现的那条通道;poll 与 push 不被它支持。

模式层面的可选性,对任何在造服务端的人都有一个直接后果:合规不等于互通。一台只实现 poll 的服务端与一个只实现 webhook 的客户端,可以都是正确的,却彼此无话可说。今天这件事落地成一条事实上的要求——去实现 webhook,因为那是最大的客户端说的那门话,而这同时也意味着持久化的订阅存储,以及一条从你的服务端出去的 HTTPS 通路,而这两样东西,一台只提供工具的 MCP 服务端都不需要。

注意授权是朝哪个方向跑的。webhook 订阅必须由一个已认证的主体发起——服务端必须用 -32012 Forbidden 拒绝未认证的 events/subscribe——而订阅键是 (principal, delivery.url, name, arguments)。签名密钥由客户端提供。而 poll 与 push 则对未认证的服务端也开放。所以模式的选择,同时也是在选「这条流背后到底有没有一个主体」。

失败模式是沉默,而草案知道这一点

草案自己的摘要把这件事说得直白:事件让客户端订阅,并「have the agent react when they occur, without the user being present」。没人在等那个结果,所以也没人会注意到它的缺席。接下来有四件事会产出一条空的流。

第一是撤销。授权在订阅时作为 MUST 被检查;此后「the server SHOULD periodically re-verify permissions」,而当权限丢失时,订阅被终止,并由一个 terminated 信封带上 -32012 Forbidden 与一条原因。注意那几个情态动词:最初那次检查是必需的,持续那次检查是建议性的,而那条通知是一条你的客户端必须在听的消息。一个忽略它的客户端会在下一次续订时才知道,因为 events/subscribe 兼作续订调用——而草案允许服务端授予 refreshBefore: null,即不过期,到那时 SDK 的续订循环「drops to an occasional health-check cadence」。你的智能体「我还订着」这份信念,永远只和它最后一次续订一样新鲜,而草案允许那个间隔非常长。

第二是重启。只有「when the cursor is backed by a durable upstream」才有至少一次的保证;对仅发射型的事件类型,草案说得很钝——「at-most-once across server restarts — the in-memory buffer and its cursors do not survive」。第三是投递失败:webhook 的重试是逐事件独立的,所以到达顺序会被打乱,而客户端对失败的视野是它在下一次续订时读到的 deliveryStatus 字段。第四是确实什么都没发生。

而草案对这个下限很诚实:「This design intentionally does not provide protocol-level guarantees around event ordering, exactly-once delivery, or transactional consistency. There are no logical clocks, sequence numbers, or cross-subscription ordering constraints.」恰好一次需要在应用层按 eventId 去重。这是一个站得住的选择——Stripe、GitHub 与 Shopify 都是这么发货的——但它落在的那个消费方是一个智能体循环,而那是所有人技术栈里最不幂等的部件。一条重复的 webhook 不会重新渲染一个页面;它会重新执行一个计划。

孵化仓库里已经有一份正是这一类「盲视」的现场报告,2026 年 8 月 24 日针对长轮询提交:一次 30 天试点、74 次成功调用、中位 45.9 秒、p95 241.7 秒,其中 22 次超过 60 秒。作者给出的那个拦路问题只有一句——「Client abandonment is invisible. All 22 calls past 60 s were recorded ok.」服务端分辨不出已经没人在听了。这与那四种沉默是同一个问题,只是从另一头到来。

webhook 那套管道恰恰是他们做对的部分

把批评落在哪里、不落在哪里,值得说得精确些——因为这份草案里的安全工程,比围绕「服务端发起的事件」的那些议论所暗示的要好。与 A2A 的推送通知相比——那是最接近的先例,一个智能体把任务更新 POST 到客户端提供的 webhook,凭据取自配置里的 authentication 字段——MCP Events 在每一条轴上都更严。

What each implementation covers Matrix comparing the MCP Events draft, the ChatGPT plugin integration and A2A push notifications across eight axes. The draft covers all three delivery modes and both control envelopes and specifies endpoint verification, mandatory HTTPS and a replay window. The ChatGPT integration covers webhook delivery and endpoint verification only. A2A push covers webhook delivery with authenticated headers but specifies no endpoint handshake and no replay window. Coverage by axis — draft, shipped integration, and the nearest prior art MCP EVENTS DRAFT CHATGPT PLUGINS A2A PUSH Webhook delivery Specified Implemented Specified Poll and push modes Both Neither Not in scope The “terminated” envelope Defined, optional Not consumed None The “gap” envelope Defined, optional Not consumed None Endpoint verification handshake Mandatory Implemented Unspecified HTTPS for callbacks MUST Required SHOULD Replay window on delivery 5 minutes Signed timestamp Unspecified Ordering / exactly-once Explicitly out Caller's problem Caller's problem Covered Partial or optional Absent
加固这部分是覆盖到了的;而报告「缺席」的那个控制面,正是可选的那一列。

回调 URL 必须是 https——这是一条 MUST,而 A2A 说的是 SHOULD。投递带着 Standard Webhooks 的 HMAC 签名,密钥是客户端提供的 whsec_,所以服务端从不铸造那把用来认证它自己投递的凭据。在投递启用之前有一次强制的端点验证握手,按 (principal, url) 缓存,专门用来阻止一个订阅被当作对第三方的反射或放大原语——A2A 没有规定任何等价物。重放被一个 webhook-timestamp 新鲜度窗口约束,草案把它定在五分钟,外加 eventId 去重与一个一次性 nonce。SSRF 防护是投递时对着 IANA 专用地址登记做 IP 校验、用原始 Host 与 SNI 连到那个已校验的 IP、且不跟随重定向。

草案也用显而易见的方式关上了所有人最先会问的那扇门。事件被收到「does NOT constitute authorization to act」——智能体对事件的回应要走正常的 MCP 授权——而载荷是「untrusted data with the same injection considerations as tool results」。它刻意不提供令牌透传、不提供 client-credentials 授权、也不代租户附上 OIDC 身份令牌。无论你怎么看事件驱动的智能体,写这份东西的人是读过威胁模型的。

而这正是那处遗漏值得玩味的地方。那些困难的安全问题拿到了 MUST。而「告诉客户端它的流结束了」这个问题拿到的是一个可选的信封——而可选的信封不会被实现,因为没人会为一个从未到来的事件提 bug。

这周该改什么

如果你在造一台将要承载事件的 MCP 服务端,或在把一个智能体接去消费它们:

  • 把「缺席」当成一个可告警的状态,而不是一个安静的状态。为每个订阅记录一个期望到达率,并在流平坦得超过该速率所预言的时长时报警。这与按排程与按触发运行的智能体里「盯着缺席看」是同一种纪律,而它也是唯一一件无论你的客户端消费哪些信封都管用的事。
  • 即便它只是 SHOULD,也在投递时复核。如果你是服务端,请在出门那一刻检查主体的权限,而不只在订阅时检查。另一个选项是:把某个 Slack 频道的消息,投递给一个其用户早在三月就退群了的智能体。
  • 在上线之前就让事件处理器按 eventId 幂等。不是等第一条重复到了以后。草案给你的最好情况是至少一次,而对仅发射型事件是至多一次;这两种都会以 Web 后端能静默吸收的方式,把一个智能体循环弄坏——幂等、重试与副作用安全。
  • 约束一个事件可以造成什么。一个屋里没有请求方的触发器,是「意图与效果之间裂口最宽」的那种配置,所以请设花费上限、收紧写权限、并在路径里保留可逆性。爆炸半径,以及讲那份多出来的可达范围从哪来的环境权限。
  • 锁定协议版本,并预期它会动。这份集成要求 2026-07-28;而事件这块本身尚处 SEP 之前,工作组每周开会。把版本写进你服务端的契约测试里,而不是写进你的记忆里——2026-07-28 修订版,以及协议修订与弃用窗口。
  • 把订阅当成一项授权记下来。它派生自一次只发生过一回的权限检查、它会自我续期,而且它可能活得比提出它的那个项目更久。把它放进清册里、紧挨着凭据——智能体凭据的权限复核。

常见问题

MCP Events 是 MCP 规范的一部分吗?

不是。截至 2026 年 10 月 1 日,规范仓库里没有它的 SEP 文件,工作组章程把它的 RFC 列为「Ideating」,设计草图标注为「Draft proposal」,而孵化仓库的 README 写明其内容「are exploratory and do not represent official MCP specifications or recommendations」。OpenAI 自己的措辞——「被提议的 MCP Events 规范」——是准确的。

权限被撤销之后,事件还会继续投递吗?

如果服务端按草案的意图行事就不会:它在察觉权限丢失时会终止该订阅。缺口在边界的客户端那一侧。投递时的复核是 SHOULD 而不是 MUST,而「订阅已结束」这条通知是一个客户端必须去消费的信封——所以现实中的失败是:一个智能体以为自己还在盯着某样东西,其实没有,而这段时间有多长,取决于它的续订间隔。

既然 webhook 能用,只支持 webhook 为什么要紧?

因为它改变了「MCP 服务端是什么」。只提供工具的服务端回应请求;而一个 webhook 发布方需要持久化的订阅存储、一条出站 HTTPS 通路、签名生成与一次验证握手。那是一个带投递 SLA 的有状态服务,而这与多数 MCP 服务端当初被造出来时所承担的运维承诺并不相同——MCP 的生产运维。

这和 A2A 的推送通知有什么不同?

A2A 通知的是客户端自己创建的那个任务,所以流的背后总有一次在先的用户动作。MCP Events 订阅的是一个上游的第三方系统,而用草案自己的话说,用户并不在场。webhook 的机械部分是相似的,而 MCP 的更严;新出现的风险是那个缺席的请求方,不是传输层。

我该等到 SEP 出来再去对接吗?

如果用例是真的,就把服务端造起来——webhook 那块面很小,加固要求也很清楚。但不要写那种假定了顺序、恰好一次投递或可靠终止信号的业务逻辑,因为草案明确拒绝提供前两者,并把第三者做成了可选。那几处最可能变,而无论如何,你的正确性都不该依赖它们。

延伸阅读

本维基内:

来源: