Un serveur MCP remet des chiffres à quelque chose qui va ensuite raisonner dessus à voix haute, avec assurance, devant quelqu'un qui ne peut pas vérifier. C'est un problème de conception différent de celui d'une API destinée à un programme, et l'essentiel ne relève pas du protocole.
Voici six décisions prises dans le serveur MCP de Skomi et ce contre quoi chacune se défend. Aucune n'est astucieuse ; plusieurs sont la deuxième version, après que l'évidente s'est révélée être un piège.
1. Le jeton est un en-tête, jamais un paramètre d'outil
La conception évidente est un paramètre token sur chaque outil. Elle s'auto-documente, elle fonctionne, et elle met le secret du client dans la conversation, où il atteint les journaux du client, toute transcription exportée, et tout ce que le modèle choisit de répéter en expliquant ce qu'il vient de faire.
Un en-tête est invisible pour le modèle. Il ne peut pas être cité parce qu'il n'est pas dans le contexte.
Le même raisonnement dit que le serveur ne devrait détenir aucun jeton à lui. Celui de Skomi transmet ce que l'appelant envoie et ne garde rien : il ne peut donc rien faire que vous ne puissiez déjà faire avec curl. Il n'existe aucun mode dans lequel il aurait un accès permanent aux données de qui que ce soit, ce qui est la seule version vérifiable de « nous prenons la sécurité au sérieux ».
2. Rien n'écrit
Chaque outil est annoté readOnlyHint, et l'API qui est derrière est en lecture seule de toute façon : l'annotation est donc une description plutôt qu'une promesse.
Cela vaut la peine d'être dit franchement parce que la tentation va dans l'autre sens : un assistant qui pourrait changer un réglage fait une démonstration magnifique. Cela veut dire aussi qu'une instruction lue de travers a des conséquences qui survivent à la conversation. Lire est réversible ; écrire ne l'est pas, et un outil d'analytics n'a rien à écrire qui vaille cela.
3. Une panne renvoie une erreur, pas des zéros
Quand le magasin est indisponible, les outils échouent. Ils ne renvoient délibérément pas une page de zéros.
Des zéros seraient la réponse la plus aimable et la plus dangereuse, parce qu'un modèle ne peut pas distinguer une semaine cassée d'une semaine calme. « Le trafic est tombé à rien mardi » est une phrase qu'un assistant produira, en toute bonne foi, à partir de données d'apparence plausible, et la personne qui la lit n'a aucun moyen de savoir que c'était une panne de l'analytics plutôt qu'une panne de son site.
Une erreur est une plus mauvaise expérience et une expérience vraie. La règle générale : ne jamais renvoyer une valeur indiscernable d'une mesure réelle quand vous n'avez pas de mesure.
4. « Pas de comparaison » plutôt qu'un chiffre inventé
Chaque mesure vient avec sa variation par rapport à la période précédente. Quand la période précédente était à zéro, la variation vaut null : pas l'infini, pas 100 %, pas un grand nombre qui se trouve s'afficher.
La variation en pourcentage à partir de zéro n'est pas définie, et toute valeur concrète que vous substituez est un chiffre que le modèle répétera comme un fait. Les outils disent « pas de comparaison ». C'est moins satisfaisant et c'est la seule chose honnête disponible.
5. Les lignes ne font pas 100, et l'outil le dit
Une répartition compte des visiteurs distincts par ligne. Une personne sur un téléphone et sur un portable est dans deux lignes : les lignes font donc plus que le total et les pourcentages n'additionnent pas à 100.
Laissé non dit, c'est un piège avec un échec très précis : un modèle à qui l'on demande la « part de trafic par pays » normalisera obligeamment les lignes à 100 %, une chose parfaitement raisonnable à faire avec une liste de nombres, et fausse ici d'une façon que personne en aval n'attrapera.
La propre description de l'outil le dit donc : divisez par le total de visiteurs de la période donné par l'outil de résumé, jamais par la somme des lignes. La correction doit vivre là où le modèle regarde, c'est-à-dire dans la description de l'outil, pas dans la documentation que quelqu'un d'autre a lue.
6. Les filtres sont des dimensions, et il y a un seul analyseur
Chaque nom de dimension fonctionne aussi comme filtre, avec le même sens aux deux endroits :
breakdown dimension=path filters={"country":"EE","device":"mobile"}
Un seul analyseur signifie que les deux ne peuvent pas être en désaccord : un filtre que le modèle construit est exactement un filtre que le tableau de bord pourrait appliquer, et une dimension qu'il a découverte en demandant est une dimension sur laquelle il peut filtrer immédiatement. Il y a aussi un outil qui ne renvoie rien d'autre que les dimensions, les périodes et les règles de filtre acceptées, pour qu'un modèle puisse demander ce qui est licite au lieu de deviner et de réessayer.
Le coin gênant : pour filtrer sur une valeur qui n'a jamais été enregistrée (les visites sans campagne, disons), vous passez __none__. Une chaîne vide est indiscernable d'un paramètre absent une fois la requête analysée : la valeur vide doit donc voyager sous une forme quelconque. Toute API finit par en avoir une ; le geste honnête est de la documenter plutôt que de faire comme si la forme était uniforme.
Ce que tout cela donne
L'essentiel est un seul principe appliqué à répétition : une réponse ambiguë est pire qu'un refus, parce que la chose qui consomme votre API lèvera l'ambiguïté avec assurance et à voix haute.
Ce n'est pas propre à MCP. C'est seulement que le consommateur habituel (un programme avec un programmeur derrière) aurait levé une exception, et que celui-ci écrit un paragraphe à la place.
Le serveur de Skomi est sur mcp.skomi.com et il n'y a rien à installer ; l'API de requête qu'il lit est celle-là même que n'importe quoi d'autre peut utiliser.
Les chiffres qu'il lit sont ceux-là mêmes que Analytics vous montre, à travers la même API de requête ; il n'y a pas de seconde chaîne à propos de laquelle le modèle pourrait se tromper. Serveur MCP définit le terme s'il est nouveau pour vous.