Rene-Roscher/mcp-server-design

Conçoit et révise des serveurs MCP que les agents utilisent en toute sécurité sur des produits multi-locataires : outils en tant qu'adaptateur de la couche service, un locataire par connexion lié dans le jeton, droits vérifiés par appel, classes d'effets avec prévisualisation et confirmation, protection contre les rejeux, coupe-circuit, formes d'erreur, évaluations de modèles. À utiliser lors de la construction ou de la révision d'outils ou de serveurs MCP. Ne s'applique pas à l'utilisation de serveurs MCP ou à la conception REST.

Qu'est-ce que mcp-server-design ?

Cette compétence définit la conception et les règles de sécurité des serveurs MCP (Model Context Protocol) pour les produits multi-locataires. Elle fournit un ensemble d'invariants et de pratiques garantissant que les outils MCP interagissent de manière sécurisée avec les agents d'IA, en évitant les fuites de données entre locataires, les actions non autorisées et les injections de texte. Basée sur la spécification MCP (révision 2026-07-28) et les meilleures pratiques de sécurité, elle couvre des aspects cruciaux comme la liaison d'un locataire unique par connexion, la vérification dynamique des droits à chaque appel, la classification des effets des outils (lecture, écriture silencieuse, notification, suppression, etc.) avec un mécanisme de prévisualisation et de confirmation à deux étapes pour les opérations à risque. La compétence est conçue pour les développeurs qui construisent ou révisent des serveurs MCP, et s'applique à tout projet utilisant l'architecture décrite. Elle ne concerne pas la configuration de clients MCP tiers ou la conception d'API REST.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Rene-Roscher/skills/tree/HEAD/plugins/api/skills/mcp-server-design

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

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 sur ToolAnnotations reç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_id est 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 ; attendez not_found et 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/list et tools/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, un confirm_token et un confirm_summary écrit par le serveur. Seul confirm_action exé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 produit changed_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: true et n'exécute rien. Une idempotency_key optionnelle 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ésultat replayed: 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és idempotentHint ou readOnlyHint une 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: true et 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 retourne conflict avec 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_count obligatoire ; 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 avec expected_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_text avec 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 par Error:, 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 outil find_, 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 par Error: 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 structuredContent portent le même JSON, et un outputSchema dé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:30 dans Europe/Berlin est rejeté ; 2026-10-25 02:30 retourne 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, et list_recent_changes montre 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 canal mcp. 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 ; un Origin erroné 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 locataire
  • authorization-rbac – Catalogue d'autorisations et vérification des droits
  • dual-key-identifiers – IDs publics et internes pour les objets référençables
  • gdpr-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

Skills associés