MCP : exposer son SI aux agents sans ouvrir les vannes
Il y a un moment précis où l’IA cesse d’être un gadget et commence à poser de vraies questions d’architecture. Ce moment, c’est quand un agent a besoin de lire vos données, d’appeler vos services, de déclencher vos workflows. Tant que le LLM se contente de reformuler du texte, il reste à la périphérie du système, là où il ne peut pas faire grand mal. Dès qu’il touche le système d’information, il entre dans le périmètre que vous êtes censé gouverner, avec tout ce que cela implique.
Le Model Context Protocol, ou MCP, est arrivé pour standardiser ce contact. Et comme tout standard d’intégration, il mérite mieux qu’une adoption à la hâte. Bien employé, il transforme votre SI en une plateforme que les agents consomment proprement. Mal employé, il en fait une surface d’attaque que vous avez vous-même installée, outil après outil, en croyant gagner du temps.
Ce que MCP résout vraiment
Avant MCP, connecter un agent à un outil interne relevait du bricolage propriétaire. Chaque fournisseur avait sa façon de déclarer des fonctions, de passer des arguments, de structurer les résultats. Vous écriviez une intégration pour un fournisseur, une autre pour le suivant, et vous recommenciez à chaque changement de modèle ou d’API. Le couplage fort que j’évoquais à propos de l’intégration des LLM en .NET se rejouait une couche plus haut, au niveau des outils cette fois, avec les mêmes conséquences : du code jetable, refait à chaque évolution du paysage.
MCP pose une frontière nette, et c’est tout son intérêt. D’un côté, un serveur expose des ressources, des outils et des invites. De l’autre, un client, l’agent, les découvre et les consomme via un protocole unique. Le serveur ne sait pas quel modèle l’appelle. L’agent ne sait pas comment le serveur est implémenté derrière. C’est l’inversion de dépendance classique, appliquée à la frontière entre l’IA et votre système.
Pour un architecte, la conséquence est immédiate. Votre SI n’a plus à se plier aux caprices d’un fournisseur particulier. Il publie un contrat, et n’importe quel agent compatible peut s’y brancher, aujourd’hui ou dans deux ans, avec un modèle qui n’existe pas encore. On retrouve la même logique que les fichiers cursorrules et llms.txt dont je parlais dans Clean Architecture orientée IA, à une différence majeure : ici le contrat est exécutable, pas seulement documentaire. Ce n’est plus une intention qu’on espère voir respectée, c’est une interface que l’agent appelle réellement.
Trois types de surfaces, pas un seul
MCP ne se résume pas à exposer des fonctions. Le protocole distingue trois natures de surface, et les confondre mène à des erreurs de conception.
Les ressources sont des données que l’agent peut lire : un document, un enregistrement, le contenu d’un fichier. Elles sont par nature passives, l’agent les consulte. Les outils sont des actions que l’agent peut déclencher, avec des effets potentiels sur le système. Les invites, enfin, sont des modèles d’interaction préconçus, que le serveur propose pour cadrer la façon dont l’agent aborde une tâche. La distinction n’est pas cosmétique : une ressource en lecture et un outil qui écrit ne demandent pas du tout le même niveau de prudence, et les traiter pareil, c’est soit brider inutilement les lectures, soit exposer dangereusement les écritures.
Le piège : exposer trop, trop vite
La facilité de MCP est aussi son danger. Déclarer un outil prend quelques lignes. La tentation est donc immense d’exposer tout le SI, fonction par fonction, et de laisser l’agent se débrouiller avec cette grande boîte à outils. C’est une erreur d’architecture, pas une optimisation de productivité.
Un serveur MCP n’est pas une API REST de plus. C’est une surface que va parcourir un système non-déterministe, capable d’enchaîner des appels dans un ordre que vous n’avez pas prévu, parfois sous l’influence d’un texte qu’un tiers a injecté quelque part dans les données qu’il lit. Chaque outil exposé est une capacité que vous accordez à cet automate. La question n’est donc jamais « que puis-je techniquement exposer », mais « qu’est-ce qui doit pouvoir être déclenché par un raisonnement probabiliste, sans qu’un humain ne valide ».
Concrètement, je classe les opérations en trois familles, et chacune a son régime.
Les opérations de lecture sans effet de bord. On peut les exposer assez librement, à condition qu’elles respectent les droits de celui pour qui l’agent travaille. Lire une commande, consulter un catalogue, chercher dans une base documentaire.
Les opérations d’écriture réversibles. On les encadre par des validations, des plafonds, des journaux. Créer un brouillon, ajouter une note à un dossier, modifier un statut qu’on peut remettre en arrière. L’erreur coûte, mais elle se rattrape.
Les opérations irréversibles ou sensibles. Paiement, suppression définitive, envoi vers l’extérieur, modification de droits. On ne les expose presque jamais directement à un agent. Quand on le fait, c’est derrière une confirmation humaine explicite, sortie de la boucle de décision de l’agent. L’automate prépare l’action, un humain la valide.
Un serveur MCP en .NET, pensé comme un adaptateur
En pratique, je traite le serveur MCP comme une couche d’adaptation, exactement au même titre qu’un contrôleur HTTP ou un consommateur de message. Il ne contient aucune logique métier. Il traduit une intention d’agent en appel vers un service applicatif qui, lui, est déjà testé, déjà gouverné, déjà au courant des règles.
// Le serveur MCP n'orchestre rien : il délègue à un service métier existant.
public class CommandeMcpTools
{
private readonly ICommandeService _commandes;
public CommandeMcpTools(ICommandeService commandes) => _commandes = commandes;
[McpTool("get_commande", "Retourne le détail d'une commande par son identifiant.")]
public async Task<CommandeDto> GetCommandeAsync(string commandeId)
{
// Lecture sans effet de bord : exposition tranquille.
return await _commandes.GetByIdAsync(commandeId);
}
[McpTool("rembourser_commande", "Déclenche un remboursement. Action sensible.")]
public async Task<RemboursementResult> RembourserAsync(string commandeId, decimal montant)
{
// Action irréversible : on ne l'expose qu'avec garde-fous.
if (montant <= 0)
throw new ArgumentException("Montant invalide.");
// Le service applique ses propres règles : plafond, autorisation, audit.
// Le serveur MCP ne décide rien, il transmet une intention.
return await _commandes.DemanderRemboursementAsync(commandeId, montant);
}
}
Le point important n’est ni le décorateur ni la signature. C’est que RembourserAsync ne décide rien par lui-même. Il transmet à un service qui connaît les plafonds, qui trace l’opération, qui peut exiger une seconde validation. Le serveur MCP reste mince. Toute la gouvernance vit dans la couche métier, là où elle vivait déjà avant l’IA. Si demain vous exposez le même service via une autre interface, les règles sont au bon endroit, elles ne sont pas dupliquées dans l’adaptateur.
Décrire les outils pour un lecteur non-déterministe
Un détail qui sépare un serveur MCP correct d’un serveur MCP utile : la qualité des descriptions. L’agent choisit quel outil appeler en se fondant sur leur nom et leur description. Une description vague (« gère les commandes ») mène à des appels au mauvais moment, avec les mauvais arguments. Une description précise, qui dit ce que l’outil fait, ce qu’il ne fait pas, et quand l’employer, oriente l’agent bien mieux que n’importe quel réglage du modèle.
[McpTool("rechercher_commandes",
"Recherche des commandes par client ou par période. " +
"Lecture seule. À utiliser pour répondre à une question, " +
"jamais pour modifier une commande. Retourne au plus 50 résultats.")]
public async Task<IReadOnlyList<CommandeDto>> RechercherAsync(CommandeQuery query)
{
// La pagination est imposée côté serveur : l'agent ne décide pas du volume.
return await _commandes.SearchAsync(query, limite: 50);
}
Notez la limite imposée à 50. On ne laisse pas l’agent décider du volume qu’il ramène. Un automate qui demande « toutes les commandes » sans pagination peut faire exploser à la fois la base, le contexte du modèle et la facture en tokens. La description guide l’intention, le code impose les bornes. Les deux sont nécessaires.
La gouvernance fait partie du contrat
Exposer un outil, c’est aussi décider qui peut l’appeler et dans quelles conditions. MCP ne dispense en rien de l’authentification, de l’autorisation, ni de la traçabilité. Au contraire, il les rend plus critiques, parce que l’appelant n’est plus un humain identifié devant son écran, mais un agent qui agit pour le compte de quelqu’un, parfois en enchaînant des dizaines d’appels en quelques secondes.
Je raisonne donc en termes d’identité déléguée. L’agent porte une identité, qui porte elle-même un périmètre. Un agent de support n’accède pas aux mêmes outils, ni aux mêmes données, qu’un agent d’administration. Surtout, l’identité de l’utilisateur final doit traverser toute la chaîne : un agent qui travaille pour un conseiller ne doit voir que ce que ce conseiller pourrait voir lui-même. Le serveur applique ce périmètre avant tout appel, et journalise chaque invocation avec son contexte complet.
public class McpAuthorizationFilter
{
public async Task<bool> PeutInvoquerAsync(AgentIdentity agent, string outil)
{
// Le périmètre est porté par l'identité, pas par l'application.
var perimetre = await _perimetres.GetAsync(agent.RoleId);
if (!perimetre.OutilsAutorises.Contains(outil))
{
_audit.AccesRefuse(agent.Id, outil);
return false;
}
_audit.AccesAccorde(agent.Id, outil);
return true;
}
}
C’est la même discipline de traçabilité que je recommandais pour maîtriser la consommation et imputer les coûts, avec une différence de portée importante : ici, on ne trace pas que des tokens. On trace des actions, et chaque action peut avoir un effet sur le monde réel. Le journal MCP n’est pas un outil de FinOps, c’est une pièce de sécurité et d’auditabilité.
Cette discipline rejoint un sujet que je développerai plus loin dans la série, à propos de la sécurité des agents et de l’injection de prompt. Pour l’instant, retenez le principe directeur. Un serveur MCP est une porte. Une porte se conçoit avec sa serrure, dès le départ, pas après le premier cambriolage.
Ce que MCP change pour l’architecte
MCP ne rend pas l’intégration triviale. Il déplace l’effort, et c’est une bonne nouvelle quand on regarde où il le déplace. Hier, on dépensait son énergie à écrire de la tuyauterie spécifique à chaque fournisseur, un travail fastidieux et sans grand enjeu intellectuel. Aujourd’hui, cette tuyauterie est standard, et l’énergie se reporte là où elle a toujours dû être : décider ce qu’on expose, à qui, dans quelles limites, avec quelles garanties.
Les questions de protocole sont ennuyeuses mais sans grand risque. Les questions de périmètre, elles, sont le cœur du métier d’architecte, parce qu’une erreur de périmètre ne se voit pas à la compilation et ne se rattrape pas par un correctif rapide. MCP nous rend un peu de temps pour les traiter sérieusement. La pire façon d’utiliser ce temps libéré serait de le combler en exposant tout, simplement parce que c’est devenu facile.
Un système d’information bien exposé via MCP devient une plateforme que les agents consomment proprement, sans que personne n’ait jamais à se demander avec inquiétude ce qu’un automate pourrait déclencher à son insu. Mal exposé, il devient une surface qu’un texte bien tourné peut détourner. La frontière entre les deux ne tient pas dans le choix du protocole, qui est le même pour tout le monde. Elle tient dans les décisions que vous prenez, outil par outil, sur ce que vous acceptez de mettre à portée d’un raisonnement que vous ne contrôlez pas entièrement.
// À lire ensuite