Articles

Pourquoi nous avons construit un serveur MCP natif pour les rétrospectives

Illustration éditoriale d'un tableau de rétrospective avec des post-it qui se déversent dans un panneau de chat à gauche, dégradé doux violet et rose, style vectoriel plat et moderne, le chat choisit des outils dans une petite palette, aucun texte ni libellé d'interface lisible
Kelly Lewandowski

Kelly Lewandowski

Dernière mise à jour 19/05/20267 min de lecture

Nous aurions pu livrer un serveur MCP Kollabe en un après-midi en passant notre spec OpenAPI dans un générateur de code. À la place, nous avons passé quelques semaines à concevoir la surface d'outils à la main. Voici ce qui nous a le plus surpris dans cette décision : le travail portait presque entièrement sur les rétrospectives. Les daily standups et le planning poker sont mécaniques. On soumet des réponses ; on vote. Un modèle capable de lire et d'écrire du JSON se débrouille très bien avec un wrapper généré. Les rétrospectives sont l'inverse exact. Elles sont longues, désordonnées, anonymes par endroits, soumises à des votes, à des réactions, regroupées, résumées, et elles font référence à des personnes qu'on ne peut pas forcément retrouver par leur nom. La première version que nous avons essayée, celle qui calquait le REST un pour un, s'est effondrée dès qu'un modèle a essayé de donner des kudos à quelqu'un.

"Natif" ne veut pas dire "code différent"

Un serveur MCP natif n'est pas un backend séparé. Le nôtre est une fine couche au-dessus des mêmes endpoints /api/v1/ que n'importe qui détenant un token d'accès personnel peut appeler directement. Les mêmes handlers, les mêmes schémas Zod, les mêmes vérifications de permission. Si nous changeons une règle dans l'un, l'autre suit. Ce que "natif" veut vraiment dire, c'est que la surface d'outils a été conçue pour ce qui l'utilise. Une API REST est lue par un développeur, la doc ouverte à côté. Un serveur MCP est lu par un modèle en pleine conversation, avec mille tokens de prompt système déjà dépensés et un utilisateur qui attend. Les deux appelants veulent des choses différentes du même backend. Quatre différences ont fini par compter pour les rétrospectives.

Décision 1 : moins d'outils, avec des bascules au lieu de paires

Un serveur généré nous aurait donné un retro_create_reaction et un retro_delete_reaction, à l'image de nos routes REST qui séparent la création de la suppression. Deux outils par réaction emoji. Multipliez ça par les items et les commentaires et vous dépensez de vrais tokens en bruit avant même que le modèle n'ait fait quoi que ce soit d'utile. Nous avons fusionné chaque action réversible en un seul outil. retro_toggle_reaction est un outil unique qui active ou désactive un emoji selon que l'appelant a déjà réagi ou non. Il renvoie "added" ou "removed" dans la réponse, pour que le modèle puisse raconter ce qui s'est passé sans avoir à stocker des identifiants de réaction dont il n'aurait besoin que pour appeler delete. La même logique a gardé les votes sur items sous forme de paire (on peut détenir plusieurs votes sur un même item, donc on a réellement besoin d'un identifiant pour en retirer un) et a maintenu la création et la suppression d'items séparées (les items ne sont pas un état réversible, ce sont des enregistrements). Bascule quand c'est réversible, séparation quand ça ne l'est pas.

Décision 2 : glisser les indices dans les descriptions, pas dans la doc

L'outil de kudos en rétrospective nous a appris ça. La première version acceptait un userId et un kudoType et renvoyait une erreur sympathique si vous passiez quelqu'un qui n'était pas dans l'espace. Les modèles inventaient systématiquement un identifiant utilisateur, touchaient l'erreur, s'excusaient et demandaient à l'utilisateur de coller le bon identifiant. Inutile. Nous avons corrigé en réécrivant la description de l'outil plutôt que le handler :
Donner des kudos à un autre utilisateur, rattachés à un item de rétrospective existant (créez-en un d'abord avec retro_create_item s'il n'y en a pas). Le destinataire doit être membre de l'espace de la rétrospective — utilisez organization_list_users (qui supporte un filtre de recherche) pour retrouver l'identifiant utilisateur par son nom.
Même code, même erreur, mais maintenant le modèle lit la description, appelle organization_list_users avec le nom que l'utilisateur a dit à voix haute, récupère l'identifiant et donne les kudos d'un coup. Le handler n'a pas changé. L'indice, oui. Illustration d'une bulle de chat à gauche avec un petit workflow à droite montrant trois étapes : rechercher un utilisateur, trouver sa fiche, puis rattacher des kudos à un item de rétrospective, palette pastel douce, style vectoriel éditorial plat Nous avons commencé à faire ça partout. retro_update prévient dans sa description que supprimer une colonne supprime aussi tous les items qu'elle contient. retro_cast_item_vote mentionne explicitement le plafond de votes par tableau et par colonne, pour que le modèle puisse alerter l'utilisateur avant de se prendre le 400. Chaque "il faut savoir ceci pour m'appeler correctement" passe dans l'outil, pas dans un guide séparé que personne ne lit.

Décision 3 : un outil de recherche sémantique, pas une chaîne lister-puis-filtrer

Les rétrospectives s'accumulent. Une équipe qui en mène une toutes les deux semaines, ça fait 26 tableaux par an, avec peut-être 800 items au total. Quand quelqu'un demande à un assistant "qu'est-ce qu'on a dit sur les déploiements instables le trimestre dernier", la pire réponse possible, c'est que le modèle appelle retro_list, puis retro_list_items pour chaque résultat, puis charge tout en contexte. C'est une tempête d'appels d'outils qui coûte de l'argent à l'utilisateur et produit une réponse pire que celle de grep. Nous avons donc construit un outil search qui lance une recherche sémantique sur tout l'espace en une fois. Il renvoie items de rétrospective, commentaires, action items, réponses de standup, réponses de sondage, réponses de brise-glace et notes, classés par similarité cosinus avec la requête, regroupés par type. Le modèle obtient les 20 résultats pertinents en un appel au lieu de s'éparpiller sur des centaines d'enregistrements.

Décision 4 : consentement par fonctionnalité au moment de l'OAuth

Le serveur MCP Kollabe expose environ quarante outils répartis entre rétrospectives, standups, planning poker, action items et recherche. Demander à un utilisateur de consentir à l'ensemble des quarante sur un seul écran, c'est le genre de décision sur laquelle on clique sans lire. Nous avons découpé l'écran de consentement par catégorie. Quand vous connectez Kollabe à Claude ou Cursor, vous cochez les catégories voulues (rétrospectives, standups, planning poker, action items, recherche) et le token est limité à celles-là. Une équipe qui veut seulement que sa PM rédige les action items de rétrospective via Claude n'a pas besoin de donner accès aux standups ou au poker. Une révocation depuis la page des paramètres utilisateur le tue, qu'il y ait eu consentement à une seule catégorie ou à toutes. Le token nomme aussi une organisation Kollabe précise. Si vous appartenez à plusieurs organisations, vous choisissez laquelle sur l'écran de consentement, et le token agit en votre nom, dans cette organisation uniquement. Vous changez d'organisation, vous vous reconnectez.

Ce que ça donne au tableau de rétrospective

Un utilisateur qui a connecté Kollabe à son client IA peut désormais tenir une conversation qui ressemble à ça, le modèle faisant le travail de fond :
  1. Préremplir le tableau
    "Ouvre la rétrospective de ce sprint et ajoute des items à partir des postmortems qu'on a écrits dans Linear ces deux dernières semaines, un par incident, dans la colonne What Could Improve." Le modèle crée les items, les marque comme anonymes là où le ticket source l'était, et s'arrête.
  2. Retrouver du contexte dans d'anciennes rétrospectives
    "On a déjà parlé de l'instabilité de la CI ?" La recherche sémantique renvoie les trois rétrospectives où le sujet est ressorti et les action items qui en ont découlé, en un seul appel.
  3. Transformer la discussion en action items
    "Fais des action items à partir des trois items les plus votés du tableau, assigne-les à la personne qui les a écrits, échéance vendredi." retro_list_items puis quelques appels à action_item_create . Le modèle fait l'assignation depuis l'auteur de l'item.
  4. Donner des kudos par leur nom
    "Donne des kudos à Priya pour avoir débloqué la migration." Le modèle appelle organization_list_users avec une recherche sur "Priya", puis rattache les kudos.
Rien de tout ça n'est une nouvelle fonctionnalité. Chacun de ces appels correspond à un endpoint REST qui est en production depuis des mois. La couche MCP, c'est la différence entre "l'API existe" et "le modèle peut utiliser l'API".

Ce qu'on referait, ce qu'on éviterait

On referait le motif de bascule dès le premier jour. On écrirait les indices avec renvois croisés dans les descriptions dès le premier jour. On livrerait la recherche sémantique avant n'importe quel endpoint de liste individuel, parce que c'est l'outil que le modèle veut vraiment. Ce qu'on éviterait, c'est la tentation de rendre MCP "plus riche" que REST. On a essayé, brièvement, d'intégrer des résumés automatiques à l'outil retro_get. Les modèles ont alors résumé le résumé, et la latence a triplé. L'ennuyeux a gagné. L'outil MCP renvoie la même forme que l'endpoint REST. L'IA par-dessus, c'est à l'utilisateur de la déclencher via prompt, pas à nous de la cuire dans le protocole. Si vous voulez essayer ça sur vos propres rétrospectives, le guide de configuration tient en soixante secondes d'OAuth. Le contexte plus approfondi sur le protocole est dans notre explication MCP. Et si vous préférez voir l'IA prendre place dans une rétrospective avant de brancher un modèle, notre générateur de modèle de rétrospective est un bon point de départ sans engagement.

Non. Le serveur MCP est un fin adaptateur au-dessus des mêmes handlers /api/v1/. Le travail intéressant se trouve dans les définitions d'outils : le nommage, le regroupement, les indices et le choix des actions à fusionner en une seule bascule. La logique backend est partagée.

Oui. retro_create_item accepte un drapeau anonymous, et retro_create_item_comment aussi. Quand il est activé, la réponse omet l'identifiant utilisateur de l'auteur, comme le fait l'interface. L'anonymat est appliqué au niveau du handler, pas dans la description.

La description de l'outil retro_update prévient que supprimer une colonne supprime aussi tous les items qu'elle contient. On compte sur le modèle pour le signaler à l'utilisateur avant de l'appeler, comme un développeur lisant la doc remarquerait l'avertissement. Il n'y a pas d'étape de confirmation séparée à l'intérieur du protocole.

Non. Le consentement est découpé par catégorie au moment de l'OAuth. Vous pouvez accorder uniquement l'accès aux rétrospectives, ou n'importe quelle combinaison de rétrospectives, standups, planning poker, action items et recherche. Les tokens sont révocables depuis vos paramètres Kollabe.