8 min de lecture

Comment la dérive de schéma MCP casse les agents de longue durée

La dérive de schéma MCP peut casser les agents de longue durée après la modification des outils d'un serveur. Testez les anciens schémas, les migrations sûres, les tentatives, les sorties et les approbations.

Comment la dérive de schéma MCP casse les agents de longue durée

Les agents qui fonctionnent longtemps rendent une mauvaise hypothèse particulièrement coûteuse : la description de l'outil chargée au début de la session resterait valable jusqu'à la fin de l'exécution. C'est souvent faux. Un serveur peut déployer une nouvelle version, activer une capacité propre à un compte, faire évoluer une API en amont ou corriger le contrat d'un résultat alors qu'un agent planifie encore ses actions à partir de la forme d'outil d'hier.

C'est cela, la dérive de schéma MCP. Il ne s'agit pas d'un cas limite exotique du protocole. C'est une modification de contrat entre un client qui a déjà établi un plan et un serveur qui a continué d'évoluer. Si vous testez seulement une nouvelle connexion après un déploiement, vous vérifiez le cas facile et laissez le cas dangereux de côté.

Le Model Context Protocol permet aux serveurs d'annoncer les changements de leur liste d'outils. Il ne transforme pas l'ancien cache d'un client en nouveau contrat, ne corrige pas les appels d'outils déjà proposés par un modèle et ne décide pas si une ancienne requête reste sûre. Ce sont des décisions d'ingénierie. Prenez-les volontairement, puis testez-les pendant qu'une session est active.

La description d'un outil fait partie de l'état de la session

Un schéma d'outil est un contexte exécutable. Un agent utilise le nom, la description, le schéma d'entrée, les annotations et parfois le schéma de sortie pour décider de l'action à demander. De nombreux clients analysent aussi tools/list pour créer des structures locales, compiler des validateurs ou placer une description compacte de l'outil dans le contexte du modèle. Aucune de ces copies ne change simplement parce que le serveur déploie une autre version.

Cela reste vrai même lorsque le protocole réseau fonctionne exactement comme prévu. Supposons qu'un client ait commencé avec cet outil :

{
  "name": "deploy_preview",
  "description": "Deploy the current branch to a preview environment.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "branch": { "type": "string" }
    },
    "required": ["branch"],
    "additionalProperties": false
  }
}

Une heure plus tard, le serveur modifie l'opération et exige un champ region explicite. Un client qui se connecte alors voit le nouveau schéma et peut le fournir. L'ancien client croit toujours que {"branch":"fix-login"} constitue une requête complète.

Il y a ici trois états distincts que les équipes confondent souvent :

  1. Le schéma annoncé correspond à ce que le serveur renvoie maintenant avec tools/list.
  2. L'instantané du schéma côté client correspond à ce qu'un client donné a conservé lors de sa dernière demande de liste d'outils.
  3. Le contrat d'exécution correspond à ce que le serveur acceptera et fera lorsque tools/call arrivera.

Un serveur peut mettre à jour immédiatement le premier état. Il ne peut pas supposer que le deuxième a été actualisé. Il doit choisir comment gérer le troisième.

Parler simplement d'un problème d'invalidation de cache est incomplet. Cette expression évoque des données d'affichage obsolètes. Un schéma d'outil peut définir les comptes de destination, le périmètre d'écriture, les champs de confirmation et le sens des résultats. Lorsqu'un agent conserve l'ancienne version, le décalage peut provoquer un travail échoué, des tentatives répétées ou une requête qui signifie désormais davantage que ce que le modèle voulait demander.

La règle par défaut la plus sûre est simple : gardez le contrat d'entrée accepté par un outil déployé compatible avec les anciennes requêtes pendant une fenêtre de transition, sauf si l'acceptation d'une ancienne forme rendrait l'action dangereuse. Lorsque la sécurité et la compatibilité s'opposent, refusez clairement l'ancienne forme et exigez une nouvelle décision.

tools/list_changed annonce un changement, mais ne synchronise rien

La spécification MCP définit notifications/tools/list_changed pour les serveurs dont la liste d'outils change. Le serveur envoie une notification, puis le client peut actualiser ses outils avec tools/list. La spécification prévoit aussi la prise en charge des notifications de changement de liste dans la capacité d'outils du serveur négociée pendant l'initialisation.

C'est utile, mais le mot « peut » fait beaucoup de travail. Une notification n'attend aucune réponse. À elle seule, elle ne permet pas au serveur de savoir si le client l'a reçue, s'il a actualisé sa liste, si son cache a été mis à jour ou si un modèle a déjà préparé un appel à partir de l'ancienne description.

Traitez la notification comme un signal d'invalidation, pas comme une barrière de synchronisation.

Un client qui gère correctement la dérive doit effectuer quatre opérations après réception de la notification :

  • Demander à nouveau tools/list et remplacer de façon atomique les définitions locales correspondantes.
  • Conserver suffisamment longtemps l'ancien instantané pour associer un appel déjà planifié au schéma qui l'a produit.
  • Revalider tout appel en file d'attente avec le schéma actualisé avant de l'envoyer.
  • Fournir au modèle une erreur réparable lorsqu'un appel construit à partir d'un ancien contexte ne peut plus être exécuté.

C'est souvent sur le troisième point que les clients prennent un raccourci. Ils actualisent la liste d'outils visible, mais laissent partir un appel déjà en file avec l'ancien objet d'arguments. Une course apparaît alors : l'interface utilisateur indique une chose, l'agent envoie autre chose et le serveur doit corriger le décalage.

Les serveurs doivent appliquer une discipline parallèle. Lorsqu'un outil change, envoyez la notification après que la nouvelle réponse de tools/list est prête. N'annoncez pas un nouveau contrat tout en laissant un ancien processus traiter les appels pendant une durée indéterminée. Si votre architecture de déploiement le permet, ajoutez une révision de contrat explicite dans la réponse du serveur et refusez les appels qui arrivent sur un processus dont le comportement est incompatible.

La notification ne résout pas non plus le cas des clients qui ne la prennent pas en charge, se déconnectent pendant l'événement ou passent par un intermédiaire disposant de son propre cache. La compatibilité au niveau de tools/call reste nécessaire. Si le serveur ne fonctionne que lorsque chaque client répond parfaitement à une notification, il ne fonctionnera pas en production.

Les changements d'entrée entraînent des ruptures différentes

Ajouter un champ ne constitue pas une seule catégorie de changement. Le risque dépend de l'effet du champ sur la validation, le sens ou l'autorité de l'action.

Ajouter une préférence d'affichage facultative est généralement sûr. L'ancien appelant l'omet et le serveur choisit une valeur par défaut stable. Ajouter un filtre facultatif peut aussi être sûr si son omission renvoie le même résultat qu'auparavant.

Ajouter une entrée region obligatoire à deploy_preview est différent. L'ancien appelant ne satisfait plus le validateur. Vous pouvez refuser la requête ou fournir une valeur par défaut. La première option interrompt l'agent, mais énonce la réalité. La seconde n'est acceptable que si cette valeur a toujours été la région prévue pour ce dépôt et ne peut pas rediriger un déploiement vers un environnement plus sensible.

Modifier le sens d'un champ est encore plus grave qu'ajouter un champ obligatoire. Imaginez un outil qui accepte à l'origine project comme identifiant lisible d'un projet. Le serveur décide ensuite que project doit être un identifiant opaque d'organisation. Un ancien agent peut continuer à envoyer payments, et le serveur peut résoudre cette chaîne dans un espace de noms inattendu. La validation réussit, la requête aboutit et l'action est incorrecte. C'est une rupture sémantique, plus dangereuse qu'une erreur de validation nette.

La suppression d'un champ d'entrée demande la même prudence. additionalProperties: false dans JSON Schema rend la rupture visible. Un ancien client envoie un argument qui était auparavant valide et reçoit une erreur. Si le serveur ignore silencieusement le champ supprimé, l'appel peut réussir avec une interprétation différente de celle attendue par le modèle.

Le conseil souvent répété « soyez tolérant sur ce que vous acceptez » convient mal aux outils qui déclenchent des actions. Il s'est imposé parce qu'il permet aux intégrations de continuer à fonctionner malgré des changements mal maîtrisés. Pour une préférence de formatage en lecture seule, cette tolérance peut être inoffensive. Pour une requête HTTP contenant des identifiants, une commande SSH, un déploiement, une suppression ou un paiement, une analyse permissive transforme une demande ambiguë en supposition côté serveur.

N'utilisez un adaptateur de compatibilité que si vous pouvez décrire précisément son comportement. Par exemple :

function normalizeDeployArgs(raw: unknown) {
  if (!isPlainObject(raw)) {
    throw executionError("Expected an object for deploy_preview.");
  }

  if (typeof raw.branch !== "string" || raw.branch.length === 0) {
    throw executionError("The branch field must be a non-empty string.");
  }

  if (raw.region === undefined) {
    return { branch: raw.branch, region: "us-east-preview", schemaRevision: 1 };
  }

  if (raw.region !== "us-east-preview" && raw.region !== "eu-preview") {
    throw executionError("region must be us-east-preview or eu-preview.");
  }

  return { branch: raw.branch, region: raw.region, schemaRevision: 2 };
}

Cet adaptateur a une propriété acceptable : l'ancienne requête produit la même destination de prévisualisation qu'auparavant. Il ne serait pas acceptable si us-east-preview n'était qu'une supposition pratique après un changement de propriété du compte.

Pour une modification incompatible, renvoyez une erreur que l'agent peut exploiter. Indiquez la révision d'outil attendue par le serveur, nommez le champ manquant ou obsolète et demandez au client d'actualiser ses outils. Ne renvoyez pas un message vague comme « entrée invalide ». Les modèles retentent les erreurs vagues avec de petites variations. Des erreurs claires donnent une chance de corriger la requête.

Les changements de forme des résultats peuvent fausser la décision suivante

Les équipes prêtent attention à la validation des entrées, car une mauvaise requête s'arrête au serveur. Elles examinent moins les changements de résultats parce que l'action est déjà terminée. Pour les agents, c'est l'inverse qui devrait se produire. Le résultat sert souvent de preuve pour l'appel suivant.

Imaginez le résultat initial de create_issue :

{
  "issue": {
    "id": "I-482",
    "url": "https://tracker.example/issues/I-482",
    "state": "open"
  }
}

Un agent peut extraire issue.id, le conserver dans sa mémoire de travail puis appeler add_comment avec cet identifiant. Si un serveur modifié renomme id en issueId, place le résultat sous data ou transforme state d'une chaîne en objet, l'action suivante de l'agent peut échouer très loin de l'appel initial. Pire encore, une solution de repli limitée au texte peut toujours contenir une phrase plausible, et le modèle peut improviser un identifiant à partir de cette prose.

Les résultats d'outils MCP peuvent contenir du contenu destiné au modèle et du contenu structuré destiné à un traitement programmatique. Si vous publiez un schéma de sortie, faites de la sortie structurée le contrat machine de référence. Gardez le texte concis et utile à une personne qui lit une transcription, mais ne comptez pas sur une extraction fiable par les clients.

Le travail de MCP autour de JSON Schema 2020-12 est pertinent ici. Les recommandations ultérieures du protocole rendent le dialecte explicite, et les schémas de sortie peuvent décrire davantage qu'un sous-ensemble de JSON limité aux objets. Cela améliore l'expressivité, mais ne donne pas le droit de remodeler à la légère un résultat utilisé par une session active. Un client peut valider les résultats avec une version précise, un type généré ou un décodeur qui ne tolère pas votre nouvelle forme d'union ou de tableau.

Pour faire évoluer les résultats, suivez ces règles :

  1. Ajoutez des champs avant de les renommer ou de les supprimer.
  2. Gardez le sens des champs stable, en particulier pour les identifiants, les états et les horodatages.
  3. Ajoutez un champ schema_revision ou result_version dans la sortie structurée lorsque plusieurs interprétations doivent coexister.
  4. Renvoyez un objet d'erreur structuré complet en cas d'échec de l'exécution, au lieu de transformer un résultat de succès en excuse non structurée.
  5. Ne supprimez l'ancienne forme qu'après avoir arrêté les anciennes sessions ou terminé une fenêtre de migration publiée.

Un champ de révision du résultat n'est pas décoratif. Il permet au client de distinguer « le serveur a renvoyé une ancienne réponse incomplète » de « le serveur a renvoyé une nouvelle réponse dont le champ facultatif est absent ». Cette distinction compte lorsqu'un agent doit décider s'il doit réessayer, interroger l'utilisateur ou poursuivre vers une action importante.

Ne transformez pas chaque sortie en enveloppe versionnée simplement parce que c'est possible. Placez un marqueur de révision lorsque plusieurs consommateurs déployés indépendamment en ont besoin. Pour un petit serveur privé et un client intégré unique, une forme stable et additive peut suffire. Pour un outil partagé entre plusieurs hôtes d'agents, processus et extensions, une information de révision explicite évite des jours de conjectures pendant un incident.

Le test important oppose un ancien plan à un nouveau serveur

Bloquez les appels à la porte du coffre-fort
Un coffre-fort verrouillé refuse toute action. Les plans obsolètes ne peuvent donc pas utiliser les identifiants stockés lorsqu'il est fermé.

Un client neuf connecté à un serveur neuf vous indique que le nouveau schéma est valide. Il ne vous apprend rien sur la dérive. Le test nécessaire demande à un client de conserver un instantané, laisse le serveur changer, puis exécute des appels issus de cet instantané.

Construisez le test autour de deux configurations de serveur. La configuration A annonce l'ancienne définition de l'outil. La configuration B annonce la nouvelle définition et contrôle la manière dont les anciens arguments sont traités. Le client reste connecté pendant le changement. Si votre serveur ne peut pas modifier son comportement sans redémarrage, placez un commutateur de test déterministe dans le registre des outils au lieu d'essayer de reproduire le timing avec votre système de déploiement.

Voici la séquence minimale utile :

1. Client initializes and receives tools.listChanged capability.
2. Client calls tools/list and stores deploy_preview revision 1.
3. Client prepares arguments: {"branch":"fix-login"}.
4. Server switches to revision 2, where region is required for new clients.
5. Server emits notifications/tools/list_changed.
6. Client sends the already prepared revision 1 call.
7. Client refreshes tools/list.
8. Client retries only if its repair policy permits it.
9. Client calls revision 2 with {"branch":"fix-login","region":"us-east-preview"}.

Le test doit examiner davantage que la réussite ou l'échec. Capturez les messages JSON-RPC réels, les arguments normalisés par le serveur, les appels simulés vers les services externes et le journal d'événements du client. Un serveur qui renvoie une erreur propre mais a déjà commencé un déploiement externe a échoué au test.

Utilisez un service aval fictif doté d'un journal de requêtes en ajout uniquement. Il doit enregistrer la méthode, le chemin, les en-têtes pertinents pour l'autorisation, le corps de la requête et un identifiant de corrélation de test. Vérifiez ensuite que l'appel obsolète n'a envoyé aucune requête en aval lorsqu'il devait être bloqué.

Un tableau concis facilite la revue du comportement attendu :

Événement de dériveComportement de l'ancien clientComportement du serveurEffet en aval
Ajout de label facultatifAppel sans labelApplique l'ancienne valeur par défautUne requête attendue
Ajout de region obligatoire avec une valeur historique sûreAppel sans regionNormalise vers une valeur par défaut stableUne requête attendue
Ajout d'une autorisation obligatoireAppel sans autorisationRenvoie une erreur d'exécution réparableAucune requête
Modification du sens de projectAppel avec l'ancien projectRefuse la requête comme incompatibleAucune requête
Ajout d'un champ de sortieAnalyse les champs précédentsRenvoie les anciens champs et le nouveauAucune action supplémentaire
Suppression d'un identifiant de sortieTente l'appel dépendant suivantLe client s'arrête et signale une erreur de contratAucune requête dépendante

Ne testez pas uniquement la nouvelle tentative réussie. Les agents savent bien réessayer, et c'est précisément pourquoi ils peuvent amplifier une mauvaise migration. Testez les appels obsolètes répétés, une notification qui arrive après l'entrée d'un appel dans la file, un échec de l'actualisation et une reconnexion du client au milieu de la transition.

Le cas délicat est celui d'un appel d'outil en cours. Un serveur doit exécuter chaque appel selon une seule révision de contrat cohérente. Ne commencez pas la validation avec la révision 1, ne rechargez pas la configuration puis ne construisez pas la requête aval avec la révision 2. Capturez la configuration du gestionnaire au moment de l'admission de l'appel. Si l'opération peut durer suffisamment longtemps pour que le contrat cible change lui-même, exposez un travail durable ou refusez l'appel avant la phase irréversible. Ne mélangez pas deux révisions dans une seule action.

La logique de correction du client doit encadrer les tentatives

Lorsqu'un serveur refuse une requête obsolète, le client dispose de plusieurs options : actualiser, demander au modèle de corriger les arguments, réessayer avec une requête convertie ou s'arrêter pour demander une intervention. Le bon choix dépend de la question suivante : le changement concerne-t-il uniquement la syntaxe ou modifie-t-il aussi l'autorité de l'action ?

Actualisez automatiquement puis réessayez uniquement si toutes les conditions suivantes sont réunies :

  • Le serveur identifie explicitement une révision de schéma obsolète ou un champ manquant.
  • La définition actualisée de l'outil fournit une valeur par défaut non sensible et sans ambiguïté, ou une conversion déterministe.
  • L'action d'origine reste dans les mêmes limites de cible et d'autorisation.
  • La première tentative n'a produit aucun effet externe.

Dans tous les autres cas, une nouvelle décision est nécessaire. Si une révision ajoute environment, account_id, repository, host, user ou un texte de confirmation, une nouvelle tentative automatique peut élargir ou rediriger l'action. Même si le modèle peut deviner une réponse probable, il doit obtenir un contexte actualisé ou une approbation humaine.

Séparez l'identité de la requête de l'identité de la tentative. Si un appel d'outil peut atteindre un système externe avant que le client ne reçoive sa réponse, le client ne doit pas le renvoyer aveuglément après une actualisation du schéma. Utilisez un jeton d'idempotence lorsque l'API aval le permet. Pour SSH, lorsqu'aucun mécanisme générique d'idempotence n'est disponible, concevez les commandes de manière à ce qu'une exécution répétée soit sûre ou détectable. Une migration de schéma est le pire moment pour découvrir qu'un délai d'attente crée un travail en double.

Une bonne erreur côté client donne au modèle les informations nécessaires sans lui transmettre une instruction trompeuse. Par exemple :

{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "deploy_preview rejected this request because its input contract changed. Refresh tools before retrying. The current schema requires branch and region. No deployment was started."
    }
  ],
  "structuredContent": {
    "error_code": "STALE_TOOL_SCHEMA",
    "tool": "deploy_preview",
    "required_action": "refresh_tools",
    "side_effect_started": false,
    "current_revision": 2
  }
}

La forme exacte de l'enveloppe d'erreur relève de votre conception, mais certains faits sont indispensables. Indiquez si un effet a commencé. Indiquez si une actualisation peut aider. Indiquez la révision actuelle si votre serveur en expose une. Un modèle peut exploiter ces faits. Une erreur de transport générique ne le peut pas.

Ne présentez pas un problème de validation du schéma comme une panne de transport. La spécification MCP distingue les échecs au niveau du protocole des échecs d'exécution d'un outil, et les recommandations actuelles privilégient les erreurs d'exécution pour une entrée d'outil invalide afin de laisser au modèle une chance de se corriger. Utilisez cette distinction. Une méthode JSON-RPC inconnue n'est pas le même événement qu'un outil connu qui refuse des arguments obsolètes.

La revue de sécurité doit porter sur le sens, pas uniquement sur les secrets

Réapprouvez les actions sensibles modifiées
Exigez Touch ID ou une approbation en un clic à chaque utilisation d'une clé HTTP ou SSH sensible.

La dérive de schéma devient un problème de sécurité lorsqu'une ancienne description autorise implicitement une action plus récente. Cela se produit avec les valeurs par défaut, les champs renommés, les autorisations ajoutées et les adaptateurs de serveur trop accommodants.

Imaginez un outil initialement nommé run_report qui accepte {\"team\":\"sales\"}. Le serveur est modifié pour accepter une chaîne target pouvant désigner une équipe, un rapport enregistré ou une requête brute. Un ancien agent continue d'envoyer team. Si l'adaptateur transforme cela en target: \"sales\", qu'a-t-il autorisé ? Une équipe ? Un rapport nommé sales ? Un alias de requête ? Le serveur a créé une ambiguïté à la frontière d'une action. Refusez cette conversion et publiez un outil distinct ou un parcours de migration explicite.

Les identifiants augmentent les enjeux. Un agent qui détient directement des clés API peut mélanger la correction de schéma et le traitement des secrets dans son propre processus et ses journaux. Un plan obsolète dispose alors de davantage de possibilités pour causer un dommage. Sallyport conserve les identifiants HTTP et SSH dans son coffre-fort chiffré et exécute l'action au lieu de remettre l'identifiant à l'agent. Cette séparation ne rend pas à elle seule un contrat modifié sûr, mais elle facilite l'inspection de la requête réelle, du point d'autorisation et de la trace d'audit.

L'approbation doit s'attacher à l'action concrète qui va se produire maintenant, et non à une description d'outil mémorisée. Si un outil passe d'un hôte unique à un sélecteur d'hôte flexible, une approbation par session fondée sur une ancienne identité de code ne suffit pas pour la nouvelle cible. Exigez une nouvelle approbation par appel pour l'action sensible, ou utilisez un nouveau nom d'outil qui rende visible l'autorité accrue.

C'est aussi là que les traces d'audit deviennent indispensables. Enregistrez le nom de l'outil, la révision déclarée du schéma lorsqu'elle est disponible, les arguments bruts reçus, les arguments normalisés utilisés, la version du serveur ou du gestionnaire, la décision d'approbation et la cible en aval. Ne remplacez pas les arguments bruts par les arguments normalisés. Pendant un incident, vous devez pouvoir vérifier si le client a envoyé une ancienne forme, si l'adaptateur l'a modifiée et si la requête externe correspondait à la promesse de l'adaptateur.

Le journal Activity et le journal Sessions de Sallyport illustrent bien la séparation entre une exécution d'agent et chaque appel externe. Pour les tests de dérive, vous voulez les deux vues : un enregistrement indiquant que l'exécution a été autorisée et un autre pour chaque action HTTP ou SSH ayant quitté ou non la machine.

Utilisez des fenêtres de compatibilité, puis supprimez-les volontairement

Séparez les plans des identifiants
Stockez les clés API et SSH dans le coffre-fort chiffré de Sallyport, jamais en clair avec l'agent.

La compatibilité ascendante doit avoir une date de fin, même si cette date est liée à un cycle de publication ou à la durée de vie d'une session plutôt qu'au calendrier. Sinon, chaque convertisseur d'ancienne entrée reste en place pour toujours et le serveur devient un musée d'hypothèses que personne n'ose modifier.

Commencez par classer le changement.

Un changement additif garde l'ancien appel valide et en préserve le sens. Conservez le même nom d'outil, annoncez le changement de liste et acceptez les deux formes pendant que les sessions actives se terminent.

Une migration limitée modifie la syntaxe, mais dispose d'une conversion déterministe sûre. Conservez le nom de l'outil uniquement si vous pouvez tester complètement le convertisseur et enregistrer chaque utilisation. Présentez le schéma actuel aux nouveaux clients et n'acceptez l'ancienne entrée que pendant une courte période.

Une modification sémantique ou d'autorité exige un nouveau nom d'outil. deploy_preview et deploy_environment peuvent partager une implémentation, mais ils ne doivent pas partager un contrat si l'un sélectionne une destination de prévisualisation connue tandis que l'autre peut sélectionner la production. Cela peut sembler verbeux dans une liste d'outils. Le coût reste inférieur à celui d'un agent qui croit avoir appelé l'opération la plus limitée.

Une suppression doit échouer clairement. Renvoyez une erreur d'exécution qui nomme l'outil de remplacement ou indique que la capacité n'existe plus. Ne conservez pas un nom d'outil qui ne fait rien. Une réussite silencieuse est toxique pour le travail automatisé, car l'agent enregistre la tâche comme terminée alors que l'effet attendu n'a jamais eu lieu.

Utilisez la télémétrie pour décider du retrait d'un adaptateur, mais ne recueillez pas uniquement des taux de réussite globaux. Comptez les appels normalisés depuis chaque ancienne révision, les appels obsolètes refusés, les corrections automatiques effectuées par les clients et les appels ayant nécessité l'intervention d'une personne. Un faible volume d'anciennes entrées peut rester important si elles proviennent des tâches d'agents les plus longues ou les plus privilégiées.

Avant le retrait, exécutez le test de dérive dans l'autre sens : démarrez un client dans l'ancienne version, mettez à jour le serveur au-delà de la fenêtre de compatibilité et vérifiez que l'échec est clair, sans effet secondaire et récupérable par une reconnexion ou une actualisation des outils. Une rupture nette vaut mieux qu'une réinterprétation silencieuse.

Une étape de publication qui détecte la dérive avant les utilisateurs

Ajoutez la dérive de schéma à l'étape de validation de chaque serveur MCP capable d'effectuer des actions. Il n'est pas nécessaire de commencer par une matrice gigantesque. Il faut une configuration de test rigoureuse pour chaque catégorie de modification de contrat.

Pour chaque outil modifié, répondez aux questions suivantes dans la demande de modification ou la revue de publication :

  1. Une session existante peut-elle encore envoyer les arguments auparavant valides ?
  2. Si oui, ces arguments conservent-ils exactement le même sens d'action ?
  3. Si non, le refus intervient-il avant tout effet externe ?
  4. Le serveur émet-il notifications/tools/list_changed uniquement après que la liste de remplacement est disponible ?
  5. Le client peut-il expliquer le chemin de correction sans inventer une autorité manquante ?

Rendez ensuite les réponses exécutables. Stockez les configurations anciennes et nouvelles de tools/list à côté du test. Exécutez la séquence avec un instantané provenant d'un ancien client. Validez les requêtes en aval, pas seulement les réponses MCP. Conservez une configuration de régression après la migration, car la prochaine refactorisation pourrait supprimer une branche de compatibilité sans que personne ne se souvienne de sa raison d'être.

Le premier test à ajouter est volontairement minuscule : listez un outil, modifiez un paramètre obligatoire, envoyez l'ancien appel et prouvez que le serveur conserve l'ancien comportement sûr ou n'effectue rien. Ce test impose la question que la plupart des déploiements évitent : que signifie exactement une autorisation détenue par un agent déjà en cours d'exécution après la modification de l'outil sous-jacent ?

FAQ

Qu'est-ce que la dérive de schéma MCP ?

Un client MCP qui reste actif longtemps peut conserver en mémoire des définitions d'outils bien après leur modification sur le serveur. L'appel suivant peut échouer à la validation, être interprété selon un autre contrat ou produire une sortie que le client ne sait pas analyser correctement. Considérez le schéma comme une dépendance de session, pas comme une simple information chargée au démarrage.

Un serveur MCP peut-il modifier ses outils pendant qu'une session est active ?

Oui. MCP fournit notifications/tools/list_changed afin qu'un serveur puisse signaler à un client que les outils proposés ont changé. Cette notification invite le client à actualiser la liste avec tools/list. Elle ne met pas automatiquement à jour toutes les copies en cache du client ni le contexte actuel du modèle.

L'ajout d'un paramètre obligatoire à un outil MCP constitue-t-il une modification incompatible ?

Ajouter une entrée facultative est généralement le changement le moins risqué, à condition que les anciennes valeurs par défaut conservent le même sens. Ajouter un champ obligatoire casse les anciens clients qui appellent l'outil avec la forme d'arguments conservée en cache. Si le nouveau champ modifie l'autorisation ou la sélection de la cible, ne masquez pas ce changement derrière une valeur par défaut.

`tools/list_changed` garantit-il que les clients actualisent leurs schémas ?

Non. Un client peut ignorer la notification, retarder son actualisation ou conserver une ancienne représentation de l'outil dans le contexte actuel du modèle. Pour les outils importants, prévoyez des fenêtres de compatibilité et testez volontairement le cas d'un schéma obsolète.

Comment modifier le résultat d'un outil MCP sans casser les agents ?

Conservez le nom de l'outil et les anciens champs de résultat pendant une période de transition définie. Ajoutez de nouveaux champs au lieu de renommer les anciens, et placez une révision du schéma dans la sortie structurée lorsque les consommateurs doivent adapter leur traitement. La suppression ou la modification du sens d'un champ exige une bascule coordonnée.

Comment tester un client avec un ancien schéma d'outil MCP ?

Le test le plus solide démarre une vraie session client, conserve la réponse initiale de tools/list, modifie le serveur, puis effectue des appels avec l'ancien schéma avant et après l'actualisation. Vérifiez le résultat JSON-RPC, le résultat structuré, le contenu lisible par un humain, le nombre de tentatives et tout effet externe.

Que doit faire un serveur MCP lorsqu'il reçoit des arguments obsolètes ?

Ne réinterprétez pas silencieusement un ancien argument comme l'autorisation d'une action plus large. Renvoyez une erreur d'exécution claire, indiquez le champ actuel attendu ou le chemin de migration, puis demandez à l'agent d'actualiser ses outils ou de solliciter une approbation. Une ancienne requête incorrecte doit être bloquée avant d'atteindre un système externe.

Pourquoi la dérive de schéma pose-t-elle un problème de sécurité pour les agents d'IA ?

La dérive peut transformer une action apparemment anodine en une requête externe différente lorsque les noms, les valeurs par défaut ou le sens des résultats changent. Elle crée aussi de la confusion dans les audits, car le client peut décrire une opération tandis que le serveur en exécute une autre. L'approbation humaine n'est utile que si la personne voit l'action réelle, sa cible et l'utilisation des identifiants.

Quand faut-il créer un nouveau nom d'outil MCP plutôt que modifier un outil existant ?

Utilisez un nom d'outil versionné lorsque le sens de l'opération, son niveau d'autorité ou la confirmation requise change. Une mise à jour additive du même outil convient lorsque l'ancien appel reste sûr et conserve exactement le même sens. Le versionnage coûte du contexte et du travail de migration, mais revient moins cher que de deviner le sens d'une ancienne requête.

À quoi ressemble un test de dérive de schéma réussi ?

Réussir signifie plus que recevoir une réponse après l'actualisation. Le client doit éviter les nouvelles tentatives mal formées, préserver la bonne limite d'action, fournir au modèle un message de correction utile et conserver un enregistrement complet du schéma et des arguments réellement acceptés 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