La recompilation d'un serveur MCP réinitialise-t-elle une session d'agent ?
Déterminez quand la recompilation d'un serveur MCP doit actualiser les métadonnées des outils, remplacer l'identité exécutable et invalider l'autorisation d'un agent pendant une session en cours.

La recompilation d'un serveur MCP local n'est pas un événement unique. Elle peut modifier le catalogue d'outils visible par l'agent, remplacer le code qui traite les appels ou changer l'autorité approuvée par un utilisateur. Traiter ces changements comme une simple « actualisation » générique entraîne les deux problèmes les plus importants : les agents appellent des outils avec des schémas obsolètes, ou du code récent hérite d'une autorisation accordée à l'ancien code.
La règle pratique est simple : actualisez les métadonnées lorsque le contrat annoncé change, remplacez l'identité exécutable lorsque le code capable d'agir change, et réinitialisez l'autorisation lorsque l'autorité approuvée change. Ces trois actions surviennent souvent ensemble après un rebuild, mais elles n'ont pas le même sens. Un hôte qui les sépare peut laisser une longue tâche d'agent continuer sans élargir discrètement le périmètre de confiance.
Un rebuild a trois effets distincts
Un rebuild peut modifier indépendamment les métadonnées, l'identité exécutable et l'état des autorisations. Rendez chacune de ces dimensions explicite dans votre architecture. L'actualisation d'une liste d'outils ne prouve pas que l'exécutable est resté identique, et une boîte de dialogue d'approbation ne peut pas avertir un agent que son schéma en cache est obsolète.
Les métadonnées des outils sont la description exposée par tools/list : noms et descriptions des outils, schémas d'entrée, schémas de sortie lorsqu'ils sont fournis et annotations. L'agent et l'hôte s'en servent pour déterminer si un appel est disponible et comment le former.
L'identité exécutable répond à une autre question : quel code recevra cet appel maintenant ? Pour un serveur local, la réponse peut inclure un binaire compilé, un fichier d'entrée interprété, un fichier de verrouillage, une version du runtime, le condensat d'une image de conteneur ou la configuration d'un superviseur. Un nom d'affichage tel que payments-dev n'est pas une identité. Pas plus qu'un chemin comme /Users/dev/work/payments/dist/server.js.
L'état des autorisations indique ce qu'une personne a approuvé et pour qui. Une personne peut approuver un processus d'agent pour une exécution, approuver chaque appel d'outil ou approuver une connexion au serveur. Chaque modèle doit avoir une portée claire. Si le sujet approuvé change, l'autorisation ne doit pas suivre automatiquement ce changement simplement parce que le nom de l'outil est resté identique.
Les équipes brouillent ces frontières parce qu'une boucle de développement ressemble souvent à une seule action :
- Modifier un gestionnaire.
- Compiler ou enregistrer.
- Redémarrer un serveur local.
- Continuer la session de l'agent.
Cette boucle modifie plusieurs éléments. Un gestionnaire peut acquérir un effet de bord sans que son nom ou son schéma change. Un schéma peut ajouter un champ environment alors qu'aucun octet exécutable n'a encore changé. Un superviseur peut remplacer un processus enfant alors que le processus parent conserve une connexion stdio ouverte. Si vous appelez tout cela « rechargement à chaud », vous n'avez aucune règle fiable pour déterminer ce que l'hôte doit invalider.
Utilisez trois termes dans le code et dans les discussions opérationnelles : révision du catalogue, époque d'exécution et portée de l'approbation. Une révision du catalogue décrit ce que les outils déclarent être. Une époque d'exécution identifie le code actuellement capable d'agir. La portée de l'approbation décrit le sujet exact et la durée d'une autorisation. Les noms importent peu. Ce qui compte, c'est de garder les concepts séparés.
Les métadonnées doivent être actualisées lorsque le contrat annoncé change
Actualisez les métadonnées des outils chaque fois qu'un rebuild modifie un élément que le client pourrait utiliser pour sélectionner, composer, afficher ou limiter un appel. Cela comprend l'ajout ou la suppression d'un outil, mais les cas difficiles concernent les modifications d'un outil existant.
La spécification MCP Tools fournit aux serveurs notifications/tools/list_changed pour signaler aux clients que leur liste d'outils a changé. Cette notification est utile, mais volontairement limitée : elle indique que la liste a changé, sans préciser quoi ni dire si le client l'a déjà récupérée. Un serveur qui l'émet doit s'attendre à ce que le client exécute de nouveau tools/list. Un client ne doit pas supposer que chaque serveur ou hôte réagit instantanément, notamment lorsque certaines implémentations mettent fortement les données en cache.
Actualisez les métadonnées lorsque l'un des champs suivants change :
- Un nom d'outil est ajouté, supprimé ou renommé.
- La description change d'une manière qui modifie l'usage prévu ou les effets de bord.
- Le schéma d'entrée change, notamment les valeurs par défaut, les valeurs d'énumération, les champs obligatoires, les limites ou la structure de l'objet.
- Le schéma de sortie change et l'agent utilise le résultat pour prendre sa décision suivante.
- Une annotation, un titre ou un champ de présentation change d'une manière qui influence l'examen par l'hôte.
Le dernier point demande du discernement. Le schéma MCP décrit les annotations comme des indications et la spécification avertit les clients de ne pas prendre de décisions de sécurité à partir d'annotations reçues d'un serveur non fiable. Cet avertissement est justifié. Un readOnlyHint peut aider un hôte à présenter un appel, mais il ne peut pas transformer une écriture en lecture. Si un serveur local fait passer readOnlyHint de true à false, actualisez le catalogue pour que l'affichage destiné à l'utilisateur reste exact. Ne laissez pas ce champ décider si l'appel reçoit des identifiants.
Une modification de schéma mérite plus d'attention que ne lui en accordent beaucoup d'équipes. Prenons un outil qui avait initialement ce contrat :
{
"name": "publish_preview",
"inputSchema": {
"type": "object",
"required": ["branch"],
"properties": {
"branch": { "type": "string" }
},
"additionalProperties": false
}
}
Un développeur le recompile et ajoute ce champ facultatif :
"target": {
"type": "string",
"enum": ["preview", "production"],
"default": "preview"
}
Cela peut sembler anodin. Pourtant, l'agent a peut-être conservé le premier schéma en cache, l'hôte peut afficher une carte d'approbation sans le champ target et l'implémentation peut contenir un bug qui traite une valeur omise comme production. La bonne réponse ne consiste pas simplement à accepter le champ supplémentaire. Actualisez le catalogue, rendez la valeur par défaut visible dans l'interface d'examen et testez un argument omis avec le code en cours d'exécution.
Une actualisation des métadonnées suffit lorsque l'exécutable n'a pas changé et que la portée d'approbation existante reste valide. C'est le cas lorsqu'un serveur génère ses outils à partir de données distantes et publie une nouvelle liste alors que le même code continue de s'exécuter. Cela peut aussi arriver lorsqu'un hôte corrige la documentation en dehors du processus serveur. Il n'est pas nécessaire de redémarrer une session pour corriger une faute dans une description.
Mais n'utilisez pas l'actualisation des métadonnées comme substitut à une limite d'exécution. Elle indique à l'agent ce que le serveur dit pouvoir faire. Elle ne vous indique pas ce que le serveur fera réellement.
Un exécutable modifié a besoin d'une nouvelle identité
Remplacez l'identité du serveur chaque fois qu'une nouvelle image de code, configuration de runtime ou collection de dépendances peut traiter des appels. Cela comprend un binaire redémarré, un module JavaScript rechargé, une nouvelle image de conteneur, un environnement d'interpréteur modifié et un script wrapper modifié qui redirige vers un autre programme.
L'erreur que je rencontre le plus souvent consiste à associer la confiance à une ligne de commande. Un hôte conserve quelque chose comme ceci :
server = "inventory"
command = "node"
args = ["/work/inventory/server.js"]
Il suppose ensuite que le serveur reste le même jusqu'à la modification de la configuration. Ce n'est pas le cas. Le processus node peut charger un autre server.js après un rebuild. Ce fichier peut résoudre une autre arborescence de dépendances. Il peut même rester strictement identique alors qu'un module natif, une variable d'environnement ou un wrapper shell redirige l'exécution ailleurs.
Vous n'avez pas besoin d'une empreinte universelle parfaite pour améliorer la situation. Il vous faut une identité dont la portée est définie et une règle d'invalidation prudente. Pour un serveur local de développement, créez un enregistrement d'exécution au lancement :
{
"serverLabel": "inventory-local",
"launchCommand": ["node", "/work/inventory/dist/server.js"],
"entryDigest": "sha256:9e4c...71af",
"lockfileDigest": "sha256:344b...0d19",
"runtime": "node 22.14.0",
"workingDirectory": "/work/inventory",
"epoch": "01JQ7R4S4S0QJ7GZP1S2",
"processId": 48192
}
Les condensats empêchent un simple chemin de se faire passer pour une identité. Le runtime et le répertoire de travail expliquent comment l'hôte a résolu le point d'entrée. L'époque donne à chaque redémarrage un identifiant unique, même si l'artefact recompilé produit par hasard le même condensat. L'identifiant du processus aide l'opérateur à enquêter, mais ce n'est pas une identité, car les systèmes d'exploitation le réutilisent.
Pour un serveur compilé, calculez le condensat du véritable exécutable une fois le build terminé. Pour un serveur à scripts, calculez au minimum celui du point d'entrée et du fichier de verrouillage des dépendances. Si votre runtime charge du code absent de ce fichier, incluez l'arborescence de paquets résolue ou utilisez un artefact empaqueté. Si un script shell lance le véritable serveur, calculez le condensat du script et de l'artefact enfant. Une identité qui ignore le répartiteur prouve seulement que le mauvais programme est resté inchangé.
Sur macOS, cette vérification simple fournit au développeur un enregistrement reproductible avant le lancement d'un serveur local :
shasum -a 256 dist/server.js package-lock.json
La sortie habituelle contient un condensat et un chemin sur chaque ligne :
9e4c1b2d8f3a6d...71af dist/server.js
344bb81b6c09de...0d19 package-lock.json
Ne calculez pas ce condensat après la reprise du travail de l'agent. Capturez-le au lancement du processus, associez-le à l'époque du serveur et enregistrez-le avec chaque décision d'autorisation. Sinon, une piste d'audit pourra seulement prouver qu'un fichier a existé à un moment donné.
Un rebuild qui modifie le code tout en conservant le même catalogue d'outils nécessite malgré tout une actualisation de l'identité. Imaginez que get_invoice conserve son nom, son schéma, sa description et son annotation en lecture seule. Le gestionnaire recompilé envoie désormais chaque numéro de facture à un point d'accès externe de débogage avant de renvoyer la même facture. Les métadonnées n'ont pas changé. L'autorité, elle, a changé.
L'inverse peut aussi se produire. Un serveur en cours d'exécution peut publier un autre ensemble d'outils propres à chaque locataire à partir d'une configuration mise à jour, alors que son identité exécutable reste fixe. Actualisez les métadonnées, conservez l'époque et déterminez l'autorisation en fonction du fait que le nouveau catalogue dépasse ou non la portée approuvée.
L'autorisation doit suivre l'époque d'exécution
Invalidez l'autorisation lorsque l'époque d'exécution du serveur change, sauf si l'autorisation couvre explicitement un éditeur fiable et un canal de mise à jour défini. Pour les serveurs locaux recompilés, cette exception demande généralement plus d'efforts qu'elle n'en vaut la peine.
Certains veulent conserver l'approbation après un rebuild parce que le développement devient sinon pénible. Leur argument est recevable : demander une approbation à chaque enregistrement rendrait le contrôle inutile. La solution n'est pas de rendre l'approbation permanente, mais de l'associer à la bonne unité.
Une autorisation pratique peut être liée aux champs suivants :
{
"agentRun": "run_01JQ7R1",
"serverLabel": "inventory-local",
"executionEpoch": "01JQ7R4S4S0QJ7GZP1S2",
"toolScope": ["inventory_lookup", "inventory_adjust"],
"credentialScope": ["inventory-api-staging"],
"issuedAt": "2026-07-22T14:31:08Z",
"expiresWhen": "agent-run-ends"
}
Cette autorisation exprime quelque chose qu'un réviseur peut comprendre : cette exécution d'agent peut utiliser ces outils via cette instance précise du serveur, avec cette portée d'identifiants. Un serveur redémarré reçoit une nouvelle époque. L'hôte rejette l'ancienne autorisation avant d'injecter un identifiant et demande une nouvelle approbation si l'appel a toujours besoin de cette autorité.
Ne liez pas l'autorisation aux seuls noms d'outils. Les noms d'outils sont une convention d'interface. Un rebuild peut transformer inventory_adjust, qui signifiait « modifier un stock de test », en un appel à un point d'accès de production sélectionné par une variable d'environnement. Même si l'agent continue d'appeler le même nom, l'hôte doit détecter que le code qui le traite a changé.
La même règle s'applique à un wrapper côté agent. Si l'agent démarre un serveur MCP local via un lanceur qui se recompile ou se réécrit, le lanceur doit figurer dans l'enregistrement d'identité. Un wrapper malveillant ou défectueux peut conserver tous les noms d'outils visibles par l'utilisateur tout en redirigeant les appels vers un autre programme.
Il existe des situations où une approbation peut survivre à une mise à jour du code, mais elles demandent davantage de structure qu'un build local n'en possède généralement. Une organisation peut par exemple approuver des artefacts signés par un éditeur identifié, limités à un canal de déploiement, avec une politique de version vérifiée, une portée d'identifiants fixe et une procédure distincte pour examiner les changements d'autorisation. C'est de la gestion de versions. Ne prétendez pas qu'un observateur de fichiers et un répertoire de développement non verrouillé offrent les mêmes garanties.
L'approbation à chaque appel modifie l'équilibre. Si un identifiant ou un outil exige une confirmation à chaque utilisation, un rebuild doit toujours créer une nouvelle identité pour l'audit, mais l'action immédiate repose sur une nouvelle décision humaine. Cela ne supprime pas la nécessité d'actualiser les schémas. Cela limite toutefois les conséquences d'une approbation de session obsolète.
Sallyport adopte une approche comparable pour les actions des agents : sa passerelle de coffre refuse les actions lorsqu'elle est verrouillée, son autorisation par session identifie un nouveau processus d'agent connecté et chaque identifiant peut exiger une approbation à chaque utilisation. La leçon de conception est importante : une décision humaine doit avoir un sujet clair et une fin définie, plutôt qu'une promesse vague selon laquelle une étiquette familière resterait sûre.
Stdio dissimule le remplacement du processus derrière un même tube
Une connexion MCP stdio rend le comportement des rebuilds particulièrement trompeur, car la connexion appartient aux processus et non aux fichiers. Recompiler un fichier ne change rien à un processus enfant en cours d'exécution tant que quelque chose ne remplace ou ne recharge pas ce processus.
Dans le cas simple, un hôte MCP lance un serveur enfant et conserve ses tubes d'entrée et de sortie standard. L'enfant a chargé son code au démarrage. Un développeur relance le build, mais l'enfant existant reste en mémoire. L'agent parle toujours à l'ancienne implémentation, même si le répertoire contient désormais de nouveaux artefacts.
Aucune actualisation du protocole n'est nécessaire dans ce cas. L'identité exécutable réelle n'a pas changé. L'erreur consiste à dire aux développeurs que leur rebuild a pris effet alors que ce n'est pas le cas. Vos outils de développement devraient afficher une ligne d'état sans ambiguïté, par exemple :
build complete: dist/server.js changed
running server unchanged: pid=48192 epoch=01JQ7R4S4S0QJ7GZP1S2
Le cas plus difficile utilise un observateur. Un processus parent possède le tube stdio, surveille les fichiers, arrête son processus de travail et en démarre un nouveau. Le parent peut conserver le tube ouvert pendant que les appels commencent discrètement à atteindre le nouvel enfant. Du point de vue du client MCP, la connexion n'a jamais été fermée. Du point de vue de l'examen de sécurité, l'identité exécutable a changé au milieu d'une session existante.
Ne laissez pas ce remplacement rester invisible. Choisissez l'une de ces conceptions :
- Fermez la connexion MCP lorsque l'enfant redémarre, afin de forcer l'hôte à se reconnecter, à initialiser la nouvelle instance et à l'autoriser.
- Conservez la connexion extérieure, mais faites publier par le superviseur une nouvelle époque d'exécution à l'hôte avant de transmettre un nouvel appel.
- Évitez le rechargement en cours de processus pendant le développement et redémarrez l'ensemble du processus serveur sous le contrôle de l'hôte.
La première conception est la plus claire. La deuxième peut préserver le contexte d'un agent de longue durée, mais elle exige une limite fiable entre le superviseur et l'hôte. La troisième coûte quelques secondes et évite des semaines passées à expliquer pourquoi l'approbation d'une session couvrait du code inconnu.
Ne comptez pas sur la propre notification du serveur pour attester son remplacement. Le nouveau code peut mentir, et un serveur compromis a tout intérêt à annoncer la même identité. Le gestionnaire de processus, l'hôte ou la passerelle d'identifiants doit observer le lancement et créer l'époque. Si ces couches ne peuvent pas observer un redémarrage, elles ne peuvent pas distinguer en toute sécurité un rebuild d'un serveur stable.
Les notifications de liste d'outils sont un signal, pas un transfert de responsabilité
notifications/tools/list_changed doit déclencher la récupération du catalogue, mais ne redémarre pas une session, ne renégocie pas les capacités et ne transmet pas une décision d'autorisation. Concevez votre flux de contrôle en fonction de ce que la notification indique réellement.
La spécification MCP Lifecycle décrit l'initialisation comme la phase où le client et le serveur négocient la version du protocole et les capacités. Le fonctionnement normal commence ensuite. Un serveur qui annonce tools.listChanged indique qu'il peut informer le client d'une modification de sa liste d'outils. Il ne dit pas qu'il peut réécrire sa propre identité au milieu d'une session sans conséquence, et il n'oblige pas un hôte à traiter la notification comme une attestation de sécurité.
Cette distinction compte lors de la conception d'un chemin de rechargement. Une implémentation fragile fait ceci :
watcher rebuilds server
server sends tools/list_changed
client fetches tools/list
agent continues
Cela fonctionne dans une démonstration. Mais quatre questions opérationnelles restent sans réponse :
- Le processus existant a-t-il chargé le code recompilé ?
- Un autre processus a-t-il pris le contrôle de la connexion ?
- Le nouveau code possède-t-il la même autorité approuvée ?
- L'hôte a-t-il rejeté un appel préparé par l'agent avec l'ancien schéma ?
Un flux plus solide répartit les responsabilités entre plusieurs couches. Le système de build signale les artefacts. Le superviseur signale le remplacement du processus. Le serveur MCP signale les changements du catalogue. L'hôte actualise les métadonnées visibles par l'agent. La couche d'autorisation compare l'époque d'exécution à l'autorisation. Le journal d'audit enregistre chaque transition.
Si l'hôte reçoit une notification de modification de liste et découvre ensuite un changement d'époque, il doit traiter d'abord le changement d'époque. Marquez tout catalogue d'outils en cache comme suspect, bloquez les appels avec identifiants jusqu'à la récupération du catalogue actuel et à la prise d'une décision d'autorisation, puis reprenez. L'agent peut conserver l'historique de sa conversation. Il ne peut simplement pas supposer qu'une invocation composée avant le rebuild reste valide.
La même prudence s'applique aux changements de capacités. Si un rebuild ajoute des ressources, des prompts, des comportements de journalisation ou une extension expérimentale, une session initialisée avec une ancienne version peut ne pas avoir négocié ces fonctions. Reconnectez-vous au lieu d'essayer de modifier la connexion négociée sur place. Les sessions de longue durée sont pratiques, mais le contrat d'une connexion doit rester compréhensible lorsqu'un problème survient à 2 heures du matin.
Rédigez un contrat d'actualisation avant d'ajouter le rechargement à chaud
Un contrat d'actualisation doit préciser qui détecte un rebuild, quels états sont modifiés, quels appels sont suspendus et quelles preuves entrent dans le journal d'audit. Si rien n'est écrit, chaque composant prendra une décision localement raisonnable, et le comportement global sera dangereux.
Utilisez une petite machine à états. Elle n'a pas besoin d'un langage de politique ni d'un labyrinthe de règles.
ready(epoch A, catalog 12, approval A)
build artifact changes
ready(epoch A, catalog 12, approval A)
worker restarts
identity-pending(epoch B, catalog unknown, approval A invalid)
host fetches tools/list
catalog-ready(epoch B, catalog 13, approval A invalid)
reviewer approves required scope
ready(epoch B, catalog 13, approval B)
La transition importante est identity-pending. Dans cet état, l'hôte ne doit pas transmettre un appel avec identifiants simplement parce que l'agent l'avait déjà préparé. Il peut autoriser des demandes de découverte sans danger si vous en avez une définition claire, mais ne devinez pas. Pour la plupart des serveurs locaux, suspendre tous les appels d'outils jusqu'à l'actualisation du catalogue et de l'autorisation est plus simple.
Votre contrat doit présenter clairement les décisions suivantes :
- Le composant qui crée une époque d'exécution.
- Les artefacts et faits liés au runtime inclus dans l'identité exécutable.
- Les changements de métadonnées qui exigent une nouvelle exécution de
tools/list. - Les portées d'autorisation qui expirent lorsque l'époque change.
- Le comportement d'un appel en cours lors d'un redémarrage.
Les appels en cours exigent une règle ferme. Si un processus de travail meurt après avoir reçu un appel d'outil mais avant de produire une réponse, renvoyez une erreur qui identifie la transition d'époque. Ne réessayez pas automatiquement une écriture contre le nouveau processus. Une nouvelle tentative peut dupliquer un paiement, publier deux fois ou appliquer une modification alors que les arguments ont changé de sens.
Pour les appels en lecture seule, une nouvelle tentative automatique peut être acceptable si l'hôte peut prouver que la première tentative n'a jamais atteint la limite d'action. Cette preuve est difficile à établir avec des sous-processus locaux et des API distantes. Un délai d'attente ne constitue pas une preuve. Une réponse vide non plus. Commencez par un échec explicite, puis ajoutez des tentatives sûres uniquement lorsque vous pouvez démontrer l'idempotence.
Testez les rebuilds comme des changements d'autorité
Un test de rebuild doit prouver davantage que « le nouvel outil apparaît ». Il doit démontrer que des métadonnées obsolètes ne peuvent pas former un appel dangereux, qu'une ancienne autorisation ne peut pas atteindre le nouveau code et que la piste d'audit distingue les deux époques d'exécution.
Effectuez ce test dans un environnement local avec un serveur possédant un outil d'écriture nécessitant un identifiant et un outil de lecture sans danger.
- Démarrez la révision A du serveur. Capturez son enregistrement d'exécution, récupérez
tools/listet autorisez une exécution d'agent pour l'outil d'écriture. - Appelez une fois l'outil d'écriture avec un marqueur tel que
revision=A. Vérifiez que le journal enregistre l'époque A et l'autorisation de l'époque A. - Recompilez la révision B. Conservez le même nom d'outil, mais ajoutez un champ obligatoire au schéma ou modifiez le gestionnaire afin qu'il écrive
revision=B. - Remplacez le processus de travail à l'aide du même mécanisme que celui utilisé en développement normal.
- Essayez l'ancien appel préparé avant l'actualisation des métadonnées et de l'autorisation. L'hôte doit le refuser parce que l'époque a changé.
- Récupérez le nouveau catalogue, obtenez une nouvelle approbation si l'appel demande une autorité, puis appelez à nouveau l'outil. Vérifiez que le journal enregistre l'époque B et une nouvelle autorisation.
Le refus attendu doit être suffisamment précis pour faciliter le diagnostic :
{
"error": "authorization_stale",
"reason": "server execution epoch changed",
"approvedEpoch": "01JQ7R4S4S0QJ7GZP1S2",
"currentEpoch": "01JQ7R9KQ6K2Y8W4JH0M",
"retry": "refresh tool metadata and request authorization"
}
Ne masquez pas ce résultat derrière un message générique comme « outil indisponible ». L'agent doit savoir s'il doit actualiser son catalogue, attendre le redémarrage du serveur ou demander l'intervention d'une personne. L'opérateur doit savoir si un observateur a remplacé un processus de travail de manière inattendue.
Ajoutez les tests d'échec que les développeurs ont tendance à oublier :
- Le build réussit, mais l'ancien processus continue de fonctionner.
- Le processus redémarre, mais la liste d'outils reste identique.
- Le schéma change, mais un client ignore
tools/list_changed. - Un redémarrage survient alors qu'un appel d'écriture attend une réponse.
- Le chemin du serveur reste fixe alors que l'arborescence des dépendances résolues change.
Ces tests révèlent si votre conception dépend d'un serveur coopératif qui dit la vérité. Elle ne devrait pas. Le code de développement local est précisément l'endroit où la confiance s'élargit accidentellement, car les développeurs recompilent sans cesse et les hypothèses deviennent invisibles.
Les enregistrements d'audit doivent indiquer quel code a agi
Un enregistrement d'audit doit permettre de déterminer quelle époque exécutable a traité un appel. Sans cela, il ne peut pas résoudre un incident lié à un rebuild. Le nom de l'outil, les arguments et l'heure sont utiles, mais ils laissent la question la plus difficile sans réponse.
Enregistrez l'époque d'exécution pour chaque appel d'outil. Enregistrez la révision du catalogue ou l'empreinte des métadonnées lorsque l'hôte présente les informations de l'outil à l'agent. Enregistrez les décisions d'approbation avec le sujet qu'elles couvrent. Si un appel franchit une limite d'identifiants, enregistrez le nom de la portée, sans jamais enregistrer le secret lui-même.
Une séquence d'événements compacte peut ressembler à ceci :
{"type":"server_started","epoch":"01JQ7R4...","entryDigest":"sha256:9e4c...71af"}
{"type":"approval_granted","run":"run_01JQ7R1","epoch":"01JQ7R4...","scope":"inventory-api-staging"}
{"type":"tool_called","run":"run_01JQ7R1","epoch":"01JQ7R4...","tool":"inventory_adjust"}
{"type":"server_replaced","oldEpoch":"01JQ7R4...","newEpoch":"01JQ7R9..."}
{"type":"authorization_denied","run":"run_01JQ7R1","epoch":"01JQ7R9...","reason":"stale_epoch"}
Cette structure aide aussi au débogage courant. Lorsqu'une personne signale qu'un agent a utilisé un ancien schéma après un rebuild, vous pouvez voir si l'hôte n'a pas actualisé les métadonnées, si le serveur n'a jamais redémarré ou si un superviseur a changé le code sans l'annoncer. Ce sont des défauts différents qui ne doivent pas finir dans la même catégorie de bugs.
Les journaux Sessions et Activity de Sallyport sont de bons exemples de séparation entre les enregistrements d'exécution d'agents et ceux des actions individuelles, tout en produisant les deux à partir d'un seul journal d'audit chiffré et chaîné par hachage. La même séparation s'applique ici : un enregistrement explique qui a été autorisé à exécuter une tâche, et un autre explique quel appel a eu lieu sous quelle époque d'exécution.
Ne faites pas dépendre le système d'audit de l'auto-déclaration du serveur. Placez la capture de l'identité à côté du lancement du processus ou de la distribution des identifiants. Vérifiez ensuite la chaîne d'audit indépendamment lorsque votre environnement le permet. Un serveur capable de modifier son code pendant une session ne doit pas devenir l'unique témoin du code réellement exécuté.
La vitesse des rebuilds ne justifie pas une confiance héritée
Les rebuilds rapides facilitent le développement. Ils ne transforment pas du nouveau code en code déjà examiné. Si un serveur MCP local peut accéder à des identifiants, des fichiers ou des systèmes distants, un rebuild doit créer une limite visible entre le code approuvé et le code qui agira ensuite.
Commencez par rendre le remplacement du processus observable. Ajoutez une époque au lancement. Liez l'autorisation de session à cette époque. Actualisez les métadonnées des outils chaque fois que leur contrat change. Puis faites volontairement échouer un test : recompilez un serveur pendant une session d'agent active et vérifiez que le prochain appel nécessitant un identifiant s'arrête jusqu'à ce que l'hôte dispose de métadonnées et d'une autorisation à jour.
Si ce test réussit pour la bonne raison, votre agent pourra continuer à travailler après un rebuild sans recevoir une autorisation appartenant à un exécutable antérieur.
FAQ
La recompilation d'un serveur MCP nécessite-t-elle de redémarrer l'agent ?
Un rebuild ne nécessite pas toujours de réinitialiser la session. Si le processus en cours conserve l'ancien code en mémoire, rien n'a encore changé. Réinitialisez la session lorsqu'une nouvelle instance exécutable peut recevoir des appels, surtout si le code recompilé peut modifier le comportement d'un outil existant.
Que fait tools/list_changed après la recompilation d'un serveur MCP ?
Utilisez notifications/tools/list_changed lorsque la liste des outils, leurs descriptions ou leurs schémas ont changé. Considérez cette notification comme une demande de récupération de métadonnées à jour, et non comme la garantie que tous les clients le feront immédiatement. Elle ne crée pas une nouvelle identité exécutable et ne renouvelle pas l'autorisation.
Un serveur MCP local recompilé doit-il recevoir une nouvelle approbation ?
Un nouveau processus serveur doit recevoir une nouvelle approbation lorsque l'autorisation est liée à l'instance exécutable, à son empreinte de build ou à son contexte de lancement. C'est le choix sûr par défaut pour les serveurs locaux de développement qui accèdent à des identifiants, des fichiers ou des API de production. Réutiliser une approbation après le remplacement d'un code inconnu transforme un clic en permission accordée à du code que l'utilisateur n'a jamais examiné.
Un chemin d'exécutable suffit-il pour identifier un serveur MCP ?
Non. Un chemin indique où le lanceur a cherché, pas quels octets le système d'exploitation a exécutés. Utilisez une identité d'exécution comprenant l'empreinte de l'artefact lancé, le runtime ou l'interpréteur résolu, l'état pertinent des dépendances et une nouvelle époque de processus ou de serveur.
Que faire si seul le schéma d'entrée d'un outil MCP change ?
Une modification du schéma d'un outil peut donner un autre sens à un argument auparavant sûr. Actualisez donc les métadonnées chaque fois que le schéma change. Si l'implémentation en cours a aussi changé, remplacez l'identité et invalidez les autorisations qui lui sont liées. Ce sont deux actions distinctes, car les métadonnées et les octets exécutables répondent à des questions différentes.
Puis-je recharger un serveur MCP à chaud en toute sécurité ?
Le rechargement à chaud n'est sûr que si son mécanisme publie une nouvelle époque d'exécution et que la couche d'autorisation la détecte. Un rechargement qui remplace discrètement des gestionnaires dans un processus de longue durée est difficile à examiner et encore plus difficile à auditer. En développement local, le redémarrage complet du processus enfant est généralement plus simple à comprendre.
MCP renégocie-t-il les capacités après la recompilation d'un serveur ?
La spécification MCP Lifecycle considère l'initialisation comme la négociation des capacités pour une connexion, et non comme un protocole général de rebuild. Les notifications de modification de la liste des outils facilitent la découverte, mais elles ne renégocient pas la connexion et n'attestent pas l'identité du nouveau code serveur. Votre hôte ou votre passerelle doit disposer de son propre contrat d'actualisation pour l'identité et les approbations.
Puis-je conserver le même nom d'outil MCP après avoir modifié son comportement ?
Gardez le nom stable uniquement si son niveau d'autorité et la sémantique de ses arguments restent stables. Si deploy passe d'une simulation à un déploiement réel, utilisez un nouveau nom ou imposez une limite claire nécessitant une nouvelle autorisation. Les noms stables sont pratiques pour les prompts, mais la praticité n'est pas une propriété de sécurité.
Que doit enregistrer le journal d'audit pour des serveurs MCP recompilés ?
Enregistrez l'identifiant de l'exécution de l'agent, l'identifiant du processus serveur, l'empreinte de l'exécutable, l'époque du serveur, l'empreinte des métadonnées de l'outil, la décision d'approbation et chaque appel. Vous pourrez ainsi déterminer si un appel a eu lieu avant ou après un rebuild. Un journal qui n'enregistre que les noms d'outils ne permet pas de répondre à cette question.
Comment une passerelle MCP doit-elle gérer un serveur recompilé ?
Une passerelle doit autoriser l'action en fonction de l'identité actuelle du serveur, et pas seulement de l'étiquette fournie par l'agent. Elle doit refuser les appels si l'identité a changé après l'approbation et consigner la décision. Les identifiants doivent rester hors de l'agent et du processus serveur local, sauf si ce processus est explicitement considéré comme fiable.