Quand un serveur MCP vous appelle : le client d'événements expérimental et sa livraison par webhook

Vous avez écrit une boucle de surveillance qui fait demander à l’agent « des nouveaux tickets ? » toutes les dix minutes. La plupart du temps, la réponse est non. Vous avez payé dix appels au modèle pour recevoir dix « non », et le seul ticket qui comptait a attendu jusqu’à dix minutes avant d’être remarqué.
L’extension events de MCP existe précisément pour corriger ce type de problème : le serveur pousse un événement au moment où quelque chose se produit, au lieu que l’agent demande en boucle. Hermes implémente désormais son mode de livraison par webhook — PR #140121, fusionnée le 22 octobre 2026. C’est expérimental et ça vit sur main, mais le chemin est complet : s’abonner, répondre à la poignée de main de vérification, et laisser un événement lancer une vraie exécution de l’agent.
Pourquoi c’était impossible jusqu’ici
Hermes avait déjà la plupart des pièces. L’écouteur webhook du gateway vérifie les signatures whsec_ de Standard Webhooks et charge des routes dynamiques, et ces routes savent livrer un résultat n’importe où. Ce qui manquait, c’était le côté MCP : events/list, events/subscribe, events/unsubscribe, et une boucle de rafraîchissement pour garder les grants vivants.
Sans eux, enregistrer à la main une route webhook auprès d’un serveur d’événements échoue. Le serveur envoie une charge signée {"type":"verification"} ; Hermes la traitait comme un événement ordinaire, démarrait une exécution, répondait 202, et ne renvoyait jamais le challenge. Le serveur concluait que le callback était inutilisable, renvoyait challenge_failed, et l’abonnement mourait.
Trois étapes pour l’activer
mcp_servers:
ambush:
url: "https://example.com/mcp"
events: true # enregistre les outils d'événements pour ce serveur
platforms:
webhook:
enabled: true
extra:
public_url: "https://hermes.example.com"
Trois conditions doivent toutes être réunies :
events: trueest par serveur. Sans ça, les trois outils d’événements n’apparaissent jamais.- La plateforme webhook doit être activée — c’est par là que les événements arrivent.
public_urldoit être en HTTPS. Les serveurs rejettent les callbacks en HTTP simple, donc placez un reverse proxy qui termine le TLS ou un tunnel devant le port 8644.
Les trois outils
Une fois l’interrupteur activé, le serveur gagne trois outils utilitaires :
list_event_types— les types d’événements proposés par le serveur, plus vos abonnements en cours. À interroger avant de deviner des noms.subscribe_event— s’abonner avec les arguments propres à l’événement, ce qu’il faut faire à son arrivée (instructions, jusqu’à 2000 caractères), et où va le résultat de chaque exécution (deliver,deliver_chat_id). Chaque abonnement est approuvé par vous, parce qu’un abonnement est un déclencheur permanent pour des exécutions sans surveillance. L’approbation affiche les instructions et la destination en entier.unsubscribe_event— annuler.
instructions est la partie sur laquelle réfléchir le plus. Ce n’est pas « préviens-moi quand X arrive » ; c’est « quand X arrive, fais ceci ». Écrivez-le vaguement et vous obtenez un agent qui se déclenche à chaque fois et se trompe à chaque fois.
En dessous : une route webhook dynamique
Chaque abonnement est stocké comme une route webhook dynamique — hermes webhook list l’affiche. Elle porte son propre secret de signature whsec_, tandis que le grant et le curseur de rejeu sont à côté, dans mcp_event_subscriptions.json. La route elle-même ne change que si vous vous réabonnez avec d’autres instructions ou une autre destination.
Quelques détails d’exécution à connaître :
- Le challenge de vérification reçoit une réponse sans démarrer d’exécution.
- La déduplication se fait par
eventId, pas par lewebhook-idpropre à chaque tentative. Un serveur qui renvoie le même événement ne déclenche qu’une exécution. - Les grants sont rafraîchis avant l’expiration de la durée accordée par le serveur tant que la connexion est active, et de nouveau à chaque reconnexion.
- Si le serveur met fin à un abonnement, Hermes supprime la route et prévient la cible
deliver. - Un rafraîchissement refusé définitivement — accès révoqué, type d’événement supprimé, arguments devenus invalides — supprime la route ; les autres échecs sont retentés avec un back-off.
La charge de l’événement arrive entière dans l’exécution, jusqu’à 32 000 caractères de JSON, avec une note quand elle est tronquée. Les charges d’événements sont des données externes, donc ces exécutions utilisent le même jeu d’outils webhook restreint que toute autre route webhook.
Cinq façons dont ça tombe en silence
Ce n’est pas « abonnez-vous une fois et oubliez ». Les chemins de panne connus :
- La connexion doit être active. Avec
lazy: trueouidle_timeout_seconds, la connexion n’est pas toujours ouverte, donc un abonnement peut expirer jusqu’à ce que la connexion suivante le réabonne. - Le gateway doit tourner. Si
hermes gateway runn’est pas lancé, aucun événement n’arrive — la raison la plus fréquente pour laquelle un test ressemble à « la fonctionnalité est cassée ». - Les échecs de rafraîchissement font du back-off, mais un refus permanent supprime la route.
- Les limites de débit font tomber des événements. La limite par route de l’écouteur (
platforms.webhook.extra.rate_limit, 30 par minute par défaut) s’applique aussi aux livraisons d’événements, et un serveur peut ne pas réessayer une livraison rejetée. Augmentez-la pour un type d’événement chargé. - Désactiver la route dans le dashboard webhook laisse l’abonnement expirer ; la réactiver le réabonne.
Ce qui n’est explicitement pas pris en charge
La doc est franche sur les trous, donc ne perdez pas de temps dessus :
- Les modes de livraison
polletpushne sont pas implémentés — webhook uniquement. - Un profil secondaire sous
gateway.multiplex_profilesne peut pas s’abonner. Les routes dynamiques se chargent depuis le home du profil de lancement.
Tout le design repose par ailleurs sur le design sketch des événements MCP, encore un brouillon de groupe de travail. C’est pourquoi la fonctionnalité est étiquetée expérimentale : la forme du protocole peut changer.
Quand ça vaut le coup
Si votre besoin est « regarder une page une fois par heure pour voir si elle a changé », un job cron reste plus simple : pas d’URL publique, et il ne tombe pas discrètement quand le gateway s’arrête.
Les événements MCP sont rentables quand l’amont dispose déjà d’un flux d’événements et que la latence compte — de nouveaux tickets dans un tracker, un déploiement en échec, un nouvel élément dans un flux. Dans ces cas, « être prévenu » et « demander toutes les dix minutes » sont deux expériences différentes, et la première supprime en plus une pile d’appels au modèle revenus vides.
Si vous pesez encore push contre poll, notre tutoriel sur les notifications webhook compare les deux. Et coûts d’outils MCP et limites de dépense couvre un autre jeu de nouvelles clés dans le même bloc mcp_servers, que vous configurerez probablement à côté de celles-ci.
Statut de release
La fonctionnalité est sur main uniquement : fusionnée dans les premières heures du 22 octobre 2026, absente de la v0.22.4 livrée et d’aucun tag. Suivez main si vous voulez l’essayer, et attendez-vous à ce que la forme du protocole bouge avant de se stabiliser.