Abonnez-vous aux événements via MCP
Un agent IA connecté à Kanera via MCP peut demander à être informé des changements de travail, au lieu de procéder à une interrogation. Kanera implémente la livraison webhook du brouillon Événements MCP extension : l'hôte de l'agent enregistre un rappel HTTPS, Kanera le vérifie, et à partir de là, chaque modification correspondante y est publiée en tant qu'occurrence JSON signée.
C'est le mécanisme derrière « surveillez ce forum et dites-moi quand un commentaire arrive » dans un client tel que ChatGPT. L'hôte est propriétaire du rappel ; vous n'exécutez pas de serveur et ne collez pas d'URL n'importe où dans Kanera.
À quoi un agent peut s'abonner
| Événement | Se déclenche quand | Champs supplémentaires dans data |
|---|---|---|
card.created | Une carte est créée dans le périmètre surveillé. | title, listId, url |
card.updated | Une carte change : titre, description, achèvement, état de l'archive et autres champs de la carte. | title, listId, url |
card.moved | Une carte se déplace dans ou entre des listes ou des tableaux. | listId (la destination), fromListId (la source), prevPosition, url |
card.blocked | Une carte active gagne son premier bloqueur ouvert, y compris la réouverture ou la restauration. | title, listId, url; aucun détail sur le bloqueur |
card.unblocked | Une carte active perd son dernier bloqueur ouvert en complétant, en archivant, en dissociant ou en supprimant le tableau ou la liste du bloqueur. | title, listId, url; aucun détail sur le bloqueur |
schedule.conflict_detected | Une carte ouverte gagne son premier conflit de planification : un bloqueur ouvert est prévu pour se terminer au moment ou après son début. | blockerCardId, days (jours ouvrables pendant lesquels la personne à charge devra déménager) |
schedule.conflict_resolved | Une carte ouverte perd son dernier conflit de planification. | Aucun champ de planification supplémentaire |
schedule.critical_path_slipped | La fin prévue d'une carte de chemin critique se déplace plus tard et retarde la fin prévue de son plateau. | previousProjectedFinish, projectedFinish, days, slipDays |
comment.created | Un commentaire est déposé sur une carte. | commentId, text (jusqu'à 8 000 caractères), url |
priorities.changed | Le vôtre À suivre la file d'attente a peut-être changé. | Aucun. Voir Votre prochaine file d'attente. |
my_day.ready | C'est un matin de semaine dans votre fuseau horaire. | localDate, timeZone. Voir Votre briefing du matin. |
reminder.due | L'un des vôtres rappels de cartes vient à échéance. | reminderId, cardId, remindAt, url. Voir Vos rappels de carte. |
Chaque occurrence de carte et de commentaire porte workspaceId, boardId, cardId, et un actor (voir Dire vos propres écritures à part). Les charges utiles sont des résumés limités : la description d'une carte, les pièces jointes et le fil de commentaires complet ne sont jamais inclus. L'agent lit l'enregistrement en cours avec cards.get ou comments.list quand il en a besoin de plus.
Pour les événements de carte, de commentaire et de planification, abonnement arguments affiner ce qui est livré :
| Argumentation | Obligatoire | Effet |
|---|---|---|
workspaceId | Oui | Uniquement les événements dans cet espace de travail. |
boardId | Non | Uniquement les événements sur ce forum. Obligatoire pour les invités du conseil. |
listId | Non | Uniquement les événements pour les cartes de cette liste d'espace de travail. Pour card.moved, une carte entrant ou sortant de la liste correspond et les réorganisations à l'intérieur de celle-ci ne le font pas. Omettre boardId pour regarder la liste partagée sur chaque tableau accessible. |
cardId | Non | Uniquement les événements pour cette carte. Nécessite boardId. |
L'accès suit l'identité connectée à chaque livraison, pas seulement au moment de l'abonnement. Un membre de l'espace de travail peut surveiller l'intégralité de l'espace de travail ; un invité inter-organisation doit nommer un conseil auquel il a été invité ; un invité limité aux éléments attribués doit nommer une carte à laquelle il est actuellement attribué. Si l’identité perd ultérieurement l’accès à la portée, la livraison s’arrête et l’abonnement expire. La perte de l'accès à une carte dans le cadre d'un abonnement à l'échelle du conseil scolaire ignore uniquement cette occurrence.
Transitions de dépendance
card.blocked et card.unblocked suivre dépendances de carte, y compris les modifications apportées aux personnes, les intégrations, les automatisations et les cartes en miroir. Ils ne nécessitent pas de règle d'automatisation. L'ajout d'un autre bloqueur ou la suppression d'un seul bloqueur parmi plusieurs ne répète pas la transition. Chaque direction est limitée à une fois par carte et par transaction.
L'événement décrit la carte dépendante et omet les détails du bloqueur pour protéger les tableaux privés. Lire cards.get pour les relations actuelles visibles à votre connexion. Vérifiez l'état actuel avant d'agir, car un automatisme a peut-être déjà déplacé ou complété la carte.
Planifier les transitions
Les trois schedule.* les événements utilisent le même moteur de journée de travail que le Vue chronologique et work.schedule. Ils nécessitent que la chronologie et les dépendances soient activées dans les paramètres effectifs de l'organisation/de l'espace de travail, et ne nécessitent pas de règle d'automatisation.
schedule.conflict_detectedest la transition d'aucun conflit d'horaire à au moins un.cardIdnomme la personne à charge,blockerCardIdnomme sa pire condition préalable contradictoire, etdaysC'est le nombre de jours ouvrables du lundi au vendredi dont la personne à charge aurait besoin pour se déplacer vers la droite. L'ajout d'un autre conflit ne le répète pas.schedule.conflict_resolvedsignifie qu'une carte ouverte n'a plus de conflits, par exemple après un changement de dates, la fin d'un bloqueur ou la suppression d'une dépendance. Supprimer un seul conflit parmi plusieurs ne le déclenche pas. Une récidive ultérieure peut déclencher à nouveau la détection.schedule.critical_path_slippednécessite à la fois que la fin prévue d'une carte critique se déplace plus tard et que la fin projetée du plateau se déplace plus tard. Un changement de date absorbé par Slack n’est pas admissible, pas plus que le simple ajout de nouveaux travaux.daysmesure la distance parcourue par la projection ;slipDaysmesure son retard par rapport à la dernière échéance. Une projection peut se déplacer plus tard tandis queslipDaysreste nul.
L'évaluation initiale du calendrier établit une base de référence plutôt que d'annoncer tous les conflits existants. Les événements signalent les limites ultérieures ; utiliser work.schedule pour l'état actuel lors du démarrage d'une surveillance ou de la récupération de livraisons manquées. Relisez-le lors d'un événement avant de suggérer des modifications. Ces alertes ne reprogramment pas les cartes.
Pour un hôte personnalisé, abonnez-vous à chaque nom d'événement séparément. Cette requête surveille d'abord les conflits sur une carte ; remplacez les UUID réels de l'espace de travail et de la carte par le secret de rappel et de signature vérifié de l'hôte :
{
"jsonrpc": "2.0",
"id": 3,
"method": "events/subscribe",
"params": {
"name": "schedule.conflict_detected",
"arguments": { "workspaceId": "…", "boardId": "…" },
"delivery": {
"mode": "webhook",
"url": "https://receiver.example.com/kanera/events",
"secret": "whsec_…"
},
"ttlMs": 86400000
}
}
Une occurrence de dérapage du chemin critique ressemble à ceci (les identifiants et les dates sont illustratifs) :
{
"eventId": "evt_…_schedule.critical_path_slipped",
"name": "schedule.critical_path_slipped",
"timestamp": "2026-11-02T10:00:00.000Z",
"data": {
"workspaceId": "…",
"boardId": "…",
"cardId": "…",
"previousProjectedFinish": "2026-11-06",
"projectedFinish": "2026-11-09",
"days": 1,
"slipDays": 1,
"actor": { "kind": "user", "userId": "…", "self": false }
},
"cursor": null
}
Les dates sont YYYY-MM-DD; les durées, les marges et le nombre de glissements excluent les week-ends. Les événements de carte et de planification peuvent décrire la même modification sous-jacente mais avoir des ID d'événement distincts. Tentatives de déduplication par eventId et vérifie actor.self avant de réagir à vos propres changements. Voir exemples de planification.
Votre prochaine file d'attente
priorities.changed indique à un agent que la file d'attente Up next de l'utilisateur connecté a peut-être changé, afin qu'il puisse relire la file d'attente au lieu de l'interroger. Il se déclenche à chaque modification qui réorganise ou redimensionne la file d'attente :
- une entrée est ajoutée, déplacée ou supprimée, que ce soit depuis l'application Web, l'API ou un autre agent ;
- une carte en file d'attente est complétée, archivée, restaurée ou réaffectée, ce qui la retire de la file d'attente en direct ou la remet.
Cela ne prend aucun argument (passez {}) et n'est jamais limité à un espace de travail, car la file d'attente s'étend sur tous les espaces de travail dans lesquels vous travaillez. L'occurrence ne porte que sur la personne qui l'a modifiée :
"data": {
"targetUserId": "…",
"actor": { "kind": "user", "userId": "…", "self": false }
}
targetUserId est toujours l'utilisateur connecté. Rien sur les cartes de la file d'attente n'est inclus. Appeler priorities.list pour le classement actuel ; il applique la visibilité normale de votre carte.
Vous pouvez seulement regarder le vôtre file d'attente. Il n'y a aucun argument en faveur d'une autre personne, et cela inclut les administrateurs d'espace de travail qui peuvent lire la file d'attente d'un coéquipier avec priorities.list. Même une notification sans contenu révélerait quand et par qui une carte que l'observateur ne peut pas voir a été complétée ou réaffectée. Un invité ou une clé API en lecture seule peut également surveiller sa propre file d'attente.
Votre briefing du matin
my_day.ready permet à un agent de vous informer chaque matin de travail sans son propre planificateur. Kanera l'envoie une fois par jour, du lundi au vendredi, à 08h00 dans le fuseau horaire de votre profil, à la même heure que le e-mail de résumé quotidien. Il suit le paramètre du résumé : tant que votre e-mail de résumé quotidien est désactivé, aucun ping n'est envoyé. Il est envoyé que quelque chose soit dû ou non, afin que l'agent puisse toujours signaler un jour franc.
Cela ne prend aucun argument (passez {}) et ne décrit que votre propre journée. L'événement n'entraîne aucun travail :
"data": {
"targetUserId": "…",
"localDate": "2026-10-12",
"timeZone": "Europe/London",
"actor": { "kind": "system", "userId": null, "self": false }
}
L'agent appelle alors work.my_day avec ça timeZone. Un seul appel renvoie vos cartes en retard, les cartes dues dans les sept prochains jours, les cartes avec des éléments de liste de contrôle en retard, les cartes attribuées qui sont inactives depuis 14 jours ou plus et le haut de votre file d'attente Up next, le tout vérifié par rapport à votre accès actuel.
Le briefing revient également schedule.conflicts, schedule.critical, et schedule.lateToStart: vos cartes assignées avec des conflits, les cartes du chemin critique sans conflits et les cartes dont le début prévu est passé sans jamais entrer dans la progression. Les contrôles à démarrage tardif nécessitent un suivi du temps d'avancement et une liste En cours, et excluent les cartes déjà en retard.
Un abonnement dure jusqu'à 24 heures par actualisation, le client doit donc continuer à l'actualiser pour recevoir le ping du lendemain matin. ChatGPT fait cela pendant que la tâche est active.
Vos rappels de carte
reminder.due permet à un agent d'agir lorsqu'un de vos rappels de cartes se déclenche, par exemple pour publier la carte dans un chat ou commencer à travailler dessus. Les rappels sont définis depuis le bouton d'alarme de la carte, ou par un agent avec cards.set_reminder. Cela ne prend aucun argument (passez {}) et ne couvre que vos propres rappels ; personne ne peut s'abonner à celui de quelqu'un d'autre.
Kanera vérifie les rappels dus toutes les 30 secondes et envoie une occurrence par rappel. La reprogrammation d'un rappel qui a déjà tiré l'arme à nouveau, et le prochain tir est un nouvel événement. L'occurrence nomme la carte mais ne comporte aucun contenu :
"data": {
"targetUserId": "…",
"reminderId": "…",
"cardId": "…",
"remindAt": "2026-10-16T07:00:00.000Z",
"url": "https://board.kanera.app/o/…/c/MKT-42",
"actor": { "kind": "system", "userId": null, "self": false }
}
L'agent appelle cards.get avec le cardId pour la carte actuelle. Des chèques de livraison dont la carte est encore visible ; un rappel sur une carte archivée depuis, ou à laquelle vous ne pouvez plus accéder, se déclenche sans notification ni événement.
Surveillez le travail pour les changements dans ChatGPT
Installez et connectez le plugin Kanera à l'aide du Guide de configuration de ChatGPT, puis sélectionnez-le dans une conversation professionnelle prenant en charge les tâches basées sur des événements. Un hôte compatible découvre automatiquement la capacité des événements. Pour une connexion MCP personnalisée, ouvrez-la dans Plugins et sélectionnez Actualiser après les changements de serveur. Les mises à jour publiées des outils de plug-in suivent le processus de révision d'OpenAI.
Dans une conversation à laquelle l'application Kanera est jointe, demandez ce que vous souhaitez qu'il se passe :
Watch the Product board in Kanera. When a new comment appears on any card, summarise it for me and suggest a reply.
Tell me whenever a card moves into the Done list on the Launch board.
Whenever my Up next queue changes, tell me what is now at the top.
Every weekday morning, brief me on what is overdue, what is due this week, and what is at the top of my Up next queue.
Watch the Launch board for scheduling conflicts and critical-path slips. Read the current schedule when one appears, tell me which cards and dates are affected, and propose a repair. Tell me when conflicts clear. Do not change dates.
ChatGPT crée l'abonnement sur son propre rappel hébergé, l'actualise pendant que la tâche est active et y met fin lorsque vous arrêtez la tâche. Kanera n'envoie jamais votre travail partout où ChatGPT n'a pas prouvé qu'il contrôle (voir Vérification du rappel).
Des exemples plus concrets, notamment répondre aux questions sur les cartes, trier les nouveaux travaux et garder une seule carte, sont disponibles. Exemples d'utilisation.
Deux choses à savoir lors de la rédaction de la tâche :
- Évitez les boucles. Chaque occurrence nomme son acteur et indique si la connexion d'abonnement l'a provoquée. Une tâche qui réagit aux commentaires en commentant doit vérifier
actor.selfavant d'écrire à nouveau, sinon il répondra tout seul. - Lisez avant d'écrire. L'événement est un résumé. Demandez à la tâche de lire d'abord la carte et d'utiliser les clés d'idempotence des outils pour des modifications reproductibles.
La livraison s'effectue au mieux avec une fenêtre de nouvelle tentative limitée (voir Livraison et tentatives). Si le rappel de ChatGPT est inaccessible pendant une période plus longue, l'occurrence est manquée ; la tâche est récupérée en lisant l'état actuel.
Le protocole
Les événements sont disponibles uniquement sur le point de terminaison HTTP hébergé, https://mcp.kanera.app/mcp ou celui de votre propre déploiement /mcp, et uniquement aux clients utilisant le protocole MCP actuel, ce qui ajoute des abonnements aux événements. kanera mcp over stdio ne les propose pas : un hôte stdio local n'a pas de rappel hébergé vers lequel livrer.
server/discover annonce la capacité sous les deux capabilities.events (ce que lit ChatGPT aujourd'hui) et capabilities.extensions["io.modelcontextprotocol/events"] (là où la proposition d'extension le place). Trois méthodes de requête suivent, authentifiées exactement comme les appels d'outils, avec un jeton d'accès OAuth ou une clé API comme support. Les exemples ci-dessous montrent uniquement les paramètres de la méthode ; la bibliothèque client ajoute les en-têtes et les métadonnées par requête du protocole.
events/list
Renvoie le catalogue ci-dessus avec un schéma JSON pour les arguments et la charge utile de chaque événement. Le catalogue est une seule page ; cursor doit être absent ou vide.
{
"jsonrpc": "2.0",
"id": 1,
"method": "events/list",
"params": {}
}
events/subscribe
{
"jsonrpc": "2.0",
"id": 2,
"method": "events/subscribe",
"params": {
"name": "comment.created",
"arguments": { "workspaceId": "…", "boardId": "…" },
"delivery": {
"mode": "webhook",
"url": "https://receiver.example.com/kanera/events",
"secret": "whsec_…"
},
"ttlMs": 86400000
}
}
| Champ | Règles |
|---|---|
delivery.mode | Seulement webhook est pris en charge. Tout autre mode revient -32014 Unsupported avec data.feature = "deliveryMode". |
delivery.url | HTTPS, aucune information d'identification dans l'URL, aucun fragment et il doit être résolu en une adresse publique. Les adresses de bouclage, privées, de lien local, de métadonnées cloud et IPv4 mappées ou de transition IPv6 sont refusées. |
delivery.secret | whsec_ suivi de 24 à 64 octets de base64. Kanera le stocke crypté et signe chaque livraison avec. |
ttlMs | Durée de vie demandée. La valeur par défaut et la durée maximale sont de 24 heures ; une valeur positive plus courte est honorée, et null (pas d’expiration) est plafonné à 24 heures. |
cursor, maxAgeMs | Accepté pour compatibilité. Kanera ne propose pas de replay : cursor est toujours renvoyé comme null, et un curseur de requête non nul est signalé comme truncated: true. |
La réponse :
{
"id": "sub_…",
"refreshBefore": "2026-10-07T13:00:00.000Z",
"cursor": null,
"truncated": false,
"deliveryStatus": {
"active": true,
"lastDeliveryAt": "2026-10-06T12:58:10.000Z",
"lastError": null
}
}
L'abonnement id est déterministe pour la connexion, l'URL de rappel, le nom de l'événement et les arguments (par exemple priorities.changed, my_day.ready, et reminder.due, l'utilisateur connecté), appelant ainsi events/subscribe encore une fois avec la même identité est un actualiser: il s'étend refreshBefore, accepte un nouveau secret (l'ancien et le nouveau signent les livraisons pendant cinq minutes), et reviennent deliveryStatus pour un abonnement encore actif. deliveryStatus est omis lors d'un premier abonnement et lors d'un renouvellement après expiration. Actualiser avant refreshBefore; un abonnement expiré et renouvelé plus tard recommence à partir de l'heure de renouvellement, sans rejouer ce qu'il a manqué.
active est toujours true: Kanera ne suspend jamais un abonnement en cas d'échec de livraison. Il limite les tentatives par occurrence et signale l'état du point de terminaison via lastError (l'un des connection_refused, timeout, tls_error, http_4xx, http_5xx, challenge_failed) et failedSince, présent alors que le point de terminaison continue d'échouer.
Une connexion peut contenir jusqu'à 100 abonnements actifs. Le 101ème revient -32013 ResourceExhausted.
events/unsubscribe
Prend la même chose name, arguments, et delivery.url, sans secret. Seule la connexion qui a créé un abonnement peut y mettre fin ; la demande d'un autre identifiant est une non-opération. La désinscription est idempotente.
Erreurs
Kanera utilise les codes d'erreur des projets de documents pour les événements. Les échecs d’abonnement sont structurés data afin qu'un hôte puisse agir sur eux sans analyser les messages.
| Coder | Nom | Quand |
|---|---|---|
-32011 | NotFound | name n'est pas dans le catalogue (data.kind = "event"). |
-32012 | Forbidden | L'identité ne peut pas voir la portée demandée, ou les informations d'identification ne sont pas une clé API ou une connexion OAuth. |
-32013 | ResourceExhausted | La limite de souscription (data.max) ou une limite de taux. |
-32014 | Unsupported | Un mode de livraison autre que webhook (data.feature, data.value). |
-32015 | CallbackEndpointError | La vérification a échoué. data.reason est l'un des connection_refused, timeout, tls_error, http_4xx, http_5xx, ou challenge_failed. Une destination refusée est signalée comme challenge_failed, les réponses d'erreur ne peuvent donc pas être utilisées pour mapper un réseau privé. |
-32602 | InvalidParams | Une demande mal formée, un secret ou une URL non valide, un espace de travail, un tableau ou une carte dans arguments qui n'existe pas, ou tout argument passé à priorities.changed, my_day.ready, ou reminder.due. |
Vérification du rappel
Avant qu'un abonnement ne soit stocké, Kanera prouve que le rappel vous appartient. Il publie une demande de vérification à delivery.url, signé avec le secret que vous avez fourni :
{ "type": "verification", "challenge": "…" }
Le point final doit répondre 2xx avec un corps JSON qui fait écho exactement au défi :
{ "challenge": "…" }
Tout le reste échoue à l'abonnement avec -32015 et la raison ci-dessus. Les redirections ne sont jamais suivies. Une vérification réussie est mise en cache pendant cinq minutes par connexion et URL de rappel, donc rafraîchissante ou variable arguments ne répète pas le défi.
La vérification s'exécute avec la même politique de destination stricte que la livraison : adresses HTTPS publiques uniquement, résolues une fois et épinglées pour la connexion afin qu'un changement DNS entre la vérification et la demande ne puisse pas la rediriger.
Livraison et tentatives
Chaque occurrence est une POST au rappel avec Content-Type: application/json:
{
"eventId": "evt_…",
"name": "comment.created",
"timestamp": "2026-10-06T12:58:09.412Z",
"data": {
"workspaceId": "…",
"boardId": "…",
"cardId": "…",
"commentId": "…",
"text": "Can we ship this on Thursday?",
"url": "https://board.kanera.app/o/3F2A9C1D7B4E6A08/c/PROJ-123",
"actor": { "kind": "user", "userId": "…", "self": false }
},
"cursor": null
}
| En-tête | Contenu |
|---|---|
webhook-id | Le eventId. Stable au fil des tentatives ; utilisez-le pour rendre le gestionnaire idempotent. |
webhook-timestamp | Secondes Unix lorsque cette tentative a été signée. Frais à chaque nouvelle tentative. |
webhook-signature | Un ou plusieurs espaces séparés v1,<base64> signatures (deux lors d'une rotation secrète). |
X-MCP-Subscription-Id | L'abonnement id. |
Les octets du corps sont identiques à chaque tentative ; seuls l'horodatage et la signature changent.
Répondre 2xx reconnaître. Kanera traite les réponses comme suit :
| Réponse | Comportement |
|---|---|
2xx | Livré. Le corps est ignoré. |
410, 413, et d'autres 4xx | Cette occurrence est supprimée. L'abonnement continue ; les occurrences futures sont toujours livrées. |
408, 425, 429, 5xx, timeout, TLS ou échec de connexion | Réessayé avec le même eventId: cinq tentatives au total à environ 0 s, 30 s, 1,5 min, 3,5 min et 7,5 min. Après cela, l'occurrence est supprimée. |
Un corps de réponse supérieur à 4 Ko est supprimé sans affecter le résultat. Une occurrence supérieure à 256 Ko n’est jamais envoyée. Kanera conserve les enregistrements de livraison pendant 14 jours.
Les livraisons pour un abonnement actif survivent au redémarrage de Kanera. L'autorisation est revérifiée avant chaque tentative, de sorte qu'un agent déconnecté, une clé révoquée, un membre supprimé ou un invité qui a perdu une affectation cesse de recevoir des données à la prochaine tentative, et non à la prochaine actualisation.
priorities.changed les occurrences sont mises en file d'attente au moment où la modification de la file d'attente est enregistrée, et non à partir du journal des modifications durable utilisé par les événements de carte, car la file d'attente n'est pas liée à un seul espace de travail. Dans les rares cas où Kanera redémarre à cet instant, une notification peut être perdue ; le prochain priorities.list la lecture est toujours d’actualité.
Vérifier la signature
Le schéma est le Webhooks standards mise en page. Décodez le secret après le whsec_ préfixe de base64, HMAC-SHA256 la chaîne {webhook-id}.{webhook-timestamp}.{body}, et comparez avec chaque signature de l'en-tête en temps constant :
import crypto from "node:crypto";
import express from "express";
const app = express();
const key = Buffer.from(process.env.KANERA_MCP_EVENT_SECRET!.slice("whsec_".length), "base64");
app.post("/kanera/events", express.raw({ type: "application/json" }), (req, res) => {
const body = req.body.toString("utf8");
const payload = JSON.parse(body) as { type?: string; challenge?: string };
const id = req.header("webhook-id") ?? "";
const timestamp = req.header("webhook-timestamp") ?? "";
const expected = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest("base64");
const digest = Buffer.from(expected);
const valid = (req.header("webhook-signature") ?? "").split(" ").some((entry) => {
const received = Buffer.from(entry.replace(/^v1,/, ""));
return received.length === digest.length && crypto.timingSafeEqual(received, digest);
});
if (!valid) return res.status(401).send("invalid signature");
// Verification challenge: echo it back.
if (payload.type === "verification") return res.json({ challenge: payload.challenge });
// Occurrence: handle it idempotently by webhook-id.
res.sendStatus(204);
});
Vérifiez toujours par rapport au corps brut. Rejeter les livraisons dont webhook-timestamp est dans plus de quelques minutes pour une rediffusion limitée.
Dire vos propres écritures à part
Un agent qui réagit aux événements en réécrivant verra ses propres écritures se présenter sous forme d'événements. userId à lui seul, ne peut pas les séparer : une clé API personnelle ou un agent OAuth agit en tant qu'utilisateur, donc un commentaire posté par l'agent et un commentaire posté par la personne portent la même chose userId.
Chaque événement comprend donc :
"actor": { "kind": "apiKey", "userId": "…", "self": true }
| Champ | Signification |
|---|---|
kind | user (l'application web), apiKey, agent (un agent OAuth interactif), support, automation, ou system. |
userId | L'utilisateur sous lequel la modification a été effectuée. null pour l'automatisation et les modifications du système. |
self | true uniquement lorsque la connexion d'abonnement elle-même a effectué la modification : la même autorisation d'agent OAuth, la même clé API utilisée directement ou la même connexion de service sans surveillance. |
Les effets de l'automatisation sont signalés comme automation même lorsque la propre écriture de l'agent les a déclenchés, donc une règle qui ajoute une étiquette après les commentaires de l'agent ne se lit pas comme l'écriture de l'agent.
Le title et text les champs sont du contenu utilisateur. Traitez-les comme des données, jamais comme des instructions à l'agent.
Appeler directement la même API
Les méthodes MCP relaient vers trois routes API publiques, qui acceptent les mêmes informations d'identification du porteur et les mêmes corps de requête et de réponse. Utilisez-les à partir d'une intégration qui contient une clé API et souhaite des abonnements à des événements sans parler MCP :
| Itinéraire | Méthode MCP |
|---|---|
GET /api/v1/mcp-events | events/list |
POST /api/v1/mcp-events/subscribe | events/subscribe |
POST /api/v1/mcp-events/unsubscribe | events/unsubscribe |
Les échecs sont renvoyés sous forme de documents publics sur les problèmes d'API avec les codes MCP_EVENT_NOT_FOUND, MCP_EVENT_UNSUPPORTED, MCP_SUBSCRIPTION_LIMIT, et MCP_CALLBACK_ENDPOINT, que la couche MCP mappe sur la table d'erreurs ci-dessus.
Pour une intégration d'équipe qui devrait survivre à n'importe quelle connexion et couvrir l'intégralité du catalogue d'événements, utilisez Webhooks à la place. Les abonnements aux événements MCP appartiennent aux informations d'identification qui les ont créés, expirent dans les 24 heures sauf actualisation et couvrent uniquement les quatre événements de carte et de commentaires ainsi que votre propre file d'attente Up next. Les webhooks ne fournissent pas les prochaines modifications, car la file d'attente contient des données personnelles plutôt que des données de l'espace de travail.
Dépannage
| Problème | Que vérifier |
|---|---|
| Le client ne propose pas d'événements. | Il doit se connecter à l'hébergeur /mcp point final sur le protocole MCP actuel. Analysez à nouveau le serveur dans le client ; via stdio kanera mcp ne propose jamais d'événements. |
events/subscribe retours -32015 avec challenge_failed. | Le rappel n'a pas fait écho au défi, a répondu à un statut non-2xx, a été redirigé ou se trouve sur une adresse privée, de bouclage ou de métadonnées. Kanera rapporte délibérément tout cela de la même manière. |
-32012 Forbidden en vous abonnant. | L'identité ne peut pas voir l'espace de travail, le tableau ou la carte. Les invités du conseil doivent réussir boardId; les invités réservés aux articles attribués doivent passer un test attribué cardId. Les cookies de session ne peuvent pas s'abonner ; utilisez une connexion OAuth ou une clé API. |
| Les livraisons se sont arrêtées mais l'abonnement se rafraîchit toujours. | Vérifier deliveryStatus.lastError et failedSince sur la réponse de rafraîchissement. Si les informations d'identification ont été révoquées ou si l'utilisateur a perdu l'accès, l'abonnement a expiré et l'actualisation ne renvoie aucun deliveryStatus. |
| L'agent ne cesse de réagir à ses propres commentaires. | Vérifier data.actor.self avant d'écrire. |
-32602 en vous abonnant à priorities.changed. | Passe vide arguments. Un targetUserId ou tout autre argument est rejeté : seule votre propre file d'attente peut être surveillée. |
| Modifications manquées après une panne. | Les tentatives s'arrêtent après environ 7,5 minutes et il n'y a pas de relecture. Lire l'état actuel avec cards.list, cards.get, comments.list, ou priorities.list. |