Los eventos MCP permiten que ChatGPT se suscriba a novedades de tu servidor MCP, como mensajes nuevos, actualizaciones de contenido o cambios de estado. Los usuarios eligen qué monitorear y qué debe hacer ChatGPT cuando llega una actualización.
| Caso de uso | Solicitud del usuario | Evento MCP |
|---|---|---|
| Convertir comentarios en pull requests | Monitorea #product-feedback para detectar reportes de errores y abre pull requests en borrador con las correcciones y las pruebas. | message.created, filtrado por channel_id |
| Aplicar cambios solicitados en un documento | Monitorea este documento para detectar comentarios de revisión y aplica los cambios que se soliciten. | comment.created, filtrado por document_id |
Antes de empezar
Los eventos MCP están disponibles en los chats del modo Work en la versión web de ChatGPT, en los chats del modo Work en la aplicación de escritorio si seleccionas Nube y con dots. Se aplican los controles del Espacio de trabajo para los complementos y las tareas activadas por eventos.
ChatGPT admite eventos MCP a través de Streamable HTTP con initialize, o a través de MCP 2.0 HTTP (versión 2026-07-28 del protocolo). Configura tu servidor en el complemento y asegúrate de que cuente con almacenamiento persistente para las suscripciones y acceso HTTPS saliente a las URL de callback.
ChatGPT admite la entrega mediante webhooks y la verificación de callbacks que describe el borrador de la especificación de eventos MCP. Esta integración no admite el sondeo, la transmisión continua ni las notificaciones de control gap y terminated del borrador.
Cómo funciona
- Tu servidor enumera los eventos que admite.
- El usuario le indica a ChatGPT qué monitorear y cómo responder.
- ChatGPT se suscribe a través de tu servidor MCP y proporciona una URL de callback y un secreto de firma.
- Tu servidor envía a esa URL los eventos que coinciden con la suscripción.
- ChatGPT recibe el evento en el chat suscrito y responde según las instrucciones del usuario.
Anunciar la compatibilidad con eventos
La detección de eventos comienza con las capacidades de tu servidor. En las versiones de MCP que usan inicialización, agrega events a las capacidades que devuelve tu servidor en la respuesta a initialize:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {},
"events": {}
},
"serverInfo": {
"name": "example-server",
"version": "1.0.0"
}
}
}
En MCP 2.0 (versión 2026-07-28 del protocolo), agrega events a las capacidades que devuelve tu servidor en la respuesta a server/discover:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {},
"events": {}
}
}
}
Implementa estos tres métodos de eventos en el mismo punto de acceso MCP autenticado que usan tus herramientas:
| Método | Comportamiento del servidor |
|---|---|
events/list |
Describe los eventos disponibles y sus filtros. |
events/subscribe |
Crea o renueva una suscripción. |
events/unsubscribe |
Cancela una suscripción. |
Definir un evento
La definición de un evento le indica a ChatGPT a qué pueden suscribirse los usuarios y qué filtros están disponibles. Devuelve estas definiciones en la respuesta a events/list. Incluye el nombre del evento, los modos de entrega admitidos, los argumentos de la suscripción y el esquema de la carga útil.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"events": [
{
"name": "comment.created",
"description": "A new review comment was added to the specified document.",
"delivery": ["webhook"],
"inputSchema": {
"type": "object",
"properties": {
"document_id": {
"type": "string",
"description": "ID of the document to monitor for new review comments."
}
},
"required": ["document_id"],
"additionalProperties": false
},
"payloadSchema": {
"type": "object",
"properties": {
"document_id": { "type": "string" },
"comment_id": { "type": "string" },
"text": { "type": "string" },
"url": { "type": "string" }
},
"required": ["document_id", "comment_id", "text", "url"],
"additionalProperties": false
}
}
]
}
}
inputSchema describe los argumentos que se pasan cuando ChatGPT se suscribe, mientras que payloadSchema describe el objeto data de cada evento entregado.
Usa nombres de eventos estables y descripciones específicas. Ofrece filtros, como los ID de documentos, proyectos o colas, y aplícalos en tu servidor antes de la entrega. Devuelve solo los eventos que la cuenta conectada tiene permiso para descubrir.
Si el catálogo abarca varias páginas, devuelve nextCursor y acepta su valor como cursor en la siguiente solicitud a events/list.
Crear una suscripción
Cuando un usuario pide monitorear un evento, ChatGPT llama a events/subscribe con el nombre del evento, los argumentos de filtrado y el destino del webhook:
{
"jsonrpc": "2.0",
"id": 2,
"method": "events/subscribe",
"params": {
"name": "comment.created",
"arguments": {
"document_id": "doc_123"
},
"delivery": {
"mode": "webhook",
"url": "https://receiver.example.com/mcp-events/callback_123",
"secret": "whsec_<base64-encoded-signing-key>"
},
"cursor": null
}
}
Antes de aceptar la suscripción:
- Comprueba que el usuario tenga autorización para el evento y los argumentos solicitados.
- Valida el nombre del evento y los argumentos según la definición del evento. Exige un secreto de firma con el prefijo
whsec_cuyo valor en base64 tenga entre 24 y 64 bytes al decodificarlo. - Valida y verifica la URL de callback.
- Guarda la suscripción junto con su propietario, sus filtros, la URL de callback, el secreto de firma y la fecha de vencimiento.
Genera un ID de suscripción determinista a partir de la identidad autenticada, la URL de callback, el nombre del evento y los argumentos. Devuelve el ID junto con la fecha de vencimiento que otorgue tu servidor:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"id": "sub_123",
"refreshBefore": "2026-10-02T12:00:00Z",
"cursor": null,
"truncated": false
}
}
Asigna a refreshBefore la fecha de vencimiento que otorgue tu servidor. Devuelve cursor: null para los tipos de eventos que no admiten la reproducción de eventos anteriores.
Haz que la creación de suscripciones sea idempotente. Si ya existe una suscripción con la misma identidad, actualízala. Compara los argumentos mediante JSON canónico para evitar que las diferencias en el orden de las claves de un objeto generen suscripciones duplicadas.
Verificar el callback
Antes de enviar datos de la aplicación, verifica el callback mediante una solicitud firmada con un desafío nuevo, de un solo uso y de corta duración:
{
"type": "verification",
"challenge": "a-single-use-random-value"
}
Asigna a la solicitud de verificación un webhook-id único, como msg_verification_123, y firma el cuerpo con el secreto de la suscripción. Incluye webhook-timestamp, webhook-signature y X-MCP-Subscription-Id. Si la solicitud tiene éxito, ChatGPT devuelve el mismo desafío en la respuesta:
{
"challenge": "a-single-use-random-value"
}
Antes de activar la entrega, exige una respuesta 2xx y compara el desafío devuelto con el original en tiempo constante. Guarda en caché el resultado de la verificación exitosa por identidad autenticada y URL de callback durante un período limitado para evitar que las solicitudes de suscripción repetidas generen nuevos desafíos. Si la verificación falla, devuelve el error JSON-RPC -32015 (CallbackEndpointError) e indica la categoría del error en data.reason, por ejemplo, challenge_failed o timeout.
Exige HTTPS para los callbacks. Resuelve y valida las direcciones de destino al establecer la conexión. Luego, conéctate a la dirección validada y conserva el nombre de host original para la verificación TLS. Bloquea las direcciones privadas, locales y cualquier otra dirección que no sea pública. No sigas redireccionamientos. Aplica estas comprobaciones tanto a las solicitudes de verificación como a las entregas de eventos.
Enviar un evento
Cuando un comentario nuevo coincida con una suscripción activa, envía un solo objeto de evento mediante POST a la URL de callback de esa suscripción:
{
"eventId": "evt_456",
"name": "comment.created",
"timestamp": "2026-10-01T12:05:00Z",
"data": {
"document_id": "doc_123",
"comment_id": "comment_456",
"text": "Can we add the rollout dates to this section?",
"url": "https://docs.example.com/doc_123#comment_456"
},
"cursor": null
}
Usa un ID de evento único y consérvalo en todos los reintentos. Asigna a timestamp la fecha y la hora en que ocurrió el evento, en formato ISO 8601 con zona horaria. El campo name debe coincidir con el nombre del evento al que se suscribió ChatGPT, y el objeto data debe ajustarse a su payloadSchema. Mantén los campos de la aplicación dentro de data. Un campo type en el nivel superior identifica una notificación de control del protocolo.
Para los registros grandes, envía un resumen y ofrece una herramienta de lectura que permita recuperar el registro completo. Trata los comentarios y demás textos escritos por los usuarios como datos. No agregues a la carga útil del evento instrucciones sobre cómo debe comportarse el modelo.
Firmar la solicitud
ChatGPT verifica las entregas mediante Standard Webhooks. Envía estos encabezados:
| Encabezado | Valor |
|---|---|
Content-Type |
application/json |
webhook-id |
El mismo valor que el campo eventId del cuerpo. |
webhook-timestamp |
La hora de la firma en segundos Unix. |
webhook-signature |
La firma HMAC de Standard Webhooks. |
X-MCP-Subscription-Id |
El ID que devuelve events/subscribe. |
Firma las entregas con Standard Webhooks y el secreto de firma de la suscripción. La firma abarca el ID del evento, la marca de tiempo de la firma y los bytes exactos del cuerpo de la solicitud. Por eso, serializa el cuerpo una sola vez y envía esos mismos bytes.
Enviar un evento firmado con Node.js
Instala la biblioteca de Standard Webhooks:
npm install standardwebhooks
Pasa una función webhookFetch con la misma interfaz que fetch. La función debe validar las direcciones de callback en cada conexión y bloquear los redireccionamientos.
import { Webhook } from "standardwebhooks";
export async function sendEvent(subscription, event, webhookFetch) {
const body = JSON.stringify(event);
if (Buffer.byteLength(body, "utf8") > 256 * 1024) {
throw new Error("Event payload exceeds 256 KiB");
}
const signedAt = new Date();
const signer = new Webhook(subscription.secret);
const response = await webhookFetch(subscription.url, {
method: "POST",
redirect: "error",
signal: AbortSignal.timeout(10_000),
headers: {
"Content-Type": "application/json",
"webhook-id": event.eventId,
"webhook-timestamp": String(Math.floor(signedAt.getTime() / 1000)),
"webhook-signature": signer.sign(event.eventId, signedAt, body),
"X-MCP-Subscription-Id": subscription.id,
},
body,
});
return { accepted: response.ok, status: response.status };
}Asigna a subscription.url y subscription.secret los valores correspondientes del objeto delivery de la solicitud de suscripción.
Procesar las respuestas a las entregas
Una respuesta 2xx confirma la recepción del webhook. ChatGPT procesa el evento de forma asíncrona.
Envía un evento por solicitud. El cuerpo completo de la solicitud no debe superar los 256 KiB (262 144 bytes). ChatGPT puede agrupar eventos entregados por separado en una sola ejecución de la tarea, según la configuración de procesamiento por lotes de esa tarea.
Si se produce un error transitorio, reintenta la entrega con intervalos de espera que aumenten de forma exponencial y limita el número de intentos. Conserva el ID del evento y genera una nueva marca de tiempo de firma y una nueva firma para cada intento. No reintentes las entregas que devuelvan 410 o 413.
Los eventos pueden llegar fuera de orden. Haz que las herramientas de escritura sean idempotentes para evitar que las llamadas repetidas dupliquen los cambios.
Administrar las suscripciones
Conserva el estado de cada suscripción durante todo el período de vigencia que le otorgues, incluso si el servidor se reinicia. Vuelve a comprobar el acceso del usuario durante la vigencia de la suscripción y detén la entrega si se revoca el acceso.
Renovar una suscripción
Para renovar una suscripción próxima a vencer, ChatGPT llama a events/subscribe antes de la fecha indicada en refreshBefore, con la misma identidad de suscripción y el último cursor guardado. Actualiza la suscripción existente y devuelve la nueva fecha de vencimiento en refreshBefore.
Si se omite ttlMs, usa la duración predeterminada de las suscripciones de tu servidor. Si se proporciona, indica la duración solicitada en milisegundos. No otorgues un plazo mayor que el solicitado, salvo que debas aplicar una duración mínima para evitar un exceso de solicitudes de renovación.
ttlMs: null solicita una suscripción sin vencimiento. Devuelve refreshBefore: null solo si concedes esa solicitud. De lo contrario, devuelve una fecha de vencimiento y detén la entrega cuando se cumpla.
Si la solicitud de renovación incluye un nuevo secreto de firma, reemplaza el secreto almacenado. Durante un breve período de rotación, firma con la clave anterior y la nueva. Usa el formato de firmas separadas por espacios de Standard Webhooks.
Para los eventos que admiten reproducción, usa el cursor de la solicitud para reanudar la entrega después de que venza una suscripción o se reinicie el servidor. En las respuestas a las solicitudes de suscripción y en las cargas útiles de los eventos, devuelve un cursor que no omita eventos pendientes de entrega. Devuelve truncated: true cuando el historial solicitado ya no esté disponible. Para los tipos de eventos que no admiten reproducción, devuelve cursor: null. El protocolo no permite recuperar los eventos perdidos durante una interrupción en esos casos.
Cancelar una suscripción
Procesa events/unsubscribe con el nombre del evento, los argumentos y la URL de callback originales:
{
"jsonrpc": "2.0",
"id": 3,
"method": "events/unsubscribe",
"params": {
"name": "comment.created",
"arguments": {
"document_id": "doc_123"
},
"delivery": {
"mode": "webhook",
"url": "https://receiver.example.com/mcp-events/callback_123"
}
}
}
Deja de enviar eventos a la suscripción que coincida con esos datos y devuelve un resultado vacío:
{
"jsonrpc": "2.0",
"id": 3,
"result": {}
}
Haz que la cancelación sea idempotente y verifica que la cuenta conectada tenga permiso para cancelar la suscripción.
Probar en ChatGPT
Una vez que hayas implementado los métodos de eventos y la entrega mediante webhooks, conecta tu servidor MCP a ChatGPT a través de un complemento y prueba todo el ciclo de vida de la suscripción:
-
Confirma que tu servidor anuncie
eventsmedianteinitializeoserver/discovery que, al recibirevents/list, devuelva las definiciones de eventos esperadas. -
Verifica que tus eventos aparezcan junto a tus herramientas en la página del complemento. Vuelve a escanear tu servidor MCP cada vez que modifiques sus herramientas o eventos.
Eventos del complemento. Los eventos detectados aparecen junto a las herramientas en la página del complemento.
-
Inicia un chat en modo Work en la versión web de ChatGPT o selecciona Work y Nube en la aplicación de escritorio. Pídele a ChatGPT que se suscriba a uno de tus eventos e indícale qué debe hacer cuando llegue un evento. También puedes hacer la prueba con un dot.
-
Confirma que tu servidor reciba
events/subscribecon el nombre del evento y los argumentos esperados. -
Comprueba que se verifique correctamente el callback y que se guarde la suscripción.
-
Genera en tu aplicación un evento que coincida con la suscripción y confirma que tu servidor reciba una respuesta
2xxal entregarlo mediante el webhook. -
Verifica que ChatGPT reciba los datos esperados del evento y responda según las instrucciones.
-
Para las suscripciones con filtros, genera un evento que no coincida con los filtros y confirma que no se entregue a la suscripción.
-
Detén el monitoreo en ChatGPT. Confirma que tu servidor procese
events/unsubscribey detenga la entrega.
Prueba también las solicitudes de suscripción repetidas, el vencimiento y la renovación de suscripciones tras un reinicio del servidor, la desconexión de la cuenta, la revocación del acceso a un recurso al que el usuario está suscrito, las firmas no válidas, las entregas duplicadas y las ráfagas de eventos tanto con el procesamiento por lotes activado como desactivado. Si la acción solicitada modifica datos en la aplicación de origen, verifica que los eventos resultantes no generen un ciclo de retroalimentación.
Referencia del protocolo: propuesta de diseño de los eventos MCP.