7 min de lecture

Annotations des outils MCP et limites d'approbation

Les annotations des outils MCP peuvent décrire le comportement attendu, mais seuls des tests des effets secondaires réels doivent déterminer quelles actions d'un agent nécessitent une approbation.

Annotations des outils MCP et limites d'approbation

Les annotations des outils MCP sont une documentation utile. Elles peuvent aussi donner à un système d'approbation peu sûr une apparence bien ordonnée. Si un client traite readOnlyHint, destructiveHint ou idempotentHint comme une autorisation, l'auteur du serveur a en pratique écrit la politique d'approbation de l'utilisateur sans démontrer que l'implémentation la mérite.

C'est l'ordre des choses à l'envers. Une annotation peut aider à expliquer une invite, à trier une liste d'outils ou à suggérer une valeur par défaut raisonnable à une personne qui examine une action. L'approbation doit dépendre de la requête que le serveur va exécuter, de l'identifiant qu'il va utiliser, de la cible qu'il va atteindre et des effets secondaires qu'il peut déclencher. J'ai vu trop d'intégrations appeler un endpoint nommé get, renvoyer un objet JSON poli et créer malgré tout du travail ailleurs.

La distinction compte particulièrement avec les agents, car ils réessaient, combinent les outils et agissent à une vitesse qui rend coûteuse la moindre erreur de classification. Un outil sûr lorsqu'il est appelé une fois peut devenir dangereux dans une boucle. Un outil en lecture seule pour une API peut servir à exporter des données contre une autre. Un outil qui semble idempotent peut créer du travail en double lorsqu'un délai d'attente masque la première réussite.

Les annotations décrivent un comportement, elles n'accordent pas d'autorité

La spécification des outils du Model Context Protocol décrit les annotations comme des indications sur le comportement d'un outil. Le choix des mots est délibéré : un client peut les utiliser pour améliorer son interface, mais il ne peut pas utiliser en toute sécurité une déclaration non vérifiée comme décision de sécurité.

Les trois champs concernés décrivent des affirmations différentes :

  • readOnlyHint: true affirme que l'outil ne modifie pas son environnement.
  • destructiveHint: true affirme que l'outil peut effectuer des mises à jour destructrices.
  • idempotentHint: true affirme que des appels répétés avec les mêmes arguments n'ont aucun effet supplémentaire sur l'environnement.

Ces affirmations ne couvrent pas tous les risques d'un appel. Un outil peut lire toute une base de données clients, envoyer le résultat à l'agent et se déclarer honnêtement en lecture seule. Un autre peut écrire uniquement un horodatage d'accès, ce qui semble anodin jusqu'à ce que cet horodatage modifie la conservation des données, la facturation ou un dossier d'incident. L'idempotence ne dit rien sur l'acceptabilité du premier effet.

La spécification attribue également des valeurs par défaut prudentes à ces champs. readOnlyHint vaut false par défaut. idempotentHint vaut false par défaut. destructiveHint vaut true par défaut et n'a de sens que lorsque l'outil n'est pas en lecture seule. Ne remplacez pas ces valeurs par une règle maison telle que « des métadonnées absentes signifient que c'est suffisamment sûr ». L'absence de métadonnées signifie souvent que l'auteur du serveur n'a pas réfléchi à la classification.

Un autre point est difficile à admettre : un serveur bien intentionné peut se tromper. Un développeur ajoute readOnlyHint: true parce que le gestionnaire exécute un SELECT, puis une couche de bibliothèque actualise un jeton, écrit une entrée de cache ou appelle un hook de requête. L'annotation reste vraie longtemps après la modification du comportement. Personne n'a cherché à tromper le client, mais celui-ci a tout de même pris une mauvaise décision s'il a approuvé automatiquement l'action.

Une lecture ne signifie pas un résultat inoffensif

Une opération en lecture seule peut divulguer des données, consommer une ressource rare ou activer un comportement dans un service distant. Assimiler « n'écrit pas » à « ne nécessite pas d'approbation » relève d'une erreur de catégorie.

Prenons un outil nommé get_build_log qui accepte l'identifiant d'une tâche. Le serveur lit les données dans son système de build et renvoie le résultat. Il peut déclarer correctement readOnlyHint: true. Pourtant, le journal peut contenir du code source, des informations d'environnement, des URL de téléchargement signées ou des identifiants qu'un autre système a affichés par erreur. Renvoyer cette réponse à un agent autonome modifie les personnes qui peuvent utiliser ces informations, même si la base de données du système de build reste intacte.

Le même problème apparaît dans les API d'administration. get_user peut renvoyer des codes de récupération. list_invoices peut exposer des coordonnées bancaires. search_documents peut devenir un mécanisme d'extraction en masse lorsqu'un agent augmente la taille des pages ou parcourt tous les préfixes. L'effet secondaire est une divulgation, et l'annotation ne comporte aucun champ pour la sensibilité des données.

Les opérations de lecture peuvent également modifier le service distant. Certaines API mettent à jour last_accessed_at, consomment un jeton de téléchargement à usage unique, enregistrent une prévisualisation ou facturent une requête à l'utilisation. Un défaut de cache peut réveiller un service en aval coûteux. Ces effets ne rendent pas chaque lecture dangereuse, mais ils rendent impossible à défendre une règle d'approbation fondée sur une lecture seule sans autre précision.

Classez l'appel selon deux axes distincts : modifie-t-il un système, et que peut-il révéler ou provoquer en dehors de ce système ? Une vérification d'état à faible risque et une exportation en masse peuvent toutes deux être non mutantes. Elles ne devraient pas être soumises au même traitement d'approbation.

Un compte rendu pratique de l'examen devrait nommer clairement la frontière de données. « Lit l'état du déploiement pour le projet A » peut être évalué. « Appelle get_status » ne le permet pas. Cette dernière formulation masque la cible, le périmètre, le compte et le fait qu'une méthode portant un nom similaire peut avoir un autre sens sur un autre serveur.

Testez le gestionnaire avec une cible jetable

Vous ne pouvez pas établir la sécurité d'une annotation en lisant un nom d'outil ou un schéma d'entrée. Exécutez le serveur dans un environnement où vous pouvez observer sa requête, sa réponse et l'état avant et après l'appel.

Commencez par un compte de test contenant des enregistrements que vous pouvez vous permettre de perdre. Donnez-lui un identifiant d'API distinct et acheminez les webhooks de notification vers un endpoint de capture. Enregistrez les requêtes sortantes du serveur, l'état de la base de données si vous le contrôlez, les événements d'audit, les e-mails, les tâches en file d'attente et les compteurs de facturation ou d'utilisation. Le corps de la réponse est un élément de preuve, mais il ne constitue pas le dossier complet.

Utilisez une petite matrice de test pour chaque outil susceptible d'influencer les approbations :

  1. Appelez-le une fois avec une entrée valide ordinaire et enregistrez l'état complet avant et après.
  2. Appelez-le à nouveau avec une entrée identique octet par octet et comparez tous les effets observables.
  3. Appelez-le avec une ressource absente, une opération déjà terminée et un champ invalide.
  4. Interrompez le client après réception de la requête par le serveur, puis réessayez le même appel.
  5. Lancez deux appels identiques en parallèle si les agents peuvent les émettre simultanément.

Le cas du délai d'attente permet de détecter un échec fréquent. Supposons que create_ticket envoie la requête de création du ticket, puis que la connexion soit interrompue avant la réponse du serveur. L'agent voit une erreur et réessaie. Si le système de tickets n'a pas de jeton d'idempotence, l'outil crée deux tickets. Déclarer le gestionnaire idempotent parce que son code accepte deux fois la même entrée ne change pas le résultat distant.

Consignez un résultat qui oblige à examiner les effets plutôt qu'à faire confiance à une réponse positive :

case: retry after response timeout
request: {"title":"rotate staging certificate","request_id":"test-104"}
first call: transport timeout after request received
second call: 201 {"ticket":"842"}
remote records: ["841", "842"]
result: not idempotent without a remote idempotency mechanism

Le request_id de cet exemple n'est utile que si l'API distante l'enregistre et le fait respecter. Un identifiant généré par le client que le serveur ignore n'est qu'un ornement. Testez cette vérification en répétant exactement le même identifiant et en contrôlant si le système distant renvoie l'opération initiale au lieu d'en créer une autre.

Conservez les tests avec le serveur. La dérive des annotations arrive généralement avec une modification du code, une mise à jour de dépendance ou un nouvel endpoint. Un test réussi qui compare l'indication déclarée au comportement observable a plus de valeur qu'un commentaire placé à côté de la définition de l'outil.

Les affirmations de lecture seule échouent aux frontières

Le faux readOnlyHint le plus facile à produire vient d'un examen limité à la requête principale de la base de données. La frontière pertinente comprend chaque service appelé par le gestionnaire et chaque action provoquée par sa réponse.

Prenons un outil serveur qui récupère un document. Sa requête principale est GET /documents/42, mais le gestionnaire peut d'abord échanger un jeton d'actualisation, émettre une URL de téléchargement temporaire, mettre à jour un cache local et écrire un événement d'accès. Chaque opération peut échouer différemment. Chaque opération peut dépendre d'identifiants et d'exigences d'audit différents.

Ne retenez pas l'argument selon lequel une écriture est trop petite pour compter. Les petites écritures ont leurs propres modes d'échec. Un marqueur de dernière consultation peut influencer la conservation. Un cache peut conserver du contenu après la fin de l'accès. Un événement d'accès peut avertir un propriétaire. Un compteur d'utilisation peut faire passer un compte à un niveau payant. Demandez-vous si l'écriture modifie un fait qu'une autre personne, un autre processus ou une facture pourra constater. Si oui, documentez-la.

Le comportement déclenché par la réponse mérite le même examen. Un outil qui renvoie un lien signé peut amener l'agent à le récupérer plus tard. Un outil qui renvoie une commande exécutable peut amener un agent à l'exécuter dans un autre canal. Le premier outil reste en lecture seule au sens strict, mais un écran d'approbation qui affiche « lecture sûre » donne à la personne une fausse idée de l'action suivante que l'agent peut effectuer.

Un bon serveur sépare les opérations lorsque les risques diffèrent. get_document_metadata peut rester un appel d'inspection limité. create_download_link devrait être son propre outil, car il crée une capacité de type bearer, même si les octets du document sous-jacent ne changent pas. Cette séparation aide les agents à faire le bon choix et fournit aux personnes chargées de l'examen une formulation qu'elles peuvent réellement approuver.

Le caractère destructif concerne la réversibilité, pas une liste de verbes

Placez MCP derrière une passerelle
Connectez les agents compatibles MCP via le shim sp mcp fourni au lieu de leur transmettre des identifiants.

destructiveHint devrait indiquer si un appel peut provoquer des mises à jour nuisibles difficiles à annuler, et non si le nom de l'outil contient delete. Les équipes se trompent dans les deux sens.

Certains verbes évidents sont réversibles dans un système et permanents dans un autre. archive peut simplement masquer un enregistrement ou lancer un délai avant purge. disable_user peut conserver tous les droits et fichiers, ou révoquer l'accès d'une façon qui bloque un processus automatisé. replace_config peut modifier un brouillon ou provoquer un déploiement immédiat en production. Le gestionnaire a besoin d'informations liées à la cible qu'un simple booléen ne peut pas exprimer.

Certains outils aux noms innocents sont clairement destructifs. sync_members peut supprimer les comptes absents de la liste envoyée. apply_labels peut écraser une taxonomie soigneusement tenue à jour. reconcile peut corriger un registre externe avec des écritures comptables que personne ne devrait créer à la légère. L'auteur d'un serveur qui les marque comme non destructifs parce que l'API permet techniquement de les annuler dissimule le coût opérationnel de la réparation.

Considérez la réversibilité comme une séquence, pas comme une case à cocher. Demandez qui peut annuler le résultat, quelles preuves cette personne doit fournir, combien de temps l'annulation reste possible et si un processus ultérieur consomme la modification avant que quiconque puisse l'inverser. Si une personne doit reconstituer l'intention à partir des journaux après une mise à jour en masse, l'action mérite une classification destructive même lorsqu'une API expose une méthode inverse.

La mauvaise recommandation la plus répandue consiste à réserver l'approbation aux suppressions explicites. Elle plaît parce qu'elle permet aux agents d'avancer et donne une impression de fluidité dans une démonstration. En production, elle échoue, car les modifications destructives sont généralement des remplacements, des révocations, des envois ou des rapprochements. Approuvez la modification d'état qui compte, pas le vocabulaire utilisé pour la décrire.

Pour une action qui touche une collection, exigez que le compte rendu d'examen indique la règle de sélection et le nombre d'éléments. « Synchroniser les utilisateurs » est trop vague. « Supprimer 14 prestataires inactifs sélectionnés par les identifiants fournis » permet d'évaluer le périmètre. Si le serveur ne peut pas signaler ce périmètre avant d'agir, il n'a pas fourni au client assez d'informations pour une véritable invite d'approbation.

L'idempotence doit résister aux nouvelles tentatives et à la concurrence

Vérifiez la chaîne d'audit
Vérifiez hors ligne l'historique d'audit chiffré et chaîné par hachage de Sallyport avec sp audit verify.

idempotentHint est une affirmation limitée : des arguments identiques doivent produire aucun effet supplémentaire après le premier appel. Cela ne signifie pas qu'un appel est sûr, peu coûteux, réversible ou approprié pour qu'un agent le répète indéfiniment.

Une mise à jour d'état peut être idempotente si définir state=closed deux fois laisse le même enregistrement fermé. Mais le gestionnaire cesse d'être idempotent s'il envoie un e-mail à chaque fois, ajoute un commentaire d'audit à chaque fois ou incrémente un compteur de version. On inspecte souvent la ligne de la base et on oublie les effets secondaires que les utilisateurs remarquent en premier.

L'égalité des entrées doit aussi être définie précisément. L'ordre des propriétés d'un objet JSON ne devrait pas compter. Un serveur qui traite différemment note absent et note: "" peut recevoir ce que l'agent considère comme la même requête, mais exécuter deux mises à jour distinctes. Les valeurs temporelles, les valeurs par défaut générées et les expressions relatives comme tomorrow affaiblissent cette affirmation, car elles modifient la commande effective alors que les arguments visibles semblent stables.

La concurrence est l'endroit où l'idempotence approximative s'effondre. Deux processus workers peuvent vérifier simultanément qu'un objet n'existe pas, puis le créer tous les deux. Une contrainte d'unicité, un upsert transactionnel ou un mécanisme d'idempotence distant peut l'empêcher. Un cache en mémoire dans un seul processus du serveur MCP ne peut pas protéger un déploiement qui utilise plusieurs processus.

N'utilisez un enregistrement d'idempotence qu'après en avoir défini le périmètre. Stockez un jeton fourni par l'appelant avec l'identité authentifiée, le corps normalisé de la requête, le résultat et une expiration adaptée à l'opération. Refusez un jeton réutilisé avec une entrée normalisée différente. Sinon, un agent peut associer accidentellement un ancien jeton à une nouvelle requête et recevoir le résultat d'une autre action.

Ne réessayez pas automatiquement simplement parce que l'indication vaut true. Réessayez uniquement pour les échecs où vous savez si le serveur a reçu l'appel. Si vous ne pouvez pas le savoir, le remède est un mécanisme d'idempotence au point de mutation réel. L'indication côté client ne suffit pas.

Construisez les approbations à partir de l'action exécutée

Un système d'approbation doit répondre aux questions suivantes : quel processus fait la demande, quel identifiant sera utilisé, quelle cible externe recevra la requête, quelles données ou quel état sont concernés, et que se passera-t-il si l'appel réussit ? Les annotations des outils peuvent raccourcir cette explication. Elles ne peuvent pas fournir des faits que le serveur n'a pas exposés.

Séparez l'approbation de session et l'approbation par appel. L'approbation de session convient à un processus d'agent connu qui effectue une exécution limitée avec des capacités ordinaires. L'approbation par appel convient aux identifiants capables de transférer de l'argent, de modifier des accès en production, d'envoyer des messages, de divulguer des dossiers sensibles ou de créer un engagement externe irréversible. La décision dépend de l'identifiant et du contexte de l'action, pas d'un readOnlyHint optimiste.

Une invite utile nomme l'opération concrète : « L'agent signé par cette autorité utilisera l'identifiant de déploiement pour redémarrer le service X dans le compte Y. » Une invite faible dit : « Autoriser l'outil deploy ? » La première donne à la personne un élément à évaluer. La seconde lui demande de faire confiance à un détail d'implémentation.

Sallyport applique cette séparation en conservant les identifiants API et SSH dans son coffre-fort chiffré, en exécutant elle-même l'action HTTP ou SSH et en renvoyant le résultat à l'agent plutôt que l'identifiant. Son autorisation de session identifie le processus demandeur, tandis qu'un réglage par identifiant peut exiger une décision à chaque utilisation. C'est un meilleur endroit pour placer le contrôle humain qu'un booléen fourni par le serveur.

Même avec une barrière devant les identifiants, conservez un relevé d'activité qui enregistre la cible et le résultat de la requête finale. L'approbation répond à la question de savoir si l'action peut continuer. Le relevé d'audit répond à celle de savoir ce qui s'est passé. Ne fusionnez pas ces deux questions dans un événement vague appelé « outil utilisé ».

Intégrez la vérification des annotations à la maintenance du serveur

Gardez les identifiants hors des outils
Sallyport exécute l'appel HTTP ou SSH tandis que l'identifiant reste chiffré dans son coffre-fort.

Les annotations des outils MCP servent à communiquer honnêtement, avec l'appui de tests. L'auteur d'un serveur doit les définir avec prudence, documenter chaque cas limite et les modifier lorsque le comportement change. L'auteur d'un client doit les utiliser comme une donnée parmi d'autres pour concevoir l'interface, jamais comme la seule base d'une autorisation.

Ajoutez des tests qui contredisent volontairement une propriété revendiquée. Pour une déclaration de lecture seule, faites échouer le test si la fixture détecte une écriture, une notification sortante, une actualisation d'identifiant ou une capacité créée pour une récupération ultérieure. Pour une déclaration destructive, testez le chemin d'échec et celui de l'annulation, y compris ce qui se passe après qu'une tâche en aval a consommé la modification. Pour l'idempotence, exécutez la même requête normalisée après un délai d'attente simulé et avec des appels concurrents.

Ne masquez pas un désaccord en modifiant la cible du test jusqu'à ce qu'il réussisse. Réduisez l'outil pour que l'indication devienne vraie, modifiez l'annotation ou exposez l'effet dans les détails de l'approbation. Chaque choix indique au prochain mainteneur ce que fait réellement le code.

Exécutez sp audit verify dans le cadre de l'analyse d'un incident lorsque Sallyport sert de passerelle d'action. Cette commande vérifie hors ligne la chaîne de hachage chiffrée, afin de contrôler la cohérence de l'historique des actions enregistrées sans ouvrir le coffre-fort. Cela ne prouve pas que l'annotation d'un serveur était honnête, mais fournit un relevé inviolable des appels qui l'ont suivie.

La règle pratique est simple : une annotation doit résister à un test adversarial portant sur l'effet qui compte pour l'utilisateur. Si elle n'y résiste pas, conservez une valeur prudente et maintenez l'approbation là où l'action se produit.

FAQ

Puis-je approuver automatiquement en toute sécurité un outil MCP avec readOnlyHint ?

Considérez-la comme une affirmation qui doit être étayée, pas comme une autorisation. Examinez l'implémentation du serveur, puis exécutez l'outil sur une cible jetable et vérifiez tous les effets secondaires qu'il peut provoquer.

Que garantit réellement idempotentHint ?

Cela signifie que l'auteur estime que des appels répétés avec les mêmes arguments n'auront aucun effet supplémentaire sur l'environnement. Il vous reste toutefois à tester les systèmes externes, les nouvelles tentatives, les horodatages, les notifications et la normalisation des arguments.

Les annotations des outils MCP constituent-elles une frontière de sécurité ?

Non. La spécification du Model Context Protocol décrit ces champs comme des indications comportementales, et non comme un remplacement de la décision de sécurité du client. Un serveur malveillant, obsolète ou simplement erroné peut publier des métadonnées trompeuses.

Quels outils MCP doivent encore nécessiter une approbation ?

Conservez une approbation lorsqu'un appel peut modifier l'état métier, exposer des données sensibles, lancer une tâche coûteuse ou atteindre un système situé hors de la cible de test contrôlée. Un nom apparemment inoffensif et une annotation optimiste ne suppriment pas ces risques.

Comment tester l'idempotence d'un outil ?

Utilisez un compte jetable ou une fixture locale, enregistrez l'état initial, appelez deux fois l'outil avec des arguments identiques, puis comparez l'état obtenu et les éléments externes observables. Recommencez avec des données invalides et une requête interrompue, car les chemins de nouvelle tentative révèlent souvent les dégâts.

Un outil destructif peut-il être sûr lorsque rien ne change ?

Un outil de suppression peut être non destructif pour un enregistrement précis parce que celui-ci n'existe déjà plus, tout en restant destructif en général. La conception des approbations doit classer la capacité et le contexte de la cible, pas seulement le résultat d'un appel.

Que se passe-t-il lorsque les annotations MCP sont absentes ?

Considérez l'omission avec prudence, puis examinez la sémantique des champs avant d'écrire une automatisation. Dans les annotations actuelles des outils MCP, l'absence de destructiveHint vaut true, tandis que l'absence de readOnlyHint et d'idempotentHint vaut false.

Comment un appel d'API en lecture seule peut-il encore avoir des effets secondaires ?

Un outil peut renvoyer une réponse plausible, créer une entrée d'audit, mettre à jour un champ de dernière consultation, déclencher un webhook ou facturer un compte tout en laissant la ressource évidente inchangée. Vérifiez les enregistrements en aval, les appels réseau et les journaux système, pas seulement la réponse de l'outil.

Comment concevoir les approbations pour des agents de programmation autonomes ?

N'approuvez l'exécution de l'agent qu'après avoir identifié l'autorité de signature du code et le périmètre prévu. Réservez ensuite l'approbation par appel aux identifiants ou aux actions qui nécessitent une décision humaine à chaque utilisation. Les métadonnées peuvent aider à rédiger le texte de l'approbation, mais ne doivent pas en décider l'issue.

Comment Sallyport aide-t-il à contrôler les actions MCP ?

Sallyport conserve les identifiants hors de portée de l'agent et permet à une personne d'approuver une session ou d'exiger une approbation à chaque utilisation d'un identifiant sélectionné. Cela permet de fonder les approbations sur l'action réellement exécutée plutôt que sur une annotation fournie par le serveur.

Sallyport

Sallyport exécute les appels d'API et les commandes SSH à la place de votre agent IA. Les clés restent dans un coffre-fort local sur votre Mac ; vous approuvez chaque exécution et chaque action est consignée dans un journal scellé.

© 2026 Sallyport · Open source sous Apache-2.0 · Oleg Sotnikov