Cinq questions à trancher avant de mettre MCP en production
SOIT Team

MCP a normalisé la partie ennuyeuse consistant à donner des outils à un agent : un point d'entrée HTTP, un appel à list_tools, et les outils apparaissent dans la liste appelable du modèle. La première fois que nous en avons branché un en interne, cela a pris un après-midi, et cette partie est réellement réglée. Puis nous avons voulu le mettre en production, quelqu'un de la sécurité a posé cinq questions, et aucune n'avait de réponse. Cet article, ce sont ces cinq questions et la manière dont elles ont fini par être traitées dans SOIT, un runtime d'agents open source dont la gouvernance se tient au milieu du chemin d'appel. Chaque affirmation renvoie à un fichier du dépôt, parce que ce genre de texte est étonnamment facile à écrire sous forme de diaporama plutôt que de logiciel.
La première question est de savoir qui a le droit d'appeler un outil donné. MCP n'a pas d'avis : ce que renvoie list_tools est ce que le modèle peut appeler, donc la visibilité vaut capacité. Dans un déploiement multi-locataire et multi-espace, cela ne suffit pas, aussi SOIT installe-t-il un serveur MCP comme artefact de plugin et non comme entrée de configuration. Les références d'outils sont préfixées par un espace de noms et chaque résolution transporte un contexte de requête portant le locataire, l'espace de travail et l'utilisateur. Deux conséquences en découlent : du point de vue de l'agent, un outil de plugin, un outil MCP et un adaptateur intégré se ressemblent en tout point, avec des liaisons typées et versionnées ; et les contrôles de permission, l'injection de secrets, les limites de sortie, l'audit, l'attribution des coûts, le traçage et la relecture s'appliquent automatiquement aux outils MCP, si bien que personne n'écrit deux fois le chemin de gouvernance. Chaque version d'agent porte en outre une liste de capacités autorisées couvrant modèles, bases de connaissances, workflows, outils, plugins et serveurs MCP, ce qui fait de la question des outils appelables par la version trois d'un agent quelque chose que l'on peut comparer et annuler.
La deuxième question porte sur l'endroit où vivent les identifiants. La plupart des exemples d'intégration placent un jeton en clair dans un fichier de configuration, et c'est ainsi qu'il se retrouve dans git, dans les journaux et dans la sauvegarde exportée sur un portable. SOIT refuse cela d'emblée : la construction de l'authentification vérifie la présence d'un champ littéral de jeton ou de valeur et lève une erreur indiquant que les identifiants doivent utiliser un identifiant de secret. Seule une référence est acceptée, résolue par le port des secrets au moment de l'appel, et les clés d'API ne sont admises que dans les en-têtes, jamais dans la chaîne de requête, car celle-ci fuit par les journaux et les référents. La valeur réelle n'existe en mémoire que pendant l'appel ; ce qui est persisté en base, dans l'audit et dans les traces est une copie expurgée construite dans la même passe, ne conservant que l'identifiant du secret et la référence de politique de signature. Trois types d'authentification sont pris en charge, et OAuth suit la forme 2.1 avec découverte du serveur d'autorisation et jetons liés à la ressource via les identifiants client. Une limite mérite d'être énoncée franchement : le flux de code d'autorisation par navigateur n'est pas implémenté, car SOIT appelle les serveurs MCP en son nom propre et non au nom d'un utilisateur assis devant un navigateur.
La troisième question est celle qui devrait le plus inquiéter : jusqu'où la chose peut-elle se connecter. Un serveur MCP effectue des requêtes réseau en votre nom : mettez une URL dans les arguments et il ira la chercher, la forme classique de cette attaque consistant à lui demander l'adresse de métadonnées du cloud qui détient des identifiants temporaires. La politique de sortie de SOIT refuse par défaut, en trois couches. La première est la politique de domaine, qui confronte la cible à des listes d'autorisation et de blocage au niveau du locataire et de l'espace de travail, la liste de blocage l'emportant ; la liste d'autorisation est vide par défaut, donc rien ne passe tant que vous ne l'avez pas dit, et si la consultation de la politique échoue elle-même, la réponse est le refus et non l'autorisation. Échouer fermé n'est pas un slogan : c'est ce que vous avez réellement écrit dans chaque branche d'exception. La deuxième couche valide chaque adresse après résolution, car le DNS rebinding permet à un nom autorisé de pointer vers une adresse de bouclage ou privée : le garde résout le nom et exige que toutes les adresses renvoyées soient routables publiquement, refusant la requête entière si l'une ne l'est pas, tout en refusant les schémas autres que http, les URL portant des identifiants et en traitant un échec DNS comme un refus. La troisième couche autorise chaque saut, car une URL ayant franchi les deux premières peut encore renvoyer une redirection vers votre réseau interne. L'autorisation est donc accrochée au hook d'événement de requête du client HTTP sortant, de sorte que chaque requête réellement émise est contrôlée, sauts de redirection compris, et les redirections ne sont pas suivies par défaut. L'adaptateur MCP construit ses sessions avec ce client, si bien que l'initialisation, le listage et chaque appel restent dans ces contraintes.
La quatrième question est de savoir si l'on peut apprendre après coup ce qui s'est passé. Les appels d'outils sont le seul endroit où un agent produit de vrais effets de bord : un modèle qui dit une bêtise peut être réinterrogé, mais un outil qui a modifié une ligne en base de production l'a modifiée. SOIT persiste chaque appel comme une étape d'une exécution et écrit deux preuves. L'audit de passerelle enregistre la référence de l'outil, les paramètres expurgés et la décision de sortie côté requête, puis le succès, le type de résultat, les métadonnées et l'erreur côté réponse ; le chemin d'échec écrit lui aussi, et émettre cet enregistrement est la première chose que fait la branche d'exception, ce que l'on oublie précisément et dont on a besoin quand quelque chose a mal tourné. À côté, les métriques d'étape relèvent la latence, l'indicateur de succès, les arguments et résultats résumés, le code et le détail de l'erreur. Le même appel écrit une entrée de coût nommant le fournisseur et le port d'origine, de sorte que le coût mensuel des outils MCP d'un agent est une requête que l'on peut détailler par agent, workflow, outil et type de source. Au-dessus se trouve une trace OpenTelemetry portant les identifiants de locataire, d'espace, d'exécution et d'étape, pour l'APM que vous utilisez déjà.
La cinquième question est celle de la relecture. La propriété la plus frustrante du débogage d'agents est qu'il ne reproduit pas : même entrée, raisonnement différent. À la couche outil, on peut au moins être déterministe. Chaque appel porte une clé d'idempotence, dérivée par défaut des identifiants d'exécution et d'appel, et réclame un enregistrement d'exécution sous bail ; si la réclamation tombe sur un enregistrement déjà terminé, la réponse mise en cache revient directement et l'outil externe n'est pas rappelé. La politique de réessai change en conséquence, tombant à une seule tentative dès qu'une clé d'idempotence est présente, et le commentaire du code le dit mieux qu'un paragraphe : les appels durables sont au plus une fois à cette frontière, car tous les adaptateurs en aval n'honorent pas votre clé. Appeler une fois de trop peu vaut mieux qu'une fois de trop, et pour les écritures ce compromis n'est pas vraiment un choix. Les limites de débit et les quotas quotidiens suivent, indexés par référence d'outil, locataire, espace et utilisateur, afin qu'un agent emballé ne brûle pas le budget d'API tiers de tout un locataire.
La liste honnête de ce que cela ne fait pas : le transport est uniquement HTTP en flux face à la ligne v1 du SDK MCP, et la révision sans état du 28 juillet 2026 n'est pas encore prise en charge ; OAuth se limite aux identifiants client ; une place de marché pour installer un outil MCP en un clic figure à la feuille de route, si bien qu'aujourd'hui on installe les artefacts à la main ; et une liste de sortie vide par défaut signifie que votre premier serveur MCP sera refusé tant que vous n'aurez pas ajouté son domaine, ce qui est délibéré mais ajoute une étape au démarrage rapide. Une autre question revient sans cesse : quel rapport avec les frameworks d'agents. Ce ne sont pas les mêmes couches. Un framework répond à la question de l'orchestration d'un appel ; un runtime répond de l'identité sous laquelle il a tourné, des identifiants employés, de ce qu'il pouvait atteindre, des preuves laissées et de sa relecture possible. La première préoccupation vous occupe pendant l'écriture du code, la seconde après sa mise en service, quand un autre pose la question. On peut évidemment placer les contrôles de permission dans un framework, mais alors chaque nouvelle intégration réimplémente la logique de gouvernance, alors que la descendre dans la couche de ports du runtime est précisément ce qui fait que les outils MCP, les outils de plugin et les outils intégrés empruntent le même chemin. SOIT est sous Apache-2.0 et le code est sur github.com/soit-ai/soit, avec le démarrage rapide et une démonstration de gouvernance de vingt minutes dans le dossier de documentation. Si vous poussez MCP vers la production en ce moment, la plus difficile des cinq questions n'est en général pas technique : c'est de savoir qui décide de ce qui entre dans la liste d'autorisation.