Depuis dix-huit mois, écrire un serveur MCP relevait d’un compromis inconfortable : le protocole avait été pensé comme une session bidirectionnelle longue entre un client et un serveur local, mais l’essentiel des déploiements sérieux se sont retrouvés derrière un load balancer, dans un container ou une fonction serverless. La révision 2026-07-28 de la spécification tranche ce nœud : le cœur du protocole devient stateless, et tout ce qui exigeait de la mémoire de session est repoussé dans un framework d’Extensions versionné indépendamment.
Pour les équipes qui ont livré un serveur MCP en 2025 (spec 2025-03-26 ou 2025-06-18), il ne s’agit pas d’un simple bump de version : c’est une révision d’architecture. Voici ce qui change, et dans quel ordre s’y attaquer.
Ce que MCP résout, et pourquoi le modèle stateful coinçait
Rappel rapide : MCP standardise la façon dont un modèle — ou plutôt l’application hôte qui l’entoure — découvre et invoque des capacités externes : tools (fonctions exécutables), resources (contenus adressables), prompts (modèles réutilisables). L’intérêt est combinatoire : un serveur MCP écrit une fois est consommable par n’importe quel client conforme, sans intégration ad hoc.
Le modèle de 2025 supposait toutefois un cycle de vie explicite : un initialize négociant la version du protocole et les capacités, une session identifiée (Mcp-Session-Id en Streamable HTTP), puis un flux d’échanges pouvant aller dans les deux sens — le serveur pouvant à son tour appeler le client (sampling, roots, elicitation) ou pousser des notifications sur un canal SSE ouvert.
Élégant en local avec un transport stdio. Douloureux en production :
- Affinité de session obligatoire. L’état de négociation vivant en mémoire, il fallait des sticky sessions, donc un routage L7 conscient du header de session et un scaling horizontal contrarié.
- Connexions longues. Un canal SSE ouvert pendant des minutes se heurte aux timeouts d’API Gateway, aux idle timeouts des load balancers managés et au modèle d’exécution des fonctions serverless.
- Déploiements impossibles à froid. Impossible de servir MCP depuis un Worker edge ou une Lambda sans bricoler un store de session externe et un mécanisme de reprise.
- Redéploiements destructeurs. Un rolling update invalidait toutes les sessions actives.
Le cœur stateless : chaque requête se suffit à elle-même
Le principe de 2026-07-28 est simple à énoncer : toute requête doit être auto-contenue. Le contexte nécessaire à son traitement — version de protocole, capacités du client, identité — voyage avec elle, au lieu d’être déduit d’un initialize antérieur. Concrètement, la négociation de capacités devient une propriété de l’appel, pas de la connexion.
Un appel d’outil ressemble alors à ceci (les noms de champs exacts sont à vérifier dans la spec, mais l’esprit est là) :
{
"jsonrpc": "2.0",
"id": "req-8f21",
"method": "tools/call",
"params": {
"name": "search_invoices",
"arguments": { "customer": "ACME", "since": "2026-01-01" },
"_meta": {
"protocolVersion": "2026-07-28",
"clientCapabilities": { "sampling": {}, "elicitation": {} }
}
}
}
Les conséquences opérationnelles sont immédiates et bienvenues :
- Plus de sticky sessions. Deux requêtes consécutives du même client peuvent atterrir sur deux pods différents. Le round-robin suffit.
- Serverless et edge deviennent des cibles de premier rang. Lambda, Cloud Run, Cloud Functions, Workers : un handler HTTP POST, une réponse, fin. Le scale-to-zero redevient possible.
- Les redéploiements deviennent non événementiels. Aucun état chaud à drainer.
- Le cache HTTP redevient utilisable pour les réponses idempotentes (
tools/list,resources/readsur contenu stable), avec des ETags.
En contrepartie, tout ce qui nécessitait réellement une session — un traitement long, un serveur qui interroge le client en cours d’exécution — doit être modélisé explicitement. C’est le rôle des Extensions.
Tasks : le long-running sans garder la socket ouverte
L’extension Tasks est celle qui touchera le plus de serveurs existants. Le motif est classique et emprunté aux API REST asynchrones : un appel d’outil qui ne peut pas répondre en quelques secondes retourne immédiatement un handle, et le client interroge ensuite le cycle de vie de la tâche.
// Réponse à tools/call pour un outil long
{
"jsonrpc": "2.0",
"id": "req-8f21",
"result": {
"task": {
"taskId": "tsk_01JX9Z",
"status": "working",
"pollInterval": 2000
}
}
}
Le client enchaîne ensuite sur tasks/get pour lire l’état, tasks/update pour fournir une entrée complémentaire (typiquement une réponse à une demande d’elicitation, ou une validation humaine), et tasks/cancel pour interrompre. Les statuts couvrent le vocabulaire attendu : en cours, terminé avec résultat, échoué, annulé, en attente d’entrée.
Cette bascule a une vertu au-delà de la scalabilité : elle rend l’asynchronisme visible dans le contrat. En 2025, un outil qui prenait quatre minutes était indistinguable, côté client, d’un outil qui prenait 200 ms — d’où des timeouts arbitraires et des retries dupliquant les effets de bord. Un handle de tâche est idempotent par construction : rejouer tasks/get ne relance rien.
Côté implémentation, la tâche doit vivre hors du process : Redis, DynamoDB, une table Postgres, ou une file (SQS, Pub/Sub) avec un worker séparé. C’est exactement le travail d’externalisation que le stateless impose de toute façon.
Le framework Extensions : reverse-DNS, capabilities, versions
Le second changement structurant est gouvernance-first : le cœur du protocole se réduit à un socle stable, et tout le reste — y compris Tasks — devient une extension identifiée par un identifiant reverse-DNS et versionnée indépendamment.
{
"extensions": {
"io.modelcontextprotocol/tasks": { "version": "1" },
"com.acme.compliance/audit-trail": { "version": "2", "region": "eu" }
}
}
Trois bénéfices concrets :
- Découvrabilité explicite. Un client sait ce qu’il peut utiliser sans heuristique ni sniffing de version.
- Découplage des cadences. Une extension peut itérer sans attendre une révision du cœur, et une extension propriétaire cohabite proprement avec les extensions officielles — sans squatter le namespace standard.
- Dégradation gracieuse. Un client qui ignore
com.acme.compliance/audit-trailcontinue de fonctionner.
Deux extensions officielles méritent d’être regardées dès maintenant :
- MCP Apps : un serveur peut exposer une interface HTML rendue par le client dans une iframe sandboxée, avec un canal de communication normalisé vers le serveur. Cela résout un vrai problème — certains outils sont irréductibles à du texte : sélection sur une carte, tableau filtrable, formulaire de configuration. Le modèle de sécurité (sandbox, origine, absence d’accès direct au contexte du modèle) est ici le point à auditer en priorité.
- Skills over MCP : la distribution de compétences — instructions, procédures, scripts — via MCP, plutôt que la seule exposition d’outils atomiques. C’est le pendant protocolaire de la tendance « instructions versionnées » qu’on retrouve autour d’
AGENTS.md.
Autorisation : OAuth 2.1 / OIDC durci
L’autorisation MCP reposait déjà sur OAuth 2.1 depuis 2025 (PKCE obligatoire, metadata de serveur de ressources, Resource Indicators de la RFC 8707). La nouvelle révision serre les boulons dans une direction claire : le serveur MCP est une Resource Server, pas un Authorization Server, et l’authentification doit pouvoir être déléguée à l’IdP de l’entreprise.
Les points d’attention pour une implémentation :
- Jetons à portée restreinte et audience vérifiée. Un access token émis pour votre serveur MCP ne doit jamais être accepté par un autre — ni réutilisé en amont vers une API tierce. Vérifiez
audsystématiquement ; refusez avec un401et unWWW-Authenticatepointant vers votre metadata.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.acme.com/.well-known/oauth-protected-resource",
error="invalid_token"
- Le piège de la délégation d’identité. Un agent agit pour un utilisateur, mais pas comme lui. Réutiliser tel quel le jeton utilisateur donne à l’agent l’intégralité des droits humains, y compris ceux qu’aucune boucle autonome ne devrait avoir. La bonne approche est un échange de jetons (RFC 8693) produisant un jeton dérivé, à scopes réduits, à durée de vie courte, et portant une revendication distinguant l’agent de l’humain — afin que les logs d’audit en aval restent lisibles.
- Consentement lisible. Un écran d’autorisation qui affiche « accès à vos données » sans énumérer les outils sensibles est une invitation à l’accident.
Migrer un serveur MCP 2025, étape par étape
1. Audit de l’état conservé. Grepper les structures vivant entre deux requêtes. Le motif typique de 2025 :
// Avant : un objet serveur par session, en mémoire du process
const sessions = new Map<string, ServerSession>();
app.post("/mcp", (req, res) => {
const session = sessions.get(req.header("Mcp-Session-Id"))!;
return session.handle(req.body, res);
});
Classez chaque élément d’état : (a) dérivable de la requête → à supprimer ; (b) cache de performance → à déplacer vers un cache partagé ou à recalculer ; (c) état métier réel (progression, curseur de pagination, transaction) → à externaliser.
2. Externaliser. Un store à clé (Redis, DynamoDB, Postgres) suffit dans la majorité des cas. Deux règles : donner un TTL à tout, et faire du handle de tâche un identifiant opaque et non devinable — c’est aussi une surface d’accès.
3. Adapter le transport. Le handler devient une pure fonction requête → réponse :
app.post("/mcp", async (req, res) => {
const auth = await verifyAccessToken(req.header("Authorization")); // aud, scopes, exp
const result = await dispatch(req.body, { auth, store }); // aucun état local
res.json(result);
});
4. Négociation de version. Ne cassez pas vos clients existants. Servez les deux mondes : si la requête déclare 2026-07-28, empruntez le chemin stateless ; si elle envoie un initialize avec une version 2025, conservez le chemin legacy — quitte à le faire reposer sur le même store externe. Annoncez une date de fin de support explicite.
5. Tests de compatibilité descendante. Trois scénarios non négociables : un client 2025 sur l’ancien chemin ; un client 2026 dont deux requêtes consécutives frappent deux instances différentes (tuez un pod entre les deux) ; un client 2026 ignorant l’extension Tasks, qui doit recevoir une erreur explicite plutôt qu’un timeout.
Sécurité opérationnelle : ce que 2025-2026 a coûté
Les incidents de l’écosystème ont un point commun : la surface d’attaque n’est pas le protocole, c’est la confiance implicite.
- Tool poisoning. Les descriptions d’outils sont ingérées par le modèle : ce sont des données non fiables, pas de la documentation. Épinglez les hashes des définitions, alertez sur toute modification de description entre deux démarrages.
- Exfiltration par composition. Un serveur en lecture (fichiers, tickets) plus un serveur en écriture réseau suffisent à sortir des données, sans faille dans aucun des deux. Raisonnez par paires d’outils autorisés, pas outil par outil.
- Serveurs exposés sans authentification. Le classique
0.0.0.0au lieu de127.0.0.1, aggravé par des attaques de DNS rebinding depuis un navigateur. Bindez explicitement en loopback, validez l’en-têteOrigin, exigez un jeton même en local. - Chaîne d’approvisionnement. Vérifiez l’origine des serveurs installés, épinglez les versions, refusez l’auto-update silencieux d’un composant qui exécute du code sur le poste d’un développeur.
Côté observabilité, journalisez pour chaque appel : identifiant d’outil, hash des arguments (pas les arguments en clair si sensibles), identité de l’agent et de l’utilisateur délégant, taskId, latence, statut. Sans corrélation agent/humain, un post-mortem est impossible.
Gouvernance : suivre la spec sans casser la production
Le passage de MCP sous l’Agentic AI Foundation de la Linux Foundation — aux côtés d’AGENTS.md et de goose — change la nature du risque de dépendance : la spec cesse d’être le produit d’un seul éditeur et se dote d’un processus public. Surtout, la révision introduit une politique formelle de dépréciation : une fonctionnalité retirée passe d’abord par un statut deprecated documenté, avec fenêtre de support annoncée. C’est ce qui manquait le plus en 2025, où chaque révision était partiellement un jeu de devinettes.
Pratiquement : épinglez la version de protocole que votre serveur annonce (jamais « la dernière »), suivez les SEP et le changelog en amont plutôt que les release notes de SDK en aval, et gardez un test de conformité dans votre CI contre au moins deux versions de spec.
La bascule stateless n’est pas une nouveauté conceptuelle — c’est le même mouvement que REST a imposé aux RPC stateful il y a vingt ans, et il produit les mêmes bénéfices : élasticité, résilience, cacheabilité. Ce qui est nouveau, c’est que MCP l’accompagne d’un mécanisme d’extension gouverné, ce qui évite au cœur de re-gonfler à chaque besoin exotique. Le coût de migration est réel et se concentre presque entièrement sur un point : sortir l’état du process. Les équipes qui avaient déjà écrit leur serveur comme un service HTTP horizontal en paieront quelques jours ; celles qui ont porté un prototype stdio en production sans le repenser devraient budgéter davantage — et profiter du passage pour traiter l’audience des jetons et la délégation d’identité, qui restent les deux défauts les plus répandus de l’écosystème.
Comments (0)
Please log in to leave a comment.
Log InNo comments yet. Be the first to comment!