Ce qu'est le Model Context Protocol, pourquoi un agent branché dessus devient nettement plus efficace, et ce que ça implique quand on veut le déployer pour de vrai.
Brahim Bousnguar · septembre 2026
Un LLM, c'est un excellent collaborateur enfermé dans une pièce sans fenêtre, sans téléphone et sans accès au SI.
Sur ce qu'on lui met sous les yeux.
De vos référentiels, de vos clients, de l'état d'aujourd'hui.
Aucune action, aucune écriture, aucun effet dans le monde réel.
Tout l'enjeu d'un agent est là : lui ouvrir une fenêtre — proprement.
Avant MCP, brancher un agent sur un système d'information, c'était du câblage à la main. À chaque fois.
« Qui peut démarrer sur la mission Assureur en novembre, dans le budget ? »
Pour répondre, il faut croiser trois systèmes :
Compétences, séniorité, agence, langues.
Qui est vendu, combien de jours, sur quel mois.
Besoin en ETP, date de démarrage, budget TJM.
Trois API. Un humain met vingt minutes. Un agent devrait mettre dix secondes.
On colle la documentation de l'API dans le prompt, et on espère.
Tu as accès à l'API RH. Voici comment l'appeler :
GET /api/v2/employees?skill=<str>&level=<1-5>&agency=<str>
Attention : le paramètre s'appelle "skill", PAS "competence".
Le niveau est un entier. Ne mets pas de guillemets.
GET /api/v2/staffing/load?employee_id=<id>&month=<YYYY-MM>
Attention : format YYYY-MM strictement, sinon 500 silencieux.
POST /api/v2/bookings { "employeeId": ..., "missionId": ... }
Attention : ici c'est camelCase, contrairement aux GET ci-dessus.
La clé API est : sk-live-8f2a91c4d7e...
Réponds en JSON. N'invente jamais de paramètre. Vérifie toujours...
Ce prompt existe, dans à peu près toutes les entreprises, dans à peu près cette forme.
Il écrit competence au lieu de skill. L'API ignore le paramètre inconnu,
répond 200 OK avec tout le catalogue. L'agent croit avoir filtré.
La doc mange des milliers de tokens à chaque tour. Les réponses brutes de l'API en mangent autant. La session utile se raccourcit.
La clé API circule dans le contexte du modèle, dans les logs, dans l'historique de conversation. Toute la sécurité repose sur « ne la répète pas ».
Le même câblage RH est réécrit pour Copilot, pour l'agent support, pour le POC du client d'à côté. Rien ne se capitalise.
Chaque agent doit être câblé à chaque outil. Le coût d'intégration est un produit, pas une somme.
4 × 5 = 20 intégrations à écrire, tester, sécuriser et maintenir. Ajoutez un agent : +5.
Un protocole ouvert, publié fin 2024, qui standardise la façon dont une application d'IA se branche sur un outil.
MCP décrit une seule fois comment un système expose ses données et ses actions, de façon à ce que n'importe quel agent sache s'en servir sans code d'intégration.
MCP est à l'IA ce que l'USB-C est au matériel. Avant : un câble propriétaire par appareil. Après : un port, et tout se branche.
C'est LSP, mais pour les agents. Un éditeur qui parle LSP comprend n'importe quel langage. Un agent qui parle MCP comprend n'importe quel outil.
Techniquement : du JSON-RPC 2.0 sur stdio ou HTTP. Rien d'exotique, et c'est voulu.
Le serveur est le seul à détenir les identifiants du système qu'il expose. Le modèle ne les voit jamais.
Des fonctions que le modèle décide d'appeler : chercher, calculer, écrire. Chacune a un nom, une description et un schéma d'entrée typé.
contrôlé par le modèle
Du contenu adressable par URI que l'application peut charger dans le contexte : un fichier, une fiche, un enregistrement.
contrôlé par l'application
Des amorces de conversation réutilisables, paramétrables, que l'utilisateur déclenche explicitement.
contrôlé par l'utilisateur
En pratique, 90 % de la valeur des serveurs déployés aujourd'hui tient aux tools. C'est sur eux que porte le reste de cette présentation.
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
const server = new McpServer({ name: 'staffing', version: '1.0.0' });
server.registerTool(
'rechercher_collaborateurs',
{
description: "Recherche des collaborateurs disponibles selon une compétence, "
+ "une agence et un mois. Retourne une liste courte et triée.",
inputSchema: z.strictObject({
competence: z.string().describe("En minuscules, ex. 'react', 'kafka'"),
niveau_min: z.number().int().min(1).max(5).optional(),
mois: z.string().regex(/^\d{4}-\d{2}$/, 'Format attendu : AAAA-MM')
})
},
async ({ competence, niveau_min, mois }) => {
const resultats = chercher(competence, niveau_min ?? 1, mois);
return { content: [{ type: 'text', text: formater(resultats) }] };
}
);
await server.connect(new StdioServerTransport());
C'est tout. Ce serveur est utilisable dès maintenant par Claude Code, par VS Code + Copilot,
par Cursor, et par n'importe quel agent maison qui parle MCP.
Attention à la version du SDK : ce code utilise @modelcontextprotocol/server 2.x.
La plupart des tutoriels en ligne utilisent encore @modelcontextprotocol/sdk 1.x, dont les chemins
d'import et la forme de inputSchema diffèrent.
z.strictObject({
competence: z.string()
.describe("ex. 'react'"),
niveau_min: z.number()
.int().min(1).max(5)
.optional(),
mois: z.string()
.regex(/^\d{4}-\d{2}$/)
})
{
"type": "object",
"properties": {
"competence": { "type": "string",
"description": "ex. 'react'" },
"niveau_min": { "type": "integer",
"minimum": 1, "maximum": 5 },
"mois": { "type": "string",
"pattern": "^\\d{4}-\\d{2}$" }
},
"required": ["competence", "mois"],
"additionalProperties": false
}
Le contrat est généré, envoyé au modèle et vérifié à l'exécution. Un paramètre inventé ne passe plus : il est rejeté avec un message que l'agent peut lire et corriger.
Mêmes agents, mêmes systèmes. Le protocole se met au milieu — et l'enchevêtrement disparaît.
4 + 5 = 9 branchements. Ajoutez un agent : +1, et il hérite de tous les serveurs existants.
MCP est un standard ouvert. Les clients qui le parlent nativement aujourd'hui :
Claude & Claude Code · VS Code + GitHub Copilot · Cursor · Zed · JetBrains · Windsurf · et tout agent bâti sur un SDK officiel (TypeScript, Python, Java, C#, Kotlin, Go).
GitHub, Sentry, Figma, Atlassian, Stripe, Notion, Cloudflare, Postgres, Playwright… et surtout : les vôtres, qui exposent votre SI.
Le serveur MCP que vous écrivez pour une mission n'est pas un adaptateur jetable pour un outil donné. C'est un actif qui fonctionne avec le client d'aujourd'hui et celui de l'année prochaine.
Quatre mécanismes concrets. Aucun ne tient à un meilleur modèle : ce sont des effets d'architecture.
Le format des paramètres est décrit en prose, dans le prompt. Le modèle doit s'en souvenir à chaque appel, au milieu de tout le reste.
Quand il se trompe, l'API répond souvent 200 OK
en ignorant le paramètre inconnu. L'erreur est invisible.
Le format est un schéma, transmis avec l'outil et vérifié par le serveur avant d'exécuter quoi que ce soit.
Quand il se trompe, il reçoit une erreur précise, lisible, et se corrige tout seul au tour suivant.
# Le modèle invente un paramètre "nom" qui n'existe pas :
→ { "name": "rechercher_collaborateurs", "arguments": { "nom": "Clara" } }
# Sans validation stricte — le serveur ignore la clé inconnue :
← "5 résultat(s) : c-007 Leila Haddad… c-003 Clara Nunes… c-004 Yanis Cherif…"
⚠ succès apparent, filtre jamais appliqué, l'agent poursuit sur du faux.
# Avec z.strictObject() — capture réelle du serveur de démo :
← isError: true
"Input validation error: Unrecognized key: \"nom\""
✓ l'agent lit l'erreur, relit le schéma, rappelle avec "competence".
Même question : « la mission m-101 est-elle staffable ? »
1. GET /missions/m-101 → 1 objet
2. GET /employees → 8 objets complets,
toutes compétences,
toutes langues, tous TJM
3. GET /staffing/load?e=c-001 ┐
4. GET /staffing/load?e=c-002 │ 8 appels
… ┘
11. le modèle croise à la main :
compétences requises vs notées,
jours vendus vs capacité,
somme des ETP, comparaison budget
≈ 11 tours · ≈ 9 500 tokens (estimation)
arithmétique faite par le modèle
1. analyser_staffing_mission
{ "mission_id": "m-101" }
← Refonte du portail souscription
Assureur régional (m-101)
Démarrage 2026-11, 6 mois, TJM max 650 €
Couverture suffisante :
3 ETP disponibles pour 2 requis.
Meilleurs candidats :
100/100 Sophie Marchand (c-005)
20 j · toutes compétences couvertes
70/100 Clara Nunes (c-003)
20 j · manque : node (0/4 requis)
≈ 1 tour · ≈ 260 tokens (mesuré)
arithmétique faite par du code testé
La colonne de droite est la sortie réelle du serveur de démo ; celle de gauche est mon estimation du chemin REST équivalent. Et le gain n'est pas seulement le coût : un calcul fait par du code est reproductible, un calcul fait par un modèle ne l'est pas.
La clé API est dans le prompt système. Donc dans le contexte du modèle, dans les traces, dans l'historique de session, et dans tout ce qui journalise ces échanges. Elle est aussi la même pour tout le monde.
Le serveur détient le secret et l'utilise côté serveur. Le modèle voit un nom d'outil et un schéma ; jamais un identifiant.
Un serveur MCP en HTTP peut exiger un jeton OAuth et n'exposer que ce que l'utilisateur connecté a le droit de voir. L'agent d'un chef de projet et celui d'un directeur d'agence appellent le même outil et n'obtiennent pas les mêmes lignes. Ce n'est pas du prompt engineering — c'est du contrôle d'accès classique, au bon endroit.
Tout ce que l'agent fait sur ce système passe par un seul composant. C'est là qu'on met les garde-fous.
Qui a appelé quel outil, avec quels arguments, quand. Une piste d'audit réelle, pas une reconstitution à partir des logs du modèle.
Le serveur borne la pagination, plafonne les résultats, limite la fréquence. L'agent ne peut pas faire tomber le SI par maladresse.
Le même serveur, exposé en lecture seule à un agent et en écriture à un autre. Une ligne de configuration, pas un fork du code.
Les règles vivent dans le serveur, testées, versionnées. Elles ne dépendent pas de la formulation du prompt du jour.
Sur ma propre flotte d'agents, restreindre l'accès d'un serveur à 4 agents sur 14 a été une clé de configuration. Le même besoin, câblé à la main, aurait touché 14 bases de code.
| Doc d'API dans le prompt | Outil MCP | |
|---|---|---|
| Tours d'agent | ≈ 11 (est.) | 1 (mesuré) |
| Tokens consommés | ≈ 9 500 (est.) | ≈ 260 (mesuré) |
| Appels malformés | silencieux, non détectés | rejetés, avec un message exploitable |
| Calcul métier | fait par le modèle, non reproductible | fait par du code, testé |
| Identifiants | dans le contexte du modèle | côté serveur uniquement |
| Réutilisation | réécrit par agent et par équipe | un serveur, tous les clients |
| Audit | à reconstituer | natif, au point de passage |
La colonne « outil MCP » est mesurée sur le serveur de démo. Celle de gauche est une estimation du chemin REST équivalent, pas un chiffre de brochure. Le rapport exact dépend de votre domaine — la direction, elle, ne change pas.
Chaque token dépensé à lire un dump JSON, à se rappeler un format de paramètre ou à refaire une addition est un token qui n'est pas dépensé à raisonner sur le problème. MCP ne rend pas le modèle plus malin : il arrête de lui faire perdre son temps.
Un serveur MCP complet et open source, que vous pouvez cloner et brancher en deux minutes.
Domaine volontairement familier : le staffing d'une ESN. Données en mémoire, aucune base, aucun appel réseau — une démo ne doit dépendre de rien.
| Outil | Ce qu'il fait | Ce qu'il illustre |
|---|---|---|
| rechercher_collaborateurs | Filtre par compétence, niveau, agence, disponibilité | Le schéma remplace la doc |
| obtenir_collaborateur | La fiche détaillée d'un seul identifiant | Le détail se paie à la demande |
| analyser_staffing_mission | Score d'adéquation, écarts, ETP, alerte budget | Le serveur calcule, pas le modèle |
| reserver_collaborateur | Pose une réservation, ou échoue explicitement | Une écriture qui ne ment jamais |
Quatre outils, pas quarante. C'est un choix de conception — on y revient en partie 5.
server.registerTool(
'analyser_staffing_mission',
{
description: "Évalue quels collaborateurs peuvent couvrir une mission : score "
+ "d'adéquation, écarts de compétences, coût estimé et alerte budget. "
+ "Une seule réponse, déjà calculée.",
inputSchema: z.strictObject({ mission_id: z.string(), candidats_max: z.number().default(3) }),
outputSchema: z.object({ etp_requis: z.number(), etp_couvert: z.number(), candidats: z.array(/* … */) })
},
async ({ mission_id, candidats_max }) => {
const mission = missions.find(m => m.id === mission_id);
if (!mission) return erreur(`Mission '${mission_id}' inconnue. Disponibles : ${ids}.`);
// Le croisement compétences × charge × budget se fait ICI, en TypeScript testé,
// pas dans le contexte du modèle.
const candidats = evaluer(mission).slice(0, candidats_max);
return {
content: [{ type: 'text', text: resume(mission, candidats) }], // pour le modèle
structuredContent: { etp_requis: mission.etpRequis, etp_couvert, candidats } // pour le code
};
}
);
content est la réponse en langage naturel que le modèle lit.
structuredContent est le même résultat en JSON typé, pour le code qui orchestre.
Un seul appel sert les deux.
Trois commandes, puis une entrée de configuration. C'est tout ce qui sépare un agent de ces quatre outils.
git clone https://github.com/brbousnguar/mcp-staffing-demo
cd mcp-staffing-demo
npm install && npm run build
{
"mcpServers": {
"staffing": {
"command": "node",
"args": ["/chemin/absolu/vers/mcp-staffing-demo/dist/index.js"]
}
}
}
claude mcp add staffing -- node …/dist/index.js
Le même bloc JSON dans .vscode/mcp.json.
Le SDK client, dans le langage de votre choix.
Le serveur n'a rien à savoir du client. Le client n'a rien à savoir du serveur. C'est précisément l'intérêt.
Vous ▸ Est-ce qu'on peut staffer la mission de l'assureur qui démarre
en novembre, et si oui avec qui ?
Agent ▸ [appelle analyser_staffing_mission { mission_id: "m-101" }]
Oui, la mission est couvrable : 3 ETP disponibles pour 2 requis.
· Sophie Marchand (c-005) — 100/100. Toutes les compétences requises
sont couvertes, 20 jours libres en novembre, TJM 610 € sous le
plafond de 650 €. C'est le profil évident.
· Clara Nunes (c-003) — 70/100. Solide sur React et TypeScript,
mais aucune expérience Node alors que la mission en demande un
niveau 4. Viable en binôme avec Sophie, pas en autonomie.
Point d'attention : Marc Delaunay correspondrait parfaitement au
profil, mais il est vendu à 100 % jusqu'en décembre.
Vous ▸ Réserve Sophie 20 jours sur novembre.
Agent ▸ [appelle reserver_collaborateur { … jours: 20 }]
Réservation r-001 posée. Reste 0 j disponibles.
L'agent ne « connaît » rien au staffing. Il sait lire un schéma, appeler un outil, et restituer un résultat calculé ailleurs. C'est suffisant — et c'est vérifiable.
Ce que j'ai appris en faisant tourner une flotte d'agents branchés sur des serveurs MCP maison, tous les jours, pendant plusieurs mois.
Un agent devait corriger une entrée dans un de mes outils de suivi. Il a appelé la fonction
de mise à jour avec un paramètre name — un paramètre que le modèle avait inventé.
Le serveur, permissif, a ignoré la clé inconnue, n'a rien modifié, et a répondu success: true.
Pas le modèle. Un serveur qui acceptait une entrée invalide et rendait un succès sans effet.
Le serveur de démo applique les deux. Le passage de z.object à
z.strictObject a été écrit pendant la préparation de cette présentation —
parce que le premier test l'a immédiatement reproduit.
Mes premiers outils calquaient les colonnes de la table. Résultat : pour une seule intention métier, l'agent enchaînait cinq appels et devait faire lui-même le raccord — avec une chance sur cinq de se tromper de champ.
En exposant les deux grandeurs que l'utilisateur distingue réellement, la même intention est devenue un seul appel, sans arbitrage laissé au modèle. La boucle a disparu du jour au lendemain.
« Quelle intention mon utilisateur exprime-t-il ? » — et non « quelle ligne de ma base est-ce que j'expose ? ». Un serveur MCP n'est pas un ORM sur HTTP. C'est une API conçue pour un lecteur qui ne pose jamais de question de clarification.
Son nom, sa description et son schéma sont envoyés au modèle à chaque tour. Quarante outils, c'est un budget de contexte dépensé avant même la première question.
Plus les outils se ressemblent, plus le modèle hésite. Deux fonctions de recherche presque identiques sont pires qu'une seule bien nommée.
N'exposez à chaque agent que les serveurs dont il a besoin. Chez moi, c'est une règle de configuration côté passerelle ; en entreprise, ce sera une politique.
La bonne mesure : un serveur MCP couvre un domaine, avec des outils qui se distinguent en une phrase. Si vous devez expliquer la différence entre deux outils, le modèle ne la trouvera pas non plus.
Parce qu'une technologie qu'on présente sans limites n'est pas une technologie, c'est une brochure.
Le protocole ne vous dit pas quoi exposer, ni à quelle granularité. C'est là que se joue l'essentiel du résultat, et ça ne s'automatise pas.
MCP supprime des erreurs d'intégration, pas des erreurs de raisonnement. Un modèle faible avec de bons outils reste un modèle faible.
Un serveur en HTTP qui doit distinguer les droits de chaque utilisateur, c'est de l'OAuth, de la gestion de jetons et de la revue de sécurité. Ni gratuit, ni instantané.
Il se déploie, se supervise, se met à jour et tombe en panne. Sur ma propre flotte, la panne la plus fréquente n'a jamais été le modèle : c'était une adresse d'écoute mal configurée.
Toute organisation qui branche des agents sur son SI paie l'écart entre N×M et N+M — ou le gagne.
Chaque mission qui branche une IA sur un SI écrit aujourd'hui son propre câblage. Ce câblage est jeté à la fin de la mission. C'est là que se trouve la perte.
Un serveur par domaine récurrent — un référentiel, un outil de ticketing, un socle d'intégration. Écrit une fois, versionné, testé.
Le connecteur écrit pour un client devient un patron pour le suivant. On ne livre plus un script, on livre un composant.
« Nous savons rendre votre SI utilisable par vos agents, avec l'audit et le contrôle d'accès qui vont avec » est une offre. Pas une ligne de CV.
Les clients vont demander des agents branchés sur leur SI dans les mois qui viennent. La question ne sera pas « savez-vous faire du LLM », mais « savez-vous connecter un agent à notre SI sans tout ouvrir ? ».
| Question | Pourquoi elle se pose tôt |
|---|---|
| Qui valide un serveur ? | Un serveur MCP donne à un agent un droit d'action réel sur un système. Ça se revoit comme une API exposée, pas comme un script. |
| Lecture seule par défaut ? | La règle la plus simple et la plus efficace : les outils d'écriture sont l'exception, explicitement accordée, jamais le défaut. |
| Quelle identité l'agent porte-t-il ? | Un compte de service partagé est confortable et intraçable. L'identité de l'utilisateur coûte plus cher et vous sauve en audit. |
| Où vivent les serveurs tiers ? | Un serveur MCP externe s'exécute chez vous avec vos secrets. Il se traite comme une dépendance : source connue, version épinglée, revue. |
| Qu'est-ce qu'on journalise ? | Les appels d'outils sont la seule trace fiable de ce qu'un agent a réellement fait. Sans eux, aucune réponse possible en cas d'incident. |
Aucune de ces questions n'est propre à l'IA. Ce sont des questions d'exposition d'API — posées à un consommateur qui n'appellera jamais le support.
Ce que je recommande : six semaines, un domaine, une mesure. Pas un comité.
| Étape | Contenu | Livrable |
|---|---|---|
| S1–S2 | Choisir un domaine interne à fort usage — staffing, ticketing ou référentiel — et concevoir 4 à 6 outils au niveau de l'intention métier. | Spécification des outils |
| S3–S4 | Écrire le serveur : schémas stricts, lecture seule, journalisation des appels, jeu de tests. | Serveur déployé en interne |
| S5 | Le brancher sur les clients que les équipes utilisent déjà (Copilot, Claude Code) auprès d'un groupe restreint. | Retours réels d'usage |
| S6 | Mesurer : temps sur les questions visées, tours d'agent, erreurs. Décider d'étendre ou d'arrêter. | Note de décision chiffrée |
Si au bout de six semaines les équipes ne rouvrent pas l'outil d'elles-mêmes, le pilote a échoué et on le dit. Un pilote qui ne peut pas échouer ne mesure rien.
Ouvert, simple, déjà parlé par les outils que vos équipes utilisent. Vous n'achetez rien et vous ne vous enfermez chez personne.
Moins de tours, moins de tokens, moins d'erreurs silencieuses, un point de contrôle unique. Sans changer de modèle.
Exposez des intentions, pas des tables. Schémas stricts, jamais de succès vide, peu d'outils bien nommés.
Rendre un SI utilisable par des agents — avec l'audit, les droits et la supervision — est exactement ce qu'une ESN sait faire.
Le serveur de démo complet et les instructions de branchement sont en open source.
github.com/brbousnguar/mcp-staffing-demo
Brahim Bousnguar