Accueil / Articles

Les deux manières dont les serveurs MCP échouent et une couche de protection pour les empêcher

Les serveurs MCP échouent en raison de tokens trop larges et d’outils incapables de gérer les tâches. Les tokens ciblés, les vérifications d’audience, les erreurs filtrées et les limites des outils forment une couche de protection.

Éditeur : REACTAPP.TOP8 min de lecture1896 mots

Sources: MCP Servers Fail in Two Ways, and Both Are Preventable. Here’s the Guardrail Layer.

La plupart des serveurs MCP ne tombent pas en panne par crash. Ils échouent en accordant plus de confiance que ce que la tâche ne requiert jamais, et ce à deux niveaux : les permissions qu’ils distribuent et l’interface qu’ils exposent. Un modèle plus intelligent ne corrigerait pas ce problème. En revanche, une couche de protection située entre l’agent et l’outil le ferait, et les sections suivantes montrent ce que contient une telle couche, accompagnées de code.

Un nettoyage non sollicité

Un utilisateur demande à un agent de copier une base de données de production dans un environnement de pré-production. En traitant cette demande, l’agent décide qu’un étape de nettoyage serait utile et supprime d’abord quelques anciens données de test. Personne ne l’a demandé. Si le serveur MCP transmet les propres identifiants de l’utilisateur, et que cet utilisateur dispose de droits administrateurs, la suppression a lieu et la base de production disparaît.

AWS cite précisément ce scénario dans ses directives de gouvernance MCP pour expliquer pourquoi les jetons de portée ne peuvent pas être ignorés.

Le serveur constitue une frontière de confiance

Les gens décrivent généralement un serveur MCP de la même manière qu’ils décrivent tout outil d’intégration : quelque chose qui relie un agent à un système, avec quelques outils mis à disposition et la tâche accomplie. En réalité, c’est là que la décision du modèle d’agir devient un véritable effet secondaire, en utilisant une infrastructure réelle et des identifiants réels. Tous les étapes qui précèdent ne sont que du raisonnement. Le serveur est le point où ce raisonnement a des conséquences.

Ainsi, le serveur est une frontière de confiance, que ce soit intentionnellement ou non. Si l’on examine les serveurs en production sous cet angle, leurs erreurs se répartissent en deux catégories.

Classe d’échec un : trop de permissions

Le cas de la base de données représente la situation générale. Les directives de gouvernance d’AWS indiquent la solution : chaque appel en aval doit inclure un jeton dédié et généré spécifiquement pour cet appel, et jamais les identifiants qui ont initié l’agent. Elles ajoutent que la prétention d’audience du jeton doit être validée, ce qui empêche un jeton créé pour un serveur MCP d’être réutilisé sur un autre.

Peu d’équipes mettent en œuvre cette pratique tôt, car elle nécessite plus de configuration que le simple transfert du jeton de l’appelant. Pourtant, c’est là toute la différence entre un appel à une fonctionnalité générant une hallucination qui échoue de manière sûre et un appel qui atteint réellement l’environnement de production.

Deuxième catégorie d’échec : une interface défectueuse

Le deuxième groupe est plus subtil. Aucun incident dramatique ne se produit. La fiabilité s’érode simplement, et il est difficile de remonter à la cause. Les directives de développement Azure MCP de Microsoft mettent en évidence l’habitude sous-jacente : les serveurs sont conçus comme des API REST, chaque opération étant exposée en tant qu’outil distinct, alors que la conception devrait plutôt tenir compte de ce que l’agent souhaite accomplir. L’illustration fournie illustre bien ce point. Au lieu de quatre outils nommés create_ticket, add_comment, assign_ticket et notify_user, un seul outil file_support_request effectue internement les quatre étapes.

Ces directives indiquent deux autres symptômes de cette habitude. Les descriptions peuvent promettre des choses que l’outil ne fournit pas, ce qui amène l’agent à faire confiance à du texte que le code ne respecte jamais. De plus, les messages d’erreur peuvent être inutiles (“Erreur”) ou trop révélateurs : traces d’exécution, chemins de fichiers internes, ou une confirmation que tel utilisateur existe dans votre base de données, ce qui, à lui seul, permet des attaques d’énumération.

Une asymétrie dans l’orientation des soupçons

Considérez l’effort d’ingénierie nécessaire pour obtenir la réponse finale d’un agent : validation de la sortie structurée, mécanismes de protection, tentatives de réessai et étapes de vérification. La sortie du modèle est considérée comme suspecte, et à juste titre.

Considérons maintenant la couche de mise en appel d’outils sous cette réponse. Le jeton de l’appelant passe directement. La liste des outils s’agrandit sans que personne ne vérifie si le modèle est encore capable de raisonner avec eux. La description d’un outil est acceptée telle quelle, et aucun test ne montre jamais un agent utiliser correctement cet outil. D’énormes efforts sont consacrés à remettre en question ce que dit le modèle, tandis que presque aucun n’est consacré à remettre en question ce qu’il fait.

Ce soupçon biaisé est le véritable défaut des deux groupes. Ni une logique de permission plus sophistiquée ni de meilleurs noms d’outils seuls ne suffiront à y remédier. Ce qui est nécessaire, c’est une couche qui vérifie chaque appel d’outil, et non seulement chaque réponse du modèle, avant d’accorder sa confiance à celui-ci.

Construction de la couche

La unité de base ici est l’appel d’outil. Ne laissez pas une demande brute atteindre le backend. Enveloppez-la d’abord.

from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional

@dataclass
class ScopedToken:
 audience: str # which MCP server this token is valid for
 permissions: list[str] # e.g. ["read", "create"] - never assume "all"
 issued_at: datetime
 expires_at: datetime
 source_user: str # who originally triggered this, for audit

Chaque champ de cet enveloppe existe afin que la couche suivante puisse déterminer dans quelle mesure elle peut faire confiance à l’appel. Voici maintenant les contrôles.

Restreignez le jeton avant que l’outil ne s’exécute

Ne réutilisez pas les identifiants fournis par l’appelant pour une action ultérieure. Émettez un jeton plus restreint qui ne couvre que ce que l’outil s’apprête à faire.

def issue_scoped_token(user_token: ScopedToken, tool_name: str,
 required_permissions: list[str]) -> ScopedToken:
 # Never grant more than the tool declares it needs
 granted = [p for p in required_permissions if p in user_token.permissions]
 if set(required_permissions) - set(granted):
 raise PermissionError(
 f"{tool_name} requires {required_permissions}, "
 f"caller only has {user_token.permissions}"
 )
 return ScopedToken(
 audience=tool_name,
 permissions=granted,
 issued_at=datetime.utcnow(),
 expires_at=datetime.utcnow() + timedelta(minutes=5),
 source_user=user_token.source_user,
 )

C’est l’élément qui permet à la situation liée à la base de données de échouer de manière sûre. Une requête limitée à la lecture et à la création ne peut tout simplement pas effectuer une suppression, quel que soit le choix du modèle.

Vérifiez le public pour chaque appel

Un jeton généré pour un outil doit être refusé par tous les autres outils.

def enforce_audience(token: ScopedToken, expected_tool: str) -> None:
 if token.audience != expected_tool:
 raise PermissionError(
 f"Token issued for '{token.audience}' cannot be used on '{expected_tool}'"
 )

Adapter l’outil à la tâche

Cela applique le modèle file_support_request issu des directives d’Azure :

def file_support_request(customer_email: str, issue_type: str, description: str) -> dict:
 ticket = create_ticket(issue_type, description)
 add_comment(ticket.id, f"Filed by {customer_email}")
 assign_ticket(ticket.id, team=route_by_type(issue_type))
 notify_user(customer_email, ticket.id)
 return {
 "ticket_id": ticket.id,
 "status": "open",
 "assigned_team": ticket.team,
 }

Une seule requête exprime une intention unique et claire, de sorte que l’agent n’a jamais à gérer seul l’ordre de quatre opérations.

Sanitiser les erreurs avant qu’elles ne soient vues par l’agent

Enregistrer tous les détails sur le serveur et ne fournir à l’agent que ce dont il a besoin pour se remettre en état.

def safe_error(internal_message: str, request_id: str) -> dict:
 # internal_message goes to your logs, never to the model
 log.error(internal_message, extra={"request_id": request_id})
 return {
 "content": [{
 "type": "text",
 "text": f"Unable to complete the request. Request ID: {request_id}. "
 f"Try again or contact support."
 }],
 "isError": True,
 }

Limitez le nombre d’outils par serveur

Connaître cette limite avant que la production ne le fasse. Les directives de Microsoft indiquent un chiffre : pas plus de 10 à 15 outils par serveur, répartis par domaine, sans augmenter le nombre sur un même serveur.

MAX_TOOLS_PER_SERVER = 15

def register_tool(server, tool):
 if len(server.tools) >= MAX_TOOLS_PER_SERVER:
 raise ValueError(
 f"{server.name} already has {len(server.tools)} tools. "
 f"Split into a domain-specific server instead of adding more."
 )
 server.tools.append(tool)

Trois vérifications rapides sur un serveur existant

Aucune d’entre elles ne nécessite une reconstruction.

Vérifiez la portée des jetons, et non seulement leur présence. Récupérez dix appels récents de l’outil et comparez les permissions de chaque jeton avec celles nécessaires par l’outil. Si les deux diffèrent généralement, des jetons plus larges que nécessaire sont utilisés, ce qui correspond probablement à la situation actuelle.

Effectuez les calculs relatifs aux jetons pour déterminer le nombre d’outils que vous utilisez. Selon les directives d’AWS, entre 5 000 et 10 000 jetons sont nécessaires par appel rien que pour les descriptions, dès que le serveur compte plus de 20 outils. Comptez les outils que vous possédez. Un serveur dépassant largement ce chiffre, sans filtrage ni séparation, souffre d’un encombrement contextuel que aucun changement de modèle ne pourra résoudre.

Essayez un test au niveau de l’agent qui n’a probablement jamais été réalisé. Prenez les trois outils les plus utilisés et voyez, en utilisant un véritable agent et non un test unitaire, s’ils parviennent à sélectionner le bon outil à partir d’une demande quotidienne en langue naturelle. Les directives de Microsoft décrivent cela comme la couche que presque tout le monde omet. Passer les tests unitaires et de protocole sans jamais effectuer ce type de test laisse dans l’ombre la question de savoir si les outils fonctionnent réellement.

Comment cela s’intègre au reste

Rien de tout cela ne remplace les travaux existants en matière de fiabilité ; il s’y ajoute plutôt. Les tokens ciblés suivent le même principe que la validation des entrées à toute frontière de confiance. Une limite au nombre d’outils et une conception axée sur les tâches reflètent la discipline du versionnement des API et de la conception d’interfaces, mais pour une interface lue par un modèle et non par un développeur humain. Chaque serveur MCP représente un autre point où le système décide jusqu’où il peut faire confiance à l’appelant, ce qui exige donc les mêmes précautions que pour les autres éléments.

Les équipes adoptent MCP car il permet aux agents d’accéder plus facilement aux systèmes réels : une seule voie standard au lieu de cent intégrations personnalisées. Le problème, c’est qu’un modèle effectue une requête mal définie avec autant de fidélité qu’une requête bien définie, car il ne perçoit pas de différence. Seul le niveau de contrôle le permet.

Les échecs de permission et les échecs d’interface semblent sans rapport : l’un concerne l’accès, l’autre la conception. Tous deux découlent d’une supposition non vérifiée, à savoir qu’une requête vers un outil a gagné une confiance que personne n’a réellement accordée. La plupart des systèmes d’IA dirigent leurs soupçons vers la sortie, mais le côté des appels aux outils mérite également cette attention.

Cela nous amène à une question à poser à chaque serveur MCP en usage : qu’est-ce qui empêche actuellement un agent d’agir sur une requête présentant une confiance excessive, et un avertissement serait-il déclenché s’il l’était déjà ?

Réponses rapides

Qu’est-ce que le scoping des tokens MCP ? Cela signifie attribuer à chaque appel vers un outil son propre token restreint à cette fin, sans transmettre les identifiants originaux de l’appelant. Les directives de gouvernance d’AWS le recommandent afin qu’une action erronée ou non intentionnelle ne puisse pas aboutir simplement parce que les identifiants sous-jacents le permettaient.

Combien d’outils un serveur MCP devrait-il exposer ? Les directives d’Azure recommandent une fourchette de 10 à 15 outils en guise de règle générale, après quoi le serveur devrait être divisé par domaine. Par la suite, les agents choisissent moins fréquemment l’outil approprié, et les directives d’AWS indiquent que la liste d’outils longue entraîne des coûts de 5 000 à 10 000 tokens par appel, rien que pour les descriptions, dès 20 outils.

En quoi la conception d’outils axés sur les tâches diffère-t-elle de la conception d’API système ? La conception d’API système fait de chaque opération backend un outil distinct, à l’image d’une API REST. La conception axée sur les tâches propose plutôt ce que l’utilisateur cherche à accomplir sous forme d’un seul outil qui gère internement les différentes étapes. L’exemple de Microsoft est un outil unique file_support_request remplaçant quatre opérations de gestion de tickets.

Pourquoi nettoyer les messages d’erreur MCP ? Les traces d’exécution, les chemins internes ou les indications indiquant l’existence d’un certain enregistrement fournissent aux attaquants des détails qu’ils ne devraient pas obtenir, ce qui constitue au moins une vulnérabilité liée à l’énumération. Une approche plus sûre consiste à enregistrer tous les détails dans les journaux du côté serveur et à ne renvoyer à l’agent que ce dont il a besoin pour se remettre en état, accompagné d’un ID de corrélation.

Les tests unitaires détectent-ils ces problèmes ? Non. Les tests unitaires ne couvrent que la logique d’un outil, tandis que les tests de protocole vérifient la conformité avec MCP, mais aucun ne prouve qu’un agent est capable de trouver, choisir et utiliser correctement l’outil dans une situation réelle. Cela nécessite une troisième couche de test pilotée par un agent réel, que la plupart des équipes omettent.

Lectures complémentaires

  • Nouvelle mesure de la taxe sur les tokens MCP : deux descriptions d’outils en sont la cause — Une nouvelle exécution sur 13 jours de neuf serveurs MCP montre que les définitions des outils sont passées de 36 320 à 38 738 tokens, presque entièrement à cause d’un serveur qui a réécrit ses propres descriptions.