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 la solution é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 merveille en démonstration. Cela veut dire aussi qu'une instruction lue de travers a des conséquences qui survivent à la conversation. Une erreur de lecture se rattrape ; une erreur d'écriture, non, et un outil d'analytics n'a rien à écrire qui vaille ce risque.

3. Une panne renvoie une erreur, pas des zéros

Quand la base de données 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 de panne d'une semaine calme. « Le trafic est tombé à zéro 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 expérience moins agréable, mais 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 réponse honnête possible.

5. Les lignes ne font pas 100, et l'outil le dit

Une répartition compte des visiteurs distincts par ligne. Un visiteur qui lit trois pages figure dans trois lignes d'une répartition par page : les lignes font donc plus que le total et les pourcentages ne totalisent pas 100.

Si on ne le dit pas, c'est un piège qui échoue d'une façon très précise : un modèle à qui l'on demande la « part de trafic par page » 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 ne remarquera.

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 se trouver 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 point délicat : 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 être transmise sous une forme ou une autre. Toute API finit par avoir une exception de ce genre ; l'honnêteté consiste à la documenter plutôt qu'à 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 ; il n'y a pas de seconde chaîne de traitement sur laquelle le modèle pourrait se tromper. Serveur MCP définit le terme s'il est nouveau pour vous.