Deux semaines après sa publication, la révision 2026-07-28 du Model Context Protocol occupe déjà les canaux de discussion des équipes qui exploitent des serveurs MCP en production. Et pour une bonne raison : ce n’est pas une révision incrémentale de plus, c’est une réécriture des hypothèses de transport. Le cœur du protocole devient stateless, le handshake initialize/initialized disparaît, l’en-tête Mcp-Session-Id avec lui. Si votre serveur MCP tourne derrière un load balancer, dans un pod Kubernetes qui redémarre, ou dans une fonction serverless, cette révision vous concerne directement.
Rappel express : pourquoi le transport bloquait le passage à l’échelle
Depuis ses premières versions, MCP reposait sur un modèle de connexion logique : le client ouvrait une session, négociait la version du protocole et les capabilities via initialize, confirmait avec initialized, puis émettait ses tools/call, resources/read et autres requêtes dans le contexte de cette session. Le transport HTTP « streamable » avait apporté une amélioration nette par rapport au SSE historique, mais il conservait l’idée centrale : un identifiant de session porté par l’en-tête Mcp-Session-Id, et un état côté serveur associé à cet identifiant.
En développement local, ce modèle est confortable. En production, il produit exactement les symptômes que l’on connaît depuis vingt ans avec les sessions applicatives :
- affinité de session obligatoire sur le load balancer (sticky sessions), donc répartition de charge inégale ;
- redémarrages destructeurs : un rolling update invalide toutes les sessions en cours, le client doit re-négocier ;
- incompatibilité de fait avec le serverless, où l’instance qui répond à la requête n n’est pas celle qui a répondu à la requête n-1 ;
- impossibilité de mettre en cache les réponses de découverte (
tools/list,resources/list,prompts/list), alors que ce sont les requêtes les plus répétitives du protocole ; - opacité pour l’infrastructure : tout le sens utile est dans le corps JSON-RPC, donc invisible pour un reverse proxy, une WAF ou une couche de routage.
La rupture : chaque requête devient autonome
La nouvelle spécification tranche : plus de handshake, plus d’identifiant de session. Chaque requête HTTP transporte tout ce qui est nécessaire à son traitement — version du protocole, contexte d’autorisation, et métadonnées de routage — dans ses en-têtes.
Concrètement, l’échange passe de ceci :
POST /mcp HTTP/1.1
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{}, "clientInfo":{...}}}
HTTP/1.1 200 OK
Mcp-Session-Id: 9f3c1b0e-...
...
à cela :
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Authorization: Bearer eyJhbGciOi...
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"create_invoice","arguments":{...}}}
La négociation de version se fait par en-tête, avec un comportement défini en cas d’absence ou d’incompatibilité. Les capabilities ne sont plus échangées une fois pour toutes : elles sont découvertes à la demande, ce qui rend les réponses de découverte stables et donc cachables — avec ETag et requêtes conditionnelles côté client plutôt qu’une notification listChanged poussée sur un flux persistant.
Le second effet, plus discret mais très apprécié des équipes plateforme : les informations nécessaires au routage remontent dans les en-têtes. Un proxy peut orienter une requête, appliquer un quota ou refuser un appel sans parser ni bufferiser le corps JSON-RPC.
Conséquences opérationnelles immédiates
Il faut mesurer ce que cela débloque, parce que c’est considérable :
- Fin de l’affinité de session. Vous pouvez supprimer la configuration sticky de votre load balancer, ou plus exactement : vous devez la supprimer, sinon vous conservez artificiellement le déséquilibre de charge qu’elle induit.
- Scaling horizontal réel. N’importe quelle instance peut traiter n’importe quelle requête. L’autoscaling redevient piloté par le débit de requêtes, et non par le nombre de connexions ouvertes.
- Runtimes serverless. Cloud Run, Lambda, Workers : un serveur MCP conforme à 2026-07-28 devient un handler HTTP ordinaire. Les cold starts ne cassent plus rien de sémantique.
- Déploiements sans casse. Un rolling update ne détruit plus d’état côté serveur ; au pire une requête en vol est rejouée.
- Observabilité standard. On corrèle par
traceparentW3C et identifiant de requête, plus par identifiant de session propriétaire.
Les nouveautés qui compensent la perte d’état
Supprimer la session soulève une question légitime : comment gérer ce qui, par nature, demandait un canal bidirectionnel persistant ? La spécification répond par trois mécanismes.
Multi Round-Trip Requests. Certains appels d’outils doivent interrompre leur exécution pour obtenir quelque chose du client : une confirmation avant une action destructrice, un paramètre manquant, un choix. Auparavant, le serveur envoyait une requête au client sur le flux de la session. Désormais, le serveur retourne une réponse intermédiaire décrivant ce qu’il attend, accompagnée d’un contexte de continuation ; le client répond en réémettant une requête HTTP autonome portant ce contexte. Le pattern est celui d’une machine à états dont l’état voyage avec les messages plutôt que de résider dans la mémoire du serveur.
Conséquence de design : ce contexte de continuation doit être signé et à durée de vie limitée. Le traiter comme une donnée cliente de confiance, c’est ouvrir une élévation de privilèges (« confirmer » une suppression qu’on n’a jamais initiée).
Tasks. Pour les traitements longs, on ne s’appuie plus sur un flux de notifications de progression attaché à une connexion. Le serveur crée une tâche, renvoie son identifiant, et le client interroge son état. C’est moins élégant qu’un push, mais c’est ce qui rend le modèle compatible avec un timeout de gateway à 30 secondes et un pod qui peut disparaître.
MCP Apps. Pour les besoins réellement interactifs — une interface que le serveur veut faire afficher par l’hôte — la spécification introduit un cadre dédié, plutôt que de détourner le canal de session à cet usage.
Cadre d’extensions et dépréciation formelle
Point moins spectaculaire mais structurant pour la maintenance : la révision formalise un mécanisme d’extensions et une politique de dépréciation explicite. Les fonctionnalités non essentielles sortent du cœur pour devenir des extensions nommées et négociées, et une fonctionnalité dépréciée suit un cycle annoncé avant retrait.
Pour vos serveurs, cela signifie deux choses. D’abord, le cœur du protocole devient une cible plus petite et plus stable — c’est une bonne nouvelle pour la dette technique. Ensuite, vous devez désormais déclarer explicitement les extensions que vous implémentez et ne plus supposer qu’un client les comprend parce qu’elles étaient historiquement dans le socle.
Autorisation : durcissement et sortie du DCR
Le volet sécurité est celui qui demandera le plus de coordination avec vos équipes IAM.
Premier point : la validation du paramètre iss devient une exigence. Un serveur MCP doit vérifier que l’émetteur du token correspond à celui qu’il attend, en complément de la vérification d’audience (resource, RFC 8707) déjà présente. Concrètement, cela ferme des scénarios de confusion d’émetteur où un token valide pour un autre tenant ou un autre AS était accepté.
Second point, plus lourd : le Dynamic Client Registration (RFC 7591) est officiellement déprécié au profit des Client ID Metadata Documents (CIMD). Au lieu d’enregistrer dynamiquement un client auprès de chaque serveur d’autorisation — avec les problèmes de prolifération d’enregistrements, de nettoyage et d’abus que l’on connaît —, le client s’identifie par une URL HTTPS qui sert de client_id et qui héberge son document de métadonnées. Le serveur d’autorisation résout cette URL et découvre les redirect URIs et autres attributs.
Calendrier réaliste : « déprécié » n’est pas « supprimé ». Prévoyez une phase de double support — accepter CIMD, continuer à tolérer les clients DCR existants — sur toute la durée du cycle de dépréciation, et instrumentez la répartition entre les deux voies pour savoir quand couper.
Migration d’un serveur existant, pas à pas
1. Inventoriez l’état par session
C’est l’étape la plus longue, et elle est purement analytique. Cherchez tout ce que votre serveur retient entre deux requêtes :
# Points de départ typiques dans un projet TypeScript
rg -n "sessionId|session_id|Mcp-Session-Id" src/
rg -n "new Map\(|Map<string,|sessions\[" src/
rg -n "onInitialize|initialized" src/
Les coupables habituels : une Map de sessions, des curseurs de pagination stockés en mémoire, des abonnements à des ressources, un cache local sur le système de fichiers de l’instance, des compteurs de rate limiting par session, et des logs corrélés par identifiant de session.
2. Classez chaque élément d’état
Trois destinations possibles :
- À supprimer : tout ce qui n’existait que pour le handshake (capabilities négociées,
clientInfomémorisé). - À externaliser : ce qui doit survivre entre requêtes et être partagé entre instances (tâches longues, verrous, quotas) → Redis, base de données, ou un store managé.
- À faire voyager avec la requête : ce qui est propre à un enchaînement d’appels (curseur de pagination, contexte de continuation d’un Multi Round-Trip) → token opaque, signé, expirant.
3. Rendez le handler idempotent et sans phase d’init
// Avant : état de session implicite
server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
const session = sessions.get(extra.sessionId); // ← à supprimer
if (!session?.initialized) throw new Error("not initialized");
return runTool(req.params, session.ctx);
});
// Après : tout le contexte est dérivé de la requête
app.post("/mcp", async (req, res) => {
const version = req.header("MCP-Protocol-Version");
const auth = await verifyAccessToken(req.header("Authorization"), {
expectedIssuer: ISSUER_URL, // validation de iss
expectedResource: CANONICAL_MCP_URL, // RFC 8707
});
const ctx = buildContext({ version, auth, traceparent: req.header("traceparent") });
res.json(await handleRpc(req.body, ctx));
});
4. Rendez les listes cachables — sans fuite
C’est le piège le plus dangereux de la migration. Les résultats de tools/list dépendent généralement des droits de l’appelant. Une réponse cachable qui ne varie pas selon l’autorisation est une fuite de données.
HTTP/1.1 200 OK
ETag: "tools-v42-scope-a1b2c3"
Cache-Control: private, max-age=300
Vary: Authorization, MCP-Protocol-Version
Deux règles : private par défaut (jamais de cache partagé sur une réponse dérivée d’un token), et un ETag qui intègre à la fois la version du catalogue et l’empreinte des scopes effectifs.
5. Nettoyez l’infrastructure
upstream mcp_backend {
- hash $http_mcp_session_id consistent; # affinité devenue inutile
server mcp-1:8080;
server mcp-2:8080;
}
location /mcp {
proxy_pass http://mcp_backend;
+ proxy_pass_request_headers on; # ne pas filtrer MCP-Protocol-Version
proxy_buffering off;
}
Vérifiez aussi vos WAF et API gateways : beaucoup suppriment silencieusement les en-têtes non standard.
Ce qu’il faut tester avant de livrer
- Autonomie de la requête : envoyez un
tools/callsur une instance fraîchement démarrée, sans aucun appel préalable. Il doit réussir. - Absence d’affinité : forcez un round-robin strict et rejouez un scénario complet, y compris un Multi Round-Trip, en alternant délibérément d’instance à chaque étape.
- Idempotence : rejouez deux fois la même requête d’écriture avec la même clé d’idempotence et vérifiez qu’un seul effet est produit.
- Cachabilité : deux utilisateurs aux droits différents ne doivent jamais recevoir la même entrée de cache. Testez-le avec un cache partagé réel devant le serveur, pas seulement en unitaire.
- Continuation forgée : tentez de réémettre un contexte de continuation modifié, expiré, ou émis pour un autre utilisateur. Les trois doivent être rejetés.
- Autorisation : token au bon
audmais mauvaisiss, et inversement. Les deux doivent échouer. - Traversée de proxy : mesurez ce qui arrive réellement au serveur derrière votre chaîne complète (CDN, WAF, gateway, ingress) — en-têtes conservés, buffering, timeouts.
- Chaos de déploiement : tuez un pod en pleine séquence d’appels. Le client doit poursuivre sans re-négociation.
La révision 2026-07-28 est une correction de trajectoire honnête : MCP avait été conçu avec un modèle de session hérité du transport stdio local, et ce modèle ne survivait pas au contact d’une infrastructure HTTP réelle. Le prix à payer est une migration non triviale, où la difficulté n’est pas de supprimer trois lignes de handshake mais de retrouver — puis de reclasser — tout l’état implicite accumulé dans vos handlers. Les SDK rattrapent encore la spécification, et vos clients ne migreront pas tous en même temps : la stratégie raisonnable pour les prochains mois est un serveur stateless par construction, capable de servir l’ancien modèle en mode compatibilité derrière un flag, avec des métriques qui vous diront précisément quand l’éteindre.
Comments (0)
Please log in to leave a comment.
Log InNo comments yet. Be the first to comment!