For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navigation principale
Theme
Vue d’ensemble Documentation utilisateur de ChatGPT et Codex Cas d’usage

Événements MCP

Implémentez les abonnements aux événements et leur envoi par webhook sur votre serveur MCP.

Les événements MCP permettent à ChatGPT de s’abonner aux mises à jour de votre serveur MCP, par exemple aux nouveaux messages, aux modifications de contenu ou aux changements de statut. Les utilisateurs choisissent ce que ChatGPT doit surveiller et ce qu’il doit faire lorsqu’une mise à jour arrive.

Cas d’usage Demande de l’utilisateur Événement MCP
Transformer les retours en pull requests Surveillez le canal #product-feedback pour repérer les signalements de bugs et ouvrez des pull requests en brouillon avec les correctifs et les tests. message.created, filtré par channel_id
Modifier un document à partir des commentaires Surveillez les commentaires de relecture sur ce document et apportez les modifications demandées. comment.created, filtré par document_id

Avant de commencer

Les événements MCP sont disponibles dans les discussions en mode Work sur la version Web de ChatGPT, dans les discussions en mode Work de l’application de bureau ChatGPT lorsque l’option Cloud est sélectionnée, ainsi qu’avec les dots. Les règles de l’espace de travail relatives aux plugins et aux tâches déclenchées par des événements s’appliquent.

ChatGPT prend en charge les événements MCP via Streamable HTTP avec initialize, ou via MCP 2.0 HTTP (version du protocole 2026-07-28). Configurez votre serveur dans votre plugin et prévoyez un stockage persistant pour les abonnements ainsi qu’un accès HTTPS sortant aux URL de rappel.

ChatGPT prend en charge l’envoi par webhook et la vérification des URL de rappel décrits dans le projet de spécification des événements MCP. Cette intégration ne prend en charge ni l’interrogation périodique, ni la diffusion en continu, ni les notifications de contrôle gap et terminated prévues dans ce projet.

Fonctionnement

  1. Votre serveur répertorie les événements qu’il prend en charge.
  2. L’utilisateur indique à ChatGPT ce qu’il doit surveiller et comment réagir.
  3. ChatGPT s’abonne auprès de votre serveur MCP et lui fournit une URL de rappel et un secret de signature.
  4. Votre serveur envoie à cette URL les événements qui correspondent à l’abonnement.
  5. ChatGPT reçoit l’événement dans la discussion à l’origine de l’abonnement et réagit conformément aux instructions de l’utilisateur.

Déclarer la prise en charge des événements

Pour découvrir les événements, ChatGPT consulte d’abord les capacités de votre serveur. Pour les versions de MCP qui utilisent l’initialisation, ajoutez events aux capacités renvoyées dans la réponse à initialize :

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {},
      "events": {}
    },
    "serverInfo": {
      "name": "example-server",
      "version": "1.0.0"
    }
  }
}

Pour MCP 2.0 (version du protocole 2026-07-28), ajoutez events aux capacités renvoyées dans la réponse à server/discover :

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"],
    "capabilities": {
      "tools": {},
      "events": {}
    }
  }
}

Implémentez ces trois méthodes de gestion des événements sur le même point de terminaison MCP authentifié que vos outils :

Méthode Comportement du serveur
events/list Décrit les événements disponibles et leurs filtres.
events/subscribe Crée ou renouvelle un abonnement.
events/unsubscribe Met fin à un abonnement.

Définir un événement

La définition d’un événement indique à ChatGPT à quoi les utilisateurs peuvent s’abonner et quels filtres ils peuvent utiliser. Renvoyez ces définitions en réponse à events/list. Pour chaque événement, indiquez son nom, les modes d’envoi pris en charge, les arguments d’abonnement et le schéma de la charge utile.

{
  "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 décrit les arguments que ChatGPT transmet lorsqu’il s’abonne. payloadSchema décrit l’objet data de chaque événement envoyé.

Utilisez des noms d’événements stables et des descriptions précises. Proposez des filtres, par exemple par identifiant de document, de projet ou de file d’attente, et appliquez-les sur votre serveur avant l’envoi. Renvoyez uniquement les événements que le compte connecté est autorisé à découvrir.

Si le catalogue comporte plusieurs pages, renvoyez nextCursor et acceptez sa valeur dans cursor lors de la requête events/list suivante.

Créer un abonnement

Lorsqu’un utilisateur demande à surveiller un événement, ChatGPT appelle events/subscribe en précisant le nom de l’événement, les arguments de filtrage et la destination du 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
  }
}

Avant d’accepter l’abonnement :

  1. Vérifiez que l’utilisateur est autorisé à s’abonner à l’événement avec les arguments demandés.
  2. Validez le nom de l’événement et les arguments au regard de sa définition. Exigez un secret de signature préfixé par whsec_ dont la valeur encodée en base64 donne entre 24 et 64 octets après décodage.
  3. Validez et vérifiez l’URL de rappel.
  4. Enregistrez l’abonnement, son propriétaire, ses filtres, son URL de rappel, son secret de signature et sa date d’expiration.

Calculez un identifiant d’abonnement déterministe à partir de l’identité authentifiée, de l’URL de rappel, du nom de l’événement et des arguments. Renvoyez-le avec la date d’expiration accordée :

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "id": "sub_123",
    "refreshBefore": "2026-10-02T12:00:00Z",
    "cursor": null,
    "truncated": false
  }
}

Renseignez refreshBefore avec la date d’expiration accordée par votre serveur. Renvoyez cursor: null pour les types d’événements qui ne prennent pas en charge la relecture.

Rendez la création d’abonnement idempotente : si la demande correspond à un abonnement existant, mettez celui-ci à jour. Comparez les arguments sous forme de JSON canonique pour éviter qu’une différence dans l’ordre des clés d’un objet ne crée un abonnement en double.

Vérifier l’URL de rappel

Avant d’envoyer des données applicatives, vérifiez l’URL de rappel en envoyant une requête signée contenant une nouvelle valeur de défi, à usage unique et de courte durée de validité :

{
  "type": "verification",
  "challenge": "a-single-use-random-value"
}

Attribuez à la requête de vérification un webhook-id unique, par exemple msg_verification_123, et signez son corps avec le secret de l’abonnement. Incluez les en-têtes webhook-timestamp, webhook-signature et X-MCP-Subscription-Id. Si la requête aboutit, ChatGPT renvoie la valeur du défi dans sa réponse :

{
  "challenge": "a-single-use-random-value"
}

Exigez une réponse 2xx et comparez en temps constant la valeur renvoyée à celle du défi avant d’activer l’envoi des événements. Pour chaque paire formée par l’identité authentifiée et l’URL de rappel, mettez en cache le résultat d’une vérification réussie pour une durée limitée. Vous éviterez ainsi de relancer un défi à chaque nouvelle demande d’abonnement pour cette paire. Si la vérification échoue, renvoyez l’erreur JSON-RPC -32015 (CallbackEndpointError) et indiquez la catégorie de l’échec dans data.reason, par exemple challenge_failed ou timeout.

Exigez HTTPS pour les URL de rappel. Résolvez et validez les adresses de destination au moment de la connexion, puis connectez-vous à l’adresse validée en conservant le nom d’hôte d’origine pour la vérification TLS. Bloquez les adresses privées, locales et toute autre adresse non publique. Ne suivez pas les redirections. Appliquez ces contrôles aux requêtes de vérification comme aux envois d’événements.

Envoyer un événement

Lorsqu’un nouveau commentaire correspond à un abonnement actif, envoyez un seul objet événement par requête POST à l’URL de rappel de cet abonnement :

{
  "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
}

Utilisez un identifiant unique pour chaque événement et conservez-le lors des nouvelles tentatives. Renseignez timestamp avec la date et l’heure auxquelles l’événement s’est produit, au format ISO 8601 avec indication du fuseau horaire. Le champ name doit correspondre à l’événement visé par l’abonnement, et l’objet data doit respecter son schéma payloadSchema. Placez les champs applicatifs dans data. Un champ type à la racine indique une notification de contrôle du protocole.

Pour les enregistrements volumineux, envoyez un résumé et proposez un outil de lecture permettant de récupérer l’enregistrement complet. Traitez les commentaires et les autres textes rédigés par les utilisateurs comme des données. N’ajoutez pas dans la charge utile de l’événement d’instructions dictant au modèle son comportement.

Signer la requête

ChatGPT vérifie les envois à l’aide de Standard Webhooks. Envoyez les en-têtes suivants :

En-tête Valeur
Content-Type application/json
webhook-id Même valeur que le champ eventId du corps de la requête.
webhook-timestamp Horodatage Unix de la signature, en secondes.
webhook-signature Signature HMAC conforme à Standard Webhooks.
X-MCP-Subscription-Id Identifiant renvoyé par events/subscribe.

Signez chaque envoi à l’aide de Standard Webhooks et du secret de signature de l’abonnement. La signature couvre l’identifiant de l’événement, l’horodatage de la signature et la séquence exacte d’octets du corps de la requête. Sérialisez donc le corps une seule fois et envoyez ces mêmes octets.

Envoyer un événement signé avec Node.js

Installez la bibliothèque Standard Webhooks :

npm install standardwebhooks

Passez une fonction webhookFetch qui présente la même interface que fetch, valide les adresses de rappel à chaque connexion et bloque les redirections.

Envoyer un événement signé
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 };
}

Renseignez subscription.url et subscription.secret à partir de l’objet delivery de la demande d’abonnement.

Traiter les réponses aux envois

Une réponse 2xx confirme la réception du webhook. ChatGPT traite l’événement de manière asynchrone.

Envoyez un événement par requête. La taille totale du corps de la requête ne doit pas dépasser 256 Kio (262 144 octets). Selon les paramètres de traitement par lots de la tâche, ChatGPT peut regrouper plusieurs événements reçus séparément et les traiter en une seule exécution.

En cas d’échec transitoire, réessayez en augmentant le délai de façon exponentielle et en limitant le nombre de tentatives. Conservez l’identifiant de l’événement et générez un nouvel horodatage de signature et une nouvelle signature à chaque tentative. Ne réessayez pas si l’envoi reçoit une réponse 410 ou 413.

Les événements peuvent arriver dans le désordre. Rendez les outils d’écriture idempotents pour éviter que des appels répétés n’appliquent plusieurs fois la même modification.

Gérer les abonnements

Conservez l’état de chaque abonnement pendant toute la durée accordée, y compris en cas de redémarrage du serveur. Vérifiez à nouveau les droits d’accès de l’utilisateur pendant cette période et cessez les envois si l’accès est révoqué.

Renouveler un abonnement

Pour renouveler un abonnement qui arrive à expiration, ChatGPT appelle events/subscribe avant l’échéance indiquée dans refreshBefore, avec la même identité d’abonnement et le dernier curseur enregistré. Mettez à jour l’abonnement existant et renvoyez sa nouvelle date d’expiration dans refreshBefore.

Si la demande omet ttlMs, utilisez la durée d’abonnement par défaut de votre serveur. Si ce paramètre est présent, sa valeur numérique indique la durée demandée en millisecondes. Ne dépassez pas cette durée, sauf si vous imposez une durée minimale pour éviter des demandes de renouvellement trop fréquentes.

ttlMs: null correspond à une demande d’abonnement sans expiration. Renvoyez refreshBefore: null uniquement si vous acceptez cette demande. Sinon, renvoyez une date d’expiration et cessez les envois à cette échéance.

Si la demande de renouvellement fournit un nouveau secret de signature, remplacez le secret enregistré. Pendant une courte période de transition, signez les requêtes avec l’ancienne et la nouvelle clé, en séparant les deux signatures par une espace conformément à Standard Webhooks.

Pour les événements qui prennent en charge la relecture, utilisez le cursor de la requête pour reprendre les envois après l’expiration d’un abonnement ou le redémarrage du serveur. Dans les réponses aux demandes d’abonnement et les charges utiles des événements, renvoyez un curseur qui ne saute aucun événement encore en attente d’envoi. Renvoyez truncated: true si l’historique demandé n’est plus disponible. Pour les types d’événements sans relecture, renvoyez cursor: null. Le protocole ne permet pas de récupérer les événements manqués pendant une interruption.

Mettre fin à un abonnement

Traitez events/unsubscribe à partir du nom de l’événement, des arguments et de l’URL de rappel de la demande d’abonnement initiale :

{
  "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"
    }
  }
}

Cessez d’envoyer les événements correspondant à cet abonnement et renvoyez un résultat vide :

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {}
}

Rendez le désabonnement idempotent et vérifiez que le compte connecté est autorisé à mettre fin à l’abonnement.

Tester dans ChatGPT

Une fois les méthodes de gestion des événements et l’envoi par webhook en place, connectez votre serveur MCP à ChatGPT via un plugin et testez le cycle de vie complet d’un abonnement :

  1. Vérifiez que votre serveur déclare events dans sa réponse à initialize ou à server/discover, puis qu’il reçoit events/list et renvoie les définitions d’événements attendues.

  2. Vérifiez que vos événements figurent aux côtés de vos outils sur la page de votre plugin. Relancez l’analyse de votre serveur MCP chaque fois que vous modifiez ses outils ou ses événements.

    Page de détails du plugin présentant les événements message.created et comment.created aux côtés des outils MCP.

    Événements du plugin. Les événements découverts figurent aux côtés des outils sur la page du plugin.

  3. Démarrez une discussion en mode Work dans la version Web de ChatGPT ou sélectionnez Work et Cloud dans l’application de bureau ChatGPT. Demandez à ChatGPT de s’abonner à l’un de vos événements et précisez ce qu’il doit faire lorsqu’il en reçoit un. Vous pouvez également effectuer ce test avec un dot.

  4. Vérifiez que votre serveur reçoit events/subscribe avec le nom de l’événement et les arguments attendus.

  5. Assurez-vous que la vérification de l’URL de rappel aboutit et que le serveur enregistre l’abonnement.

  6. Déclenchez dans votre application un événement correspondant à l’abonnement et vérifiez que l’envoi par webhook reçoit une réponse 2xx.

  7. Vérifiez que ChatGPT reçoit les données attendues et réagit conformément aux instructions.

  8. Pour les abonnements avec filtres, déclenchez un événement qui ne correspond pas aux filtres et vérifiez que le serveur ne l’envoie pas au titre de cet abonnement.

  9. Arrêtez la surveillance dans ChatGPT. Vérifiez que votre serveur traite events/unsubscribe et cesse les envois.

Testez également les demandes d’abonnement répétées, l’expiration et le renouvellement de part et d’autre d’un redémarrage du serveur, la déconnexion du compte, la révocation de l’accès à une ressource surveillée, les signatures non valides et les envois en double. Testez les rafales d’événements avec le traitement par lots activé, puis désactivé. Si l’action demandée modifie des données dans l’application source, vérifiez que les événements qui en résultent ne créent pas de boucle de rétroaction.

Référence du protocole : ébauche de spécification des événements MCP.