Conception de serveur MCP
Cette compétence décide comment un serveur MCP expose un produit aux agents : où les outils se situent dans le code, comment une connexion se lie à un seul locataire et à une seule personne, quels appels nécessitent la confirmation d'une personne, et comment les résultats et les erreurs guident le modèle vers le prochain appel correct. La syntaxe du protocole suit la spécification MCP (révision 2026-07-28 au moment de la rédaction) et la documentation du SDK. Le cadrage des requêtes, les catalogues d'autorisations et les formats d'identifiants proviennent de tenant-isolation, authorization-rbac et dual-key-identifiers. La défaillance que cela prévient : un modèle qui choisit le mauvais locataire, envoie un courriel à un client deux fois ou agit sur un texte qu'un tiers a inséré, parce que le serveur a laissé une porte ouverte au modèle, au client ou à une description d'outil.
S'applique quand / ne s'applique pas
- S'applique : construction d'un serveur MCP pour un produit, ajout ou révision d'un outil, choix de la manière dont les identifiants MCP se lient aux locataires et aux personnes, outils qui notifient les clients, appellent des personnes, désactivent des alertes ou suppriment, écritures en double après des tentatives du client, formes d'erreurs et de résultats, noms et descriptions d'outils, ensembles d'outils pour petits modèles, évaluations de l'utilisation des outils.
- Ne s'applique pas : configuration d'un client pour utiliser le serveur MCP de quelqu'un d'autre, conception d'API REST ou GraphQL, chatbots et commandes slash qui ne sont pas MCP, rédaction de prompts pour l'assistant d'un produit, limites de débit sur des routes non MCP.
Règles
Les plus importantes en premier. Les exemples utilisent un système de réservation : réservations, clients, techniciens.
- [invariant] Le serveur applique ce qui ne doit pas mal se passer ; les descriptions et annotations ne font qu'orienter. Chaque porte ci-dessous s'exécute dans le code du serveur à chaque appel. Pourquoi : les annotations sont des indices, et il existe des clients qui auto-approuvent ou ne demandent jamais confirmation. Test : appelez chaque outil protégé depuis un client JSON-RPC brut qui ignore les indices ; chaque garde se déclenche encore. Source : schéma MCP 2026-07-28,
ToolAnnotations: "Les clients ne doivent jamais prendre de décisions d'utilisation d'outils basées surToolAnnotationsreçues de serveurs non fiables" (https://modelcontextprotocol.io/specification/2026-07-28/schema, vérifié le 2026-10-31). - [invariant] Les outils sont un deuxième adaptateur sur la couche service : ils appellent les mêmes services métier que l'interface utilisateur et l'API, jamais leur propre API HTTP en boucle et jamais l'ORM directement. Les vérifications qui protègent les données (limites, listes blanches d'URL contre SSRF, propriété des enregistrements référencés) résident dans le service, pas seulement dans un contrôleur ou une requête de formulaire. Un chemin d'écriture par fonction. Pourquoi : un adaptateur mince hérite de chaque vérification que seuls ses adaptateurs frères exécutaient. Test : un test d'architecture échoue sur les appels de persistance dans les classes d'outils ; par outil d'écriture, une entrée que l'API rejette est rejetée par l'outil. Source : reproductible avec le test ci-dessus.
- [invariant] Une connexion, un locataire : le locataire est lié dans l'identifiant lorsque la connexion est créée, aucun schéma d'outil n'a de paramètre de locataire, un argument comme
workspace_idest rejeté comme inconnu, et chaque handle est résolu à l'intérieur du locataire lié. Un identifiant qui sert plusieurs locataires transporte le locataire dans le transport (chemin ou en-tête) et le vérifie par rapport à l'identifiant, jamais dans un argument d'outil. Pourquoi : un paramètre de locataire permet au modèle de choisir le locataire. Test : par outil avec une référence, passez le handle valide d'un autre locataire ; attendeznot_foundet une ligne inchangée. Source : spécification des outils MCP, l'ensemble d'outils "PEUT varier selon l'autorisation présentée sur la requête" (https://modelcontextprotocol.io/specification/2026-07-28/server/tools) ; meilleures pratiques de sécurité, l'état clé par un ID utilisateur "dérivé du jeton vérifié plutôt que fourni par le client" (https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices) ; les deux vérifiés le 2026-10-31. - [invariant] Les droits sont coupés en direct : droits effectifs = droits du jeton ∩ droits actuels de la personne qui a créé la connexion, calculés à chaque
tools/listettools/call. Les droits du jeton sont plafonnés à la création, un créateur supprimé ou désactivé met fin à la connexion, la révocation passe par un service qui audite. Pourquoi : des droits figés dans un jeton survivent à un changement de rôle. Test : supprimez le rôle du créateur, appelez un outil d'écriture ; refusé lors du prochain appel. Source : spécification des outils MCP, les serveurs DOIVENT "Mettre en œuvre des contrôles d'accès appropriés" (vérifié le 2026-10-31). - [invariant] Pas d'utilisateur ambiant sur les requêtes MCP : le middleware lie un contexte de connexion (locataire, créateur, droits en direct) et laisse l'emplacement d'utilisateur courant du framework vide ; les outils lisent l'acteur depuis le contexte, les services prennent l'acteur comme paramètre. Pourquoi : avec le propriétaire défini comme utilisateur courant, une vérification de politique errante dans un outil passe parce que les propriétaires peuvent tout faire ; sans utilisateur, elle échoue en sécurité. Test : un test d'architecture interdit les helpers de porte, de politique et d'utilisateur courant dans le code d'outil ; une vérification de politique délibérément ajoutée fait échouer l'appel. Source : reproductible avec le test ci-dessus.
- [invariant] Chaque outil a exactement une classe d'effet : lecture, écriture silencieuse, notification (atteint les clients ou change ce qu'ils voient), réveil (appelle ou contacte une personne), alerte (désactive, met en pause ou redirige les alertes), suppression. Dérivez la classe du chemin de code (événements, écouteurs, observateurs, tâches, courriels), pas du nom, et divisez un outil qui en couvre deux. Pourquoi : les indices, le seau de débit, le devoir de prévisualisation et le changement de connexion découlent de la classe. Test : avec des files d'attente, des courriels et des événements simulés, les outils silencieux n'envoient aucune notification. Source : critères de révision des connecteurs, les outils d'écriture/lecture génériques sont rejetés et les écritures sont divisées par type d'action (https://claude.com/docs/connectors/building/review-criteria, vérifié le 2026-10-31).
- [invariant] Les notifications, réveils, alertes et suppressions se font en deux étapes. L'outil ne fait que préparer : il stocke une action en attente (connexion, outil, arguments normalisés et digest, hash du contenu qui sera envoyé, expiration) et retourne
PREVIEW, unconfirm_tokenet unconfirm_summaryécrit par le serveur. Seulconfirm_actionexécute, après vérification de la connexion, de l'inutilisation, de la non-expiration, du non-rejet, du résumé exact, d'un verrou, des droits et commutateurs en direct, et d'une préparation fraîche dont le hash de contenu correspond toujours (sinon une nouvelle prévisualisation, ancien jeton mort). Pourquoi : ni le modèle ni le client ne demandent de manière fiable une personne, et le texte approuvé peut changer entre-temps. Test : la prévisualisation seule n'envoie rien ; les jetons réutilisés, expirés, étrangers et au résumé erroné échouent ; une modification entre les étapes produitchanged_since_preview. Source : spécification des outils MCP, "il DEVRAIT toujours y avoir un humain dans la boucle avec la capacité de refuser les invocations d'outils" ; exigences de serveur MRTR : l'état renvoyé par un client est "une entrée contrôlée par l'attaquant", lié au principal, expiration courte et digest de paramètre, usage unique appliqué côté serveur (https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr) ; les deux vérifiés le 2026-10-31. - [invariant] Protection contre les rejeux sur chaque écriture silencieuse : empreinte = connexion + outil + SHA-256 du JSON canonique des arguments normalisés (clés triées à tous les niveaux) ; une correspondance dans la fenêtre retourne le premier résultat avec
replayed: trueet n'exécute rien. Uneidempotency_keyoptionnelle est liée au digest des arguments. Le cache contient la sortie du présentateur, jamais les secrets à usage unique. Pourquoi : un flux interrompu perd la requête en vol et les clients DOIVENT la renvoyer avec un nouvel ID, donc une écriture qui a eu lieu arrive à nouveau. Test : deux fois le même appel avec des ID JSON-RPC différents ; une ligne, deuxième résultatreplayed: true. Source : journal des modifications MCP 2026-07-28, changement majeur 9 (https://modelcontextprotocol.io/specification/2026-07-28/changelog) ; Claude Code répète un appel en attente aux outils marquésidempotentHintoureadOnlyHintune fois sur une nouvelle connexion (https://code.claude.com/docs/en/mcp) ; les deux vérifiés le 2026-10-31. - [invariant] Chaque outil de création a un outil de mise à jour frère et une garde de doublon sur une clé métier. Une correspondance retourne une erreur nommant le handle existant et l'outil de mise à jour ; créer quand même nécessite
separate: trueet une raison. Les gardes lisent les colonnes synchrones (created_by,created_at), jamais un journal d'audit mis en file d'attente. Les outils de modification n'ont pas de paramètre de notification. Pourquoi : demandé pour "améliorer le texte", un modèle crée un deuxième objet à moins d'être arrêté. Test : créez deux fois avec la même clé ; une ligne, une erreur la nommant. Source : reproductible avec le test ci-dessus. - [invariant] Les mises à jour portent la version que l'appelant a lue : les objets et messages dans les résultats ont une
version(hash de contenu), les outils de mise à jour et de modification l'exigent, une version obsolète retourneconflictavec l'état actuel. Pourquoi : un agent travaillant à partir d'une lecture ancienne écrase la modification d'un collègue. Test : lisez, modifiez la ligne ailleurs, mettez à jour avec l'ancienne version ;conflict, ligne inchangée. Source : reproductible avec le test ci-dessus. - [invariant] Les outils par lots prennent des handles explicites, ou un filtre plus un
expected_countobligatoire ; lorsque le nombre de correspondances diffère, retournez les correspondances et n'exécutez rien. Pourquoi : un filtre plus large que ce que le modèle attendait multiplie une seule erreur. Test : un filtre correspondant à 12 réservations avecexpected_count: 3; erreur listant les 12, rien n'a changé. Source : reproductible avec le test ci-dessus. - [invariant] Validez chaque argument sur le serveur avec les propres règles de l'outil et rejetez les champs inconnus avec la liste des champs autorisés. Pourquoi : les SDK ne vérifient pas toujours les arguments par rapport à
inputSchema, et un champ ignoré fait croire au modèle qu'il a défini quelque chose. Test : un champ supplémentaire retourne la liste autorisée et n'écrit rien. Source : spécification des outils MCP, les serveurs DOIVENT "Valider toutes les entrées d'outil" (vérifié le 2026-10-31). - [invariant] Le texte tiers dans les résultats n'est pas fiable : le texte qu'un client, un utilisateur final, un système externe ou un téléchargement peut écrire (notes, corps de courriels, charges utiles de webhooks, messages d'erreur distants) est tronqué, débarrassé des caractères de contrôle et retourné sous
untrusted_textavec une note de ne pas suivre les instructions qu'il contient ; les corps bruts restent à l'écart. Les descriptions décrivent l'outil et ne disent jamais au modèle comment se comporter. Pourquoi : le texte injecté qui oriente un outil avec un effet externe est le risque principal ; l'étape de prévisualisation est la deuxième ligne. Test : un canari d'injection dans chaque champ tiers apparaît uniquement à l'intérieur du wrapper. Source : spécification des outils MCP, les serveurs DOIVENT "Assainir les sorties d'outil" ; les critères de révision des connecteurs rejettent les motifs d'injection de prompt dans les descriptions ; les deux vérifiés le 2026-10-31. - [invariant] Les résultats ne portent aucun secret et uniquement les données personnelles nécessaires à la tâche : présentateurs avec listes de champs explicites, jamais un sérialiseur d'enregistrement complet ; les journaux contiennent le nom de l'outil, le résultat et les ID publics, jamais les arguments. Pourquoi : chaque résultat va au fournisseur de modèle de l'utilisateur. Test : remplissez chaque champ secret d'un fixture avec une valeur appât, appelez chaque outil de lecture, greppez les résultats et les journaux ; aucun appât. Source : reproductible avec le test ci-dessus ; tenue de registres dans
gdpr-engineering. - [invariant] Les échecs métier, de validation et d'autorisation sont des erreurs d'exécution d'outil (
isError: true) dont le texte commence parError:, nomme la cause, le correctif et le bon outil avec des handles réels, et se termine par une ligne de détails JSON. "Non trouvé" pointe vers un outilfind_, jamais vers la création. Un outil caché par les droits, l'ensemble d'outils ou le plan répond avec cette raison. Pourquoi : le modèle se corrige uniquement sur les erreurs qu'il voit et comprend. Test : chaque chemin d'erreur dans la suite de contrat commence parError:et a une ligne JSON analysable. Source : schéma MCP,CallToolResult.isError: les erreurs d'outil "DEVRAIENT être rapportées à l'intérieur de l'objet résultat ... Sinon, le LLM ne serait pas capable de voir qu'une erreur s'est produite et de s'autocorriger" ; les critères de révision des connecteurs échouent les réponses génériques "Erreur Interne du Serveur" ; les deux vérifiés le 2026-10-31. - [invariant] Le bloc de texte et
structuredContentportent le même JSON, et unoutputSchemadéclaré couvre chaque résultat structuré, y compris les erreurs. Pourquoi : les clients diffèrent dans le canal qui atteint le modèle, et ils DEVRAIENT valider par rapport à un schéma déclaré. Test : pour chaque résultat dans la suite, le texte analysé est égal àstructuredContent. Source : spécification des outils MCP, un outil avec contenu structuré "DEVRAIT également retourner le JSON sérialisé dans un bloc TextContent" (vérifié le 2026-10-31). - [invariant] Le temps et l'argent sont le travail du serveur : les heures arrivent en heure locale plus fuseau IANA ou ISO 8601 avec décalage, jamais sans ; les heures locales inexistantes et ambiguës sont rejetées avec les options ; chaque heure dans un résultat montre le jour de la semaine, l'heure locale avec le fuseau, UTC et la distance par rapport à maintenant. Les totaux, taxes et arrondis sont calculés sur le serveur à partir de chaînes décimales ou d'unités mineures entières. Pourquoi : les erreurs de décalage d'un jour et d'heure d'été sont invisibles sans le jour de la semaine. Test :
2026-03-29 02:30dansEurope/Berlinest rejeté ;2026-10-25 02:30retourne les deux décalages. Source : reproductible avec le test ci-dessus. - [invariant] Un coupe-circuit en un seul endroit : un indicateur global, défini par une commande CLI et un basculement administrateur qui auditent tous les deux, fait retourner 503 avec
Retry-Afterà chaque appel MCP pendant que les connexions restent ; un commutateur par locataire avec sa valeur par défaut définie une fois permet à un administrateur locataire de désactiver l'accès agent. Pourquoi : un incident nécessite un seul mouvement qui arrête toutes les écritures d'agent. Test : indicateur activé, appelez un outil d'écriture ; 503, aucune ligne, aucune tâche. Source : reproductible avec le test ci-dessus. - [invariant] Chaque écriture MCP est auditée avec le canal
mcp, le nom de la connexion et du client, défini au point d'entrée comme colonnes, etlist_recent_changesmontre ce que cette connexion a changé. Pourquoi : les locataires voient ce qu'un agent a fait, et un agent qui a perdu le contexte trouve son brouillon au lieu d'en créer un autre. Test : chaque outil d'écriture produit exactement une ligne d'audit avec le canalmcp. Source : reproductible avec le test ci-dessus. - [invariant] Les identifiants MCP et API ne se croisent pas : les jetons MCP ont leur propre type et préfixe, sont stockés hachés, montrés une fois, et sont rejetés par chaque route API ; les jetons API et les cookies de session sont rejetés par le point de terminaison MCP. Avec OAuth, acceptez uniquement les jetons émis pour ce serveur et n'en passez jamais un. Validez
Origin. Pourquoi : les jetons MCP se trouvent en texte clair dans les fichiers de configuration du client. Test : chaque paire retourne 401 ; unOriginerroné retourne 403. Source : spécification d'autorisation MCP, les serveurs "NE DOIVENT PAS accepter ou transiter d'autres jetons" (https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization, vérifié le 2026-10-31).
Résumé
Les serveurs MCP sont des adaptateurs minces qui exposent une fonctionnalité produit aux agents LLM. Cette compétence rassemble les invariants de sécurité et de conception qui empêchent les agents de fuir des données entre locataires, d'effectuer des actions non autorisées ou de suivre des instructions injectées.
Les décisions clés :
- Où placer la logique outil (adaptateur de service, pas ORM direct)
- Comment lier les connexions aux locataires et aux personnes (via le jeton, pas via les arguments)
- Quand exiger une confirmation humaine (deux étapes pour les notifications, réveils, alertes et suppressions)
- Comment protéger contre les rejeux, les doubles créations, les écritures concurrentes et les filtres trop larges
- Comment structurer les erreurs et les résultats pour que le modèle puisse s'autocorriger
Chaque invariant est accompagné d'un test concret et d'une référence à la spécification MCP ou aux meilleures pratiques de sécurité.
Voir aussi
tenant-isolation– Formats de requête, de handle et de scope par locataireauthorization-rbac– Catalogue d'autorisations et vérification des droitsdual-key-identifiers– IDs publics et internes pour les objets référençablesgdpr-engineering– Minimisation des données et tenue de registres- Spécification MCP 2026-07-28 – Schéma des outils, autorisation et modèles MRTR
- Critères de révision des connecteurs Claude – Rejets pour les outils d'écriture/lecture génériques et les motifs d'injection