New in Tech

MCP 2026-07-28 : la fin des sessions, et ce que la refonte sans état change pour vos serveurs en production

Publiée le 28 juillet 2026, la révision MCP 2026-07-28 supprime les sessions du protocole, retire la poignée de main d'initialisation et réécrit l'autorisation. Pour les équipes qui exploitent des serveurs MCP distants, c'est la fin de l'affinité de session et le début d'une architecture réellement horizontale. Tour d'horizon technique de ce qui disparaît, de ce qui arrive, et de la marche à suivre pour migrer.

| | | 9 min read · 19 Aug 2026 · by
Schéma d'un load balancer distribuant des requêtes MCP vers plusieurs instances de serveur identiques, sans affinité de session

La révision 2026-07-28 du Model Context Protocol est sortie le 28 juillet 2026. Ses mainteneurs la décrivent comme la plus grande refonte depuis le lancement du protocole, et pour une fois la formule n’est pas exagérée : les sessions disparaissent du noyau, la poignée de main d’initialisation est retirée, l’autorisation est réécrite, et un cadre d’extensions versionnées indépendamment fait son apparition.

Le contexte rend l’événement difficile à ignorer. MCP n’est plus un projet de niche : OpenAI, Google, Microsoft et AWS l’ont intégré à leurs piles d’agents, plus de 10 000 serveurs publics tournent en production, et la Linux Foundation a placé le projet sous l’Agentic AI Foundation. Les SDK TypeScript, Python, Go et C# supportent déjà la nouvelle révision ; le SDK Rust est en bêta. Autrement dit : la migration n’est plus une question de calendrier lointain.

Le problème que la refonte résout

Jusqu’ici, un serveur MCP distant en HTTP fonctionnait ainsi : le client ouvrait une session via initialize, le serveur lui renvoyait un identifiant de session (Mcp-Session-Id), et toutes les requêtes suivantes devaient revenir sur le processus qui détenait l’état associé — capacités négociées, abonnements aux ressources, contexte d’autorisation dérivé au moment de l’initialisation.

Sur un poste de développeur, avec un serveur lancé en stdio, personne ne payait ce coût. En production, la facture était salée :

  • affinité de session obligatoire au niveau du load balancer (sticky sessions par cookie ou par hachage d’en-tête), ou magasin d’état partagé type Redis ;
  • autoscaling dégradé : un scale-in tue des sessions actives, un déploiement rolling aussi ;
  • serverless quasiment inutilisable : Lambda, Cloud Run ou Workers ne garantissent pas qu’une seconde requête atterrira sur la même instance ;
  • coût de démarrage : la poignée de main imposait un aller-retour supplémentaire avant le premier appel utile, ce qui pèse quand un agent interroge une douzaine de serveurs.

La session était un objet du protocole, alors que le besoin réel — corréler quelques échanges — est un problème applicatif. La révision 2026-07-28 fait redescendre ce besoin au bon niveau.

Ce qui disparaît

Trois retraits structurants :

  1. Les sessions de protocole. Plus d’identifiant de session émis par le serveur, plus de cycle de vie session-scopé, plus de reprise de flux ancrée sur une session.
  2. La poignée de main d’initialisation. Plus de initialize / notifications/initialized. La version du protocole et les métadonnées nécessaires voyagent avec chaque requête, dans les en-têtes HTTP.
  3. Les fonctionnalités dépréciées de longue date, dont l’ancien transport HTTP+SSE à double endpoint hérité de 2024-11-05, définitivement sorti du texte.

L’implication concrète pour votre code existant est simple à énoncer et parfois lourde à appliquer : tout état que votre serveur conservait en mémoire entre deux requêtes d’un même client doit être externalisé ou recalculé. Cela concerne typiquement :

  • les capacités négociées à l’initialisation (désormais portées par la requête ou déduites de la version) ;
  • le contexte d’authentification dérivé une fois puis réutilisé (désormais revalidé à chaque requête) ;
  • les abonnements aux ressources et les notifications */list_changed, qui supposaient un canal ouvert vers un client identifié ;
  • les curseurs de pagination stockés côté serveur — ils doivent devenir opaques et auto-porteurs.

Ce qui arrive

Requêtes auto-portées et routage par en-têtes

Chaque requête transporte ce qu’il faut pour être traitée seule : version du protocole, jeton d’accès, métadonnées de routage. La conséquence opérationnelle est plus intéressante que le détail syntaxique : un proxy L7 peut router sans parser le corps JSON-RPC.

POST /mcp HTTP/1.1
Host: tools.exemple.fr
MCP-Protocol-Version: 2026-07-28
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json

{"jsonrpc":"2.0","id":42,"method":"tools/call",
 "params":{"name":"search_docs","arguments":{"q":"tls"}}}

Vous pouvez donc faire du canary par version de protocole, diriger un namespace d’outils vers un backend dédié, ou appliquer du rate limiting différencié — le tout en configuration d’ingress, sans code applicatif.

Les noms exacts des en-têtes et des champs figurent dans la spécification ; les extraits de cet article sont illustratifs et destinés à montrer la forme des échanges.

Résultats de liste cachables

tools/list, resources/list et prompts/list deviennent cachables avec des validateurs explicites. Sans session, un client ne peut plus « apprendre » le catalogue une fois pour toutes au démarrage : il le redemanderait à chaque cold start. Le cache résout ce problème, et remplace en grande partie les notifications list_changed, qui supposaient un canal persistant.

GET /mcp/tools HTTP/1.1
If-None-Match: "cat-9f2b1c"

HTTP/1.1 304 Not Modified
ETag: "cat-9f2b1c"
Cache-Control: max-age=300

Côté implémentation : dérivez votre validateur d’un hachage stable du catalogue sérialisé, pas d’un timestamp de build. Deux réplicas d’une même version doivent produire le même ETag, sinon un client alternant entre pods invalidera son cache en permanence.

Requêtes multi-allers-retours

C’est la pièce qui rend le tout viable. Certaines opérations exigent une interaction en cours de traitement : demander un sampling au modèle du client, ou une elicitation auprès de l’utilisateur. Auparavant, la session servait de canal de retour.

Désormais, l’échange est modélisé explicitement : le serveur ne bloque pas, il renvoie un résultat signalant qu’une interaction est requise, accompagné d’un jeton de continuation. Le client satisfait la demande et rejoue l’appel en présentant ce jeton. La continuation est auto-porteuse ou référence un état persisté et adressable par n’importe quel réplica — d’où la compatibilité avec un autoscaler.

Le cadre d’extensions

Le noyau devient volontairement petit et stable. Les fonctionnalités à cycle de vie plus rapide passent dans des extensions versionnées indépendamment, ce qui évite d’attendre la prochaine révision majeure pour faire évoluer une capacité.

Deux extensions arrivent avec cette révision :

  • Tasks, pour les traitements longs. Une tâche est une ressource côté serveur, identifiée et persistée, que le client interroge ou reprend. C’est le remplaçant propre des bricolages à base de notifications de progression sur connexion longue — et le modèle s’accorde naturellement avec un déploiement sans état.
  • MCP Apps, pour les interfaces rendues côté serveur : le serveur déclare une UI que l’hôte affiche, au lieu de laisser chaque client réinventer une présentation à partir de texte brut.

Pour une équipe, la conséquence est un couplage plus faible : vous adoptez Tasks sans avaler l’intégralité d’une nouvelle révision de noyau.

Autorisation durcie

MCP s’appuyait déjà sur OAuth 2.1, PKCE, la découverte via Protected Resource Metadata (RFC 9728) et les indicateurs de ressource (RFC 8707). Le durcissement porte moins sur de nouveaux mécanismes que sur des obligations rendues non négociables :

  • validation de l’audience à chaque requête. Sans initialisation, il n’existe plus de moment privilégié où « ancrer » une identité : chaque appel doit être autorisé pour lui-même ;
  • interdiction explicite du token passthrough — relayer tel quel le jeton reçu vers une API tierce reste le chemin le plus court vers un confused deputy ;
  • portées plus granulaires, permettant de raisonner par outil ou par capacité plutôt que par serveur entier.

Les audits menés sur les serveurs publics ont fait ressortir ces schémas de manière récurrente, ce qui explique l’urgence du chantier. Point de vigilance en production : revalider un jeton à chaque requête coûte cher. Mettez en cache le résultat d’introspection, avec une clé dérivée du hachage du jeton et un TTL court — cache local au processus, pas d’état partagé requis.

Guide de migration pratique

État des SDK : TypeScript, Python, Go et C# supportent la révision ; Rust est en bêta. Si vous êtes sur Rust, prévoyez un gel fonctionnel plutôt qu’une migration immédiate.

Négociation de version. Elle se fait par en-tête. Un serveur qui ne connaît pas la version demandée doit répondre par une erreur explicite plutôt que par un comportement dégradé silencieux.

Cohabitation client ancien / serveur nouveau. Le cas majoritaire pendant plusieurs mois. Deux stratégies :

  1. Endpoint dédié par révision (/mcp/2025-06-18, /mcp/2026-07-28), routé par l’ingress. Lisible, testable, et vous supprimez l’ancien chemin le jour venu.
  2. Shim de compatibilité : le serveur accepte encore initialize, renvoie un identifiant de session… qu’il n’utilise pas, tout l’état étant déjà externalisé. Moins d’endpoints, mais un code de compatibilité qui a tendance à survivre trop longtemps.

Pièges de rétrocompatibilité :

  • les clients qui épinglent une version de protocole en dur ne basculeront pas seuls ;
  • les notifications list_changed n’ont plus de destinataire : basculez sur des TTL courts et des ETags stables ;
  • les curseurs de pagination doivent devenir opaques et auto-porteurs, sinon la page 2 échouera sur un autre réplica ;
  • les serveurs stdio locaux sont peu affectés : le gros de l’effort concerne le transport HTTP.

Une politique de dépréciation formelle

La révision s’accompagne d’un cycle de vie explicite : une fonctionnalité est marquée dépréciée, reste en place pendant une durée annoncée, puis est retirée — les extensions suivant leur propre cadence. Pour ceux qui construisent aujourd’hui, c’est le vrai apport de gouvernance : un horizon de planification, au lieu de retraits découverts dans un changelog.

Avant / après : un serveur derrière un autoscaler

Avant, la session imposait sa loi jusque dans le manifeste :

# AVANT — affinité obligatoire, scale-in destructeur
apiVersion: v1
kind: Service
metadata:
  name: mcp-server
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "mcp-affinity"
spec:
  sessionAffinity: ClientIP
---
# + Redis pour l'état de session
# + terminationGracePeriodSeconds élevé pour drainer les sessions
# + minReplicas surdimensionné : un scale-in coûte des sessions vivantes

Après, il ne reste qu’un service HTTP ordinaire :

# APRÈS — round-robin, scale-in sans conséquence
apiVersion: v1
kind: Service
metadata:
  name: mcp-server
spec:
  sessionAffinity: None
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  minReplicas: 2
  maxReplicas: 50
  metrics:
    - type: Pods
      pods:
        metric: {name: http_requests_per_second}
        target: {type: AverageValue, averageValue: "60"}

Et côté code, la transformation tient dans une inversion : le handler ne lit plus un contexte de session, il le reconstruit à partir de la requête.

// AVANT : état porté par la session
const session = sessions.get(req.headers["mcp-session-id"]);
if (!session) throw new Error("session inconnue");
return handleCall(session.auth, session.capabilities, params);

// APRÈS : tout vient de la requête
const auth = await verifyToken(req.headers.authorization, {
  audience: RESOURCE_URI,   // validation d'audience, à chaque appel
});
return handleCall(auth, params);

Le déploiement en serverless devient possible sans contorsion, le rolling update ne casse plus rien, et l’infrastructure d’affinité — cookies, Redis, drains longs — peut être supprimée.


La refonte 2026-07-28 ne rend pas MCP plus riche, elle le rend plus ordinaire : un serveur MCP redevient un service HTTP comme les autres, avec les propriétés opérationnelles qui vont avec. Le coût se déplace plutôt qu’il ne disparaît — vers la validation de jetons à chaque appel et vers la discipline de cache sur les catalogues d’outils, deux problèmes bien connus et bien outillés. La bonne façon d’aborder la migration n’est donc pas de traduire mécaniquement l’ancien code, mais de dresser l’inventaire de tout l’état conservé entre deux requêtes, et de décider pour chaque élément s’il doit être persisté, recalculé, ou simplement supprimé. Dans la plupart des serveurs existants, la troisième réponse est plus fréquente qu’on ne l’imagine.

Comments (0)

Please log in to leave a comment.

Log In

No comments yet. Be the first to comment!