MCP se vuelve server-initiated: auditoría del sketch de Events
Un servidor MCP todavía no puede tocarle el hombro al agente. Cuando el roadmap de agosto puso los "agentic messaging primitives" como su primera prioridad, el trabajo de diseño concreto ya estaba en un repo de incubación: un primitivo de Events con tres modos — poll, push, webhook — que recibió su última revisión sustantiva hace cuatro días.
El rastro pasa por cuatro documentos. El roadmap de MCP del 22 de agosto de 2026, firmado por los lead maintainers David Soria Parra y Den Delimarsky, nombra los server-initiated events — "webhooks and channels" — dentro de la primera de sus cinco áreas prioritarias, y promete revisión expedita para los SEPs que caigan dentro de ellas. El Triggers and Events Working Group, constituido el 24 de marzo de 2026 y co-liderado por Clare Liguori (AWS) y Peter Alexander (Anthropic), es dueño del mecanismo. El diseño en sí es el design sketch de Events de Alexander, un borrador fechado el 19 de febrero que absorbe review desde abril y fue enmendado de nuevo el 4 de septiembre. Y un field report presentado el 24 de agosto aporta lo que suele faltar en las discusiones de estándares: treinta días de datos de producción sobre lo que el polling cuesta de verdad.
Leímos los cuatro, más la maquinaria de subscriptions/listen que ya salió al aire, la propuesta competidora que se cerró sin merge hace seis días, y el bug abierto del spec que muestra lo difícil que es acertar el teardown iniciado por el servidor. Esta es una auditoría de dónde está la capa de push de MCP, primitivo por primitivo.
Lo que request-response no puede expresar
El contrato de MCP siempre apuntó en una dirección. El cliente llama tools; el servidor responde. El servidor puede enviar notificaciones — notifications/tools/list_changed, notifications/resources/updated — pero esas actualizan metadata del lado del cliente. Nunca inician un turno nuevo del modelo. El aún abierto SEP-2495 plantea el hueco sin rodeos: el LLM no puede esperar interacciones de usuario, datos de sensores o webhooks desde el lado del servidor, así que un agente que debería reaccionar a un mensaje de Slack o a un incidente de PagerDuty tiene que pollear — y cada poll que vuelve vacío es un turno de modelo desperdiciado.
La especificación 2026-07-28 dio el primer paso con subscriptions/listen: un request de larga vida que abre un stream de notificaciones, reemplazando el viejo RPC resources/subscribe y el endpoint HTTP GET. El cliente envía un filtro con los tipos de notificación que quiere; el servidor debe confirmar antes de entregar nada, no debe enviar tipos no solicitados, y marca cada mensaje con un subscription ID en _meta para que los streams concurrentes se demultiplexen limpiamente. Es un primitivo de suscripción real, con reglas de orden reales.
Pero miren lo que el filtro puede contener: toolsListChanged, promptsListChanged, resourcesListChanged y una lista de URIs de resources. Los cuatro son metadata de protocolo. No hay manera de decir "avísame cuando llegue un email nuevo" o "despierta al agente cuando se abra un incidente P1". subscriptions/listen resolvió el delivery de las notificaciones que MCP ya tenía. No creó un vocabulario para los eventos que a los agentes les importan. Ese vocabulario es lo que agrega el sketch de Events.
Incluso la maquinaria ya publicada tiene bordes sin terminar. Un issue abierto el 6 de septiembre, #3348, documenta que dos páginas de la revisión 2026-07-28 no se ponen de acuerdo en cómo un servidor termina un stream de subscriptions/listen: la página de cancellation dice que el servidor MUST enviar notifications/cancelled, la de subscriptions dice que SHOULD responder al request original y cerrar — y ambos SDKs oficiales implementan el segundo comportamiento en todos los transportes. El teardown iniciado por el servidor es exactamente el tipo de semántica que el Triggers and Events WG va a tener que clavar para Events, y esa deriva muestra lo fácil que se fractura.
El primitivo Events: occurrences tipadas, descubribles como tools
El design sketch modela los eventos como MCP modela los tools. Un servidor declara sus tipos de evento vía events/list: cada descriptor lleva un name, una descripción, un inputSchema para los argumentos de suscripción (filtros como from: "*@example.com" o severity: "P1"), un payloadSchema para los datos entregados, y los modos de delivery que ese tipo de evento soporta. Los catálogos dinámicos reutilizan el patrón conocido: notifications/events/list_changed le dice al cliente que vuelva a listar.
Cada occurrence entregada tiene la misma forma sin importar el modo: eventId para deduplicación (idealmente el identificador estable del sistema upstream, de modo que el mismo evento de Stripe entregado por webhook y por backfill de poll dedupe correctamente), name, timestamp, data conforme al payload schema, y un cursor.
Los cursors son la respuesta del sketch a la confiabilidad sin imponerla. Un cursor es una posición opaca definida por el servidor; el cliente persiste el último y lo devuelve en el siguiente poll, reconexión o refresh, y el servidor retoma desde ahí. Los servidores respaldados por un upstream durable obtienen replay ordenado. Los servidores sin historia direccionable devuelven cursor: null y el cliente vive con at-most-once. Un flag truncated: true en cualquier respuesta es la señal honesta de que se saltaron eventos — un cursor viejo, un piso de maxAgeMs o un techo del servidor. Nada finge que el delivery está garantizado cuando no lo está, una posición más defendible que las suposiciones implícitas de exactly-once que hacen la mayoría de las integraciones webhook ad-hoc.
Tres modos de delivery, ninguno obligatorio
Poll es el piso. El SDK del cliente — no el modelo — llama events/poll en un loop:
// request
{ "name": "email.received",
"arguments": { "from": "*@anthropic.com" },
"cursor": "historyId_99840", "maxEvents": 50 }
// response
{ "events": [ … ],
"cursor": "historyId_99842",
"truncated": false, "hasMore": false,
"nextPollMs": 30000 }
El servidor no mantiene estado por cliente requerido por el protocolo: cada poll es autocontenido, lo que hace de este modo un encaje natural para el núcleo stateless que estandarizó el release 2026-07-28. nextPollMs deja que el servidor dirija la cadencia, y los clientes aplican un piso (default de un segundo) para que un servidor que se porta mal no induzca un loop apretado. Lo crucial: el poll es una operación a nivel de protocolo. El LLM se invoca solo cuando el array de eventos no está vacío — el SDK absorbe los round trips vacíos que hoy queman turnos de modelo.
Push es un request events/stream de larga vida, uno por suscripción, que entrega mensajes notifications/events/event a medida que ocurren. El servidor debe enviar un heartbeat al menos cada 30 segundos, y el heartbeat lleva el cursor actual, así que la posición persistida del cliente avanza incluso en horas tranquilas. En HTTP/1.1 cada stream cuesta una conexión TCP, por lo que los clientes con varias suscripciones dependen en la práctica del multiplexing de HTTP/2; en stdio, los streams comparten stdout y se demultiplexan por subscription ID — la misma convención de correlación que usa subscriptions/listen.
Webhook es el modo del titular del roadmap, y el sketch lo trata con el mayor cuidado, porque es el único modo donde el servidor guarda estado real. El cliente llama events/subscribe con una callback URL https y un secreto de firma aportado por el cliente — el prefijo literal whsec_ más base64 de 24 a 64 bytes aleatorios, que los servidores deben rechazar si está malformado. Cada entrega va firmada siguiendo la convención de Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature) más un header de ruteo X-MCP-Subscription-Id. La rotación de secretos viene incluida: se entrega un secreto nuevo en el refresh y el servidor firma con ambos durante una ventana de gracia.
La vida de la suscripción es una negociación. El cliente sugiere ttlMs; el servidor responde con refreshBefore, una expiración autoritativa contra la cual el cliente debe refrescar. El grant no debería exceder la sugerencia — con una excepción sancionada, un piso del servidor contra tormentas de refresh. Un cliente puede pedir sin expiración con ttlMs: null, y las obligaciones se invierten: un servidor que lo concede debe persistir la suscripción a través de reinicios, porque un cliente que nunca refresca nunca puede detectar una caída silenciosa. El TTL se enmarca explícitamente como la perilla de control de recursos del servidor — grants cortos mantienen las suscripciones como soft state en memoria que la expiración recolecta; grants largos trasladan la carga de durabilidad al servidor.
La identidad es donde el sketch es más estricto. Una suscripción webhook se identifica por (principal, delivery.url, name, arguments) — sin IDs generados por el cliente. Y el principal no es opcional: events/subscribe exige un caller autenticado, porque sin un principal en la clave la tupla restante es adivinable y cualquier caller podría desuscribir o rotar el secreto de otro tenant. Un servidor MCP sin autenticación puede ofrecer poll y push. No puede ofrecer webhooks. Esa sola frase convierte silenciosamente la identidad en prerequisito del modo de delivery que la mayoría de los deployments de producción va a querer, lo que aterriza cerca de la prioridad separada del roadmap sobre agent identity.
El sketch también acuña una familia de errores de propósito general — -32011 NotFound, -32012 Forbidden, -32013 ResourceExhausted, -32014 Unsupported, -32015 CallbackEndpointError — diseñada como candidata a promoción al registro base de errores de MCP, con payloads data tipados en lugar de números nuevos por condición. A los SEPs futuros se les pide reutilizarla. Si eso ocurre, una extensión de eventos habrá estandarizado de paso la taxonomía de errores de MCP.
Treinta días de long-polling, medidos
El field report del repo de incubación es el documento más útil del hilo. Un equipo de ingeniería de Simetrik opera un conector MCP remoto (Python, Streamable HTTP, OAuth) detrás de un ALB de AWS y un ingress nginx, con tools que delegan trabajo a un agente que corre por minutos. Su workaround para la capa de push ausente es el que usa todo el mundo: bloquear dentro de tools/call por 45 segundos acotados, devolver un handle, volver a bloquear en un segundo tool.
En 30 días y 74 llamadas exitosas: p50 de 45.9 segundos, p95 de 241.7, máximo de 241.9. El long poll en sí aguantó — ninguna desconexión del lado del servidor, nunca, incluso a los cuatro minutos. El daño estaba en otra parte: 22 llamadas, el 30 por ciento del total, pasaron del techo de unos 60 segundos que el equipo observa en sus clientes objetivo. Todas quedaron registradas como éxito. El servidor terminó el trabajo, anotó ok, y no tenía forma de saber que nadie escuchaba — ni de entregar el resultado después. Su métrica de éxito y la experiencia del usuario divergieron en silencio, 22 veces.
El report nombra el candidato a show-stopper para una v1 solo-poll: un servidor necesita alguna forma de enterarse de que su resultado nunca fue recogido, o el handle debe ser lo bastante durable para que la respuesta sobreviva a la llamada abandonada. Ese es un argumento para emparejar Events con la extensión de Tasks — cuya maduración el roadmap lista en la misma área prioritaria, y cuyas notificaciones de task completion el charter del WG reclama explícitamente como propias.
Gobernanza: una propuesta murió para que esta avance
El camino es más angosto que hace un mes. SEP-1803, una propuesta de Event Subscriptions abierta por un ingeniero de OpenAI en noviembre de 2025, se cerró sin merge el 2 de septiembre por el lead maintainer David Soria Parra, tras meses de pings automáticos de inactividad al sponsor asignado sin respuesta. Más allá de los méritos del diseño, el proceso SEP ruteó alrededor de un sponsorship estancado — y el repo de incubación del WG es ahora el único foro activo para este problema.
Dentro de ese foro, el sketch sigue moviéndose. El 4 de septiembre el autor empujó reglas de evolución de schemas en respuesta al review: los descriptors deberían evolucionar de forma aditiva durante la vida de un nombre de evento, los cambios breaking deberían salir bajo un nombre nuevo servido junto al viejo, y las suscripciones vivas a un tipo removido se terminan con un error tipado en lugar de dejarlas fallar misteriosamente. La vara de éxito declarada del WG es un SEP aceptado, implementaciones de referencia en al menos dos SDKs Tier-1 y cobertura de conformance. Bajo la regla de revisión expedita del roadmap, esto aterriza antes de lo que la ausencia de fechas sugiere.
Qué significa para LLM4Agents
Estamos en ambos lados de este protocolo. Como operadores de un servidor MCP, nuestra superficie de tools asume el modelo de polling que el field report acaba de tasar: un agente que envía un job de inferencia largo al gateway vuelve a preguntar por él, gastando un turno por consulta. Events nos da el vocabulario para invertir eso — inference.completed, balance.low, settlement.confirmed como tipos de evento declarados con payload schemas, entregados por poll para agentes free-tier y por webhook firmado para los registrados. Nuestra plataforma ya expone webhooks para billing; el sketch nos dice a qué forma deberían converger: firmas Standard Webhooks, secretos whsec_ aportados por el cliente, rotación con doble firma, suscripciones acotadas por TTL.
El acoplamiento con identidad juega a nuestro favor. El modo webhook exige un principal autenticado, y la clave de suscripción está scoped a él — lo que mapea uno a uno con las API keys del gateway y las identidades de agente detrás. Un agente fondeado en stablecoins que registra un callback para balance.low está ejerciendo el mismo principal con el que paga. Y la disciplina de payload schemas importa específicamente para pagos: un evento settlement.confirmed firmado y conforme a schema es evidencia verificable por máquina sobre la que un agente puede actuar sin que un humano lea un dashboard.
La amenaza es simétrica: si Events sale y nosotros seguimos exigiendo polling, cada competidor cuyo gateway llame de vuelta al agente es más barato por job que nosotros, en turnos de modelo si no en dólares.
Cómo mantenerse en la frontera
Primero, implementar subscriptions/listen ya. Está en la especificación 2026-07-28 publicada, ambos SDKs Tier-1 lo soportan, y es el sustrato sobre el que va a viajar el delivery estilo Events. Seguir el comportamiento de los SDKs en el teardown — respuesta exitosa y cierre — que es hacia donde el issue #3348 dice que se alineará el texto del spec.
Segundo, prototipar el modo poll de Events detrás de un flag. Poll es stateless, autocontenido y calza con la arquitectura de nuestro gateway; la superficie son tres métodos y un cursor. Seguir el sketch ahora significa que nuestro feedback llega mientras el diseño todavía está blando, y la migración después es un rename y no un rewrite.
Tercero, converger nuestros webhooks de billing al contrato webhook del sketch. Firma Standard Webhooks, secretos whsec_ del cliente, ruteo X-MCP-Subscription-Id, refresh por TTL. Cada campo de ese contrato que adoptemos temprano es trabajo de migración que no hacemos después.
Cuarto, publicar nuestro propio field report. El WG está votando mecanismos de v1 con n=74 de un solo deployment. Nuestro gateway ve comportamiento de polling de muchos agentes; treinta días de nuestros números sobre polls abandonados y turnos desperdiciados cuesta poco producirlos y compra un asiento en la sala donde se decide el mecanismo.
Quinto, adoptar la familia de errores -3201x en nuestra superficie MCP. Los códigos están diseñados explícitamente para reutilización y para promoción al registro base. Llegar temprano no cuesta nada; llegar tarde significa un breaking change.
Paga por inferencia, en stablecoins, sobre una API OpenAI-compatible
Settlement con x402 y EIP-3009, model routing y fallback, sin suscripción.
Registra un agente