🎯 OBJECTIF
Comprendre comment :
🧠 MODÈLE MENTAL
Jusqu'à la révision 2025-11-25, une session MCP ressemblait à une connexion. Le client ouvrait avec initialize, le serveur répondait avec ses capacités et un Mcp-Session-Id, et tout ce qui suivait dépendait de cet identifiant. Conséquence pratique : le client était épinglé à une instance précise. Sticky sessions, storage partagé entre pods, flux SSE maintenus ouverts, reconnexions à gérer. Une part importante des bugs en production venait de là.
La 2026-07-28 fait le pari inverse : chaque requête se décrit elle-même. Version du protocole, identité du client, capacités déclarées, tout voyage dans la requête. Il n'y a plus rien à retenir entre deux appels, donc n'importe quelle requête peut atterrir sur n'importe quelle instance derrière un simple round-robin. Un serveur MCP redevient ce qu'il aurait sans doute toujours dû être : un endpoint HTTP ordinaire, cacheable et routable.
Le prix à payer est réel. Le changement est incompatible dans les deux sens, et tout ce qui reposait sur un canal serveur vers client (elicitation, sampling) a dû être repensé.
2025-03-26, 2025-11-25, 2026-07-28).server/discover — RPC optionnel pour connaître les capacités d'un serveur avant tout appel.L'échange initialize / initialized et le header Mcp-Session-Id sont retirés (SEP-2575, SEP-2567). Chaque requête porte sa version de protocole et son identité de client dans _meta.
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search","arguments":{"q":"otters"},
"_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}}
Un client qui veut connaître les capacités d'un serveur avant d'agir appelle server/discover. Ce n'est plus obligatoire, c'est une option.
flowchart LR
subgraph Avant ["2025-11-25 : session collante"]
C1["Client"] -->|"initialize → Mcp-Session-Id"| LB1["Load balancer<br/>(sticky)"]
LB1 --> P1["Pod A"]
LB1 -.->|"inaccessible sans<br/>état partagé"| P2["Pod B"]
P1 <--> ST[("Store de session")]
P2 -.-> ST
end
subgraph Apres ["2026-07-28 : requêtes autonomes"]
C2["Client"] -->|"chaque requête<br/>auto-descriptive"| LB2["Load balancer<br/>(round-robin)"]
LB2 --> P3["Pod A"]
LB2 --> P4["Pod B"]
LB2 --> P5["Pod C"]
endmermaid🔑 Conclusion clé
Supprimer la session du protocole ne rend pas l'application sans état. Si le serveur doit porter de l'état entre deux appels, il émet un handle explicite depuis un tool et le modèle le repasse en argument. L'état devient visible par le modèle au lieu d'être caché dans le transport, ce qui le rend traçable et débuggable.
🚨 L'incompatibilité va dans les deux sens
Un serveur 2026-07-28 ne comprend pas un initialize d'un client 2025, et un client 2026 ne sait pas dialoguer avec un serveur qui exige une session. Ce n'est pas un simple bump de dépendance : les serveurs qui s'appuyaient sur l'identifiant de session pour retrouver du contexte demandent un vrai refactor. Pendant la période de transition, les gateways servent de couche d'interopérabilité en annonçant plusieurs versions de protocole à la fois.
Un tool a parfois besoin d'une confirmation ou d'un paramètre manquant en plein appel. Avant, cela passait par des requêtes initiées par le serveur (elicitation/create, sampling/createMessage, roots/list) qui exigeaient un flux maintenu ouvert. Incompatible avec un cœur stateless.
MRTR (SEP-2322) inverse le sens : le serveur ne pousse rien, il refuse poliment de répondre tout de suite. Il retourne resultType: "input_required" avec la liste de ce dont il a besoin, et le client rejoue l'appel d'origine en attachant les réponses dans inputResponses.
sequenceDiagram
actor User
participant Client
participant Server
Client->>Server: tools/call { name: "delete_project" }
Server-->>Client: resultType: "input_required"<br/>inputRequests: [confirmation]
Client->>User: "Confirmer la suppression ?"
User-->>Client: oui
Client->>Server: tools/call (même appel)<br/>+ inputResponses: [oui]
Server-->>Client: resultType: "success"<br/>content: [...]mermaidLe serveur n'a rien mémorisé entre les deux tours. C'est le client qui rejoue le contexte complet.
Bonne pratique — l'usage qui débloque le plus
C'est le mécanisme de confirmation avant action destructrice ou coûteuse. Un tool qui supprime des données, engage une dépense ou déclenche un traitement long peut désormais demander un accord explicite sans imposer une architecture à état.
Les requêtes Streamable HTTP doivent maintenant inclure Mcp-Method et Mcp-Name (SEP-2243). Le nom de la méthode et celui du tool voyagent en clair dans les en-têtes HTTP.
Le bénéfice est opérationnel : un gateway, un rate limiter ou un WAF route, autorise et compte sans jamais parser le corps JSON. On peut appliquer un quota par tool, refuser un tool précis au niveau du reverse proxy, ou tracer proprement l'appel dans une chaîne distribuée.
🚨 Duplication en-tête et corps
Ces informations existent à deux endroits : dans les en-têtes HTTP et dans le corps JSON-RPC. Elles doivent être cohérentes. Un serveur qui ne fait confiance qu'à l'en-tête sans vérifier le corps ouvre une divergence exploitable : un appel routé et autorisé comme search mais exécuté comme autre chose.
Les réponses de tools/list, prompts/list, resources/list et resources/read portent désormais ttlMs et cacheScope (SEP-2549), avec un ordre déterministe.
Deux gains distincts, et le second est le moins évident :
L'autorisation est le poste où les implémenteurs passaient le plus de temps. Quatre changements :
| Changement | Effet concret |
|---|---|
Validation de iss selon la RFC 9207 (SEP-2468) |
Le client doit vérifier l'émetteur avant d'échanger le code, ce qui ferme une faille de confusion d'authorization server |
application_type lors du DCR (SEP-837) |
Les authorization servers cessent de rejeter les redirects localhost, ce qui débloque les clients desktop et CLI |
| Credentials liés à leur émetteur (SEP-2352) | Plus de réutilisation d'un même credential sur plusieurs authorization servers |
| DCR déprécié au profit de CIMD | Le client héberge son propre document de métadonnées au lieu de s'enregistrer dynamiquement |
🔑 Conclusion clé
Si un flux OAuth de client CLI échouait avec une erreur redirect_uri sans raison apparente, application_type est très probablement la cause. C'est le correctif le plus immédiatement utile de cette révision.
Le protocole se dote d'un cadre formel pour ajouter des capacités sans toucher au cœur. Les extensions sont versionnées et nommées par espace de noms.
io.modelcontextprotocol/tasks) sort du cœur expérimental et devient une extension, avec tasks/get en polling et un nouveau tasks/update (SEP-2663). C'est le support des traitements longs.Les notifications de changement quittent l'ancien endpoint HTTP GET pour un flux unique subscriptions/listen, auquel le client s'abonne par type de notification.
Sont dépréciés, avec un maintien garanti d'au moins douze mois : Roots, Sampling, Logging, et le transport HTTP+SSE legacy. Ils continuent de fonctionner. Les nouvelles implémentations ne doivent pas les adopter.
Le point structurant n'est pas la liste, c'est la politique : une fenêtre minimale de douze mois avant retrait, plus une exigence de suite de conformité. On planifie les montées de version au lieu de les subir.
| Critère | 2025-11-25 | 2026-07-28 |
|---|---|---|
| Établissement | initialize / initialized |
Aucun, requêtes autonomes |
| Identité de session | Header Mcp-Session-Id |
Aucune, _meta par requête |
| Découverte des capacités | Imposée au handshake | server/discover, optionnel |
| Interaction utilisateur | Requêtes serveur vers client sur flux ouvert | MRTR (input_required + rejeu) |
| Routing | Parsing du corps JSON | En-têtes Mcp-Method / Mcp-Name |
| Cache des listes | Non spécifié | ttlMs + cacheScope, ordre déterministe |
| Déploiement | Sticky sessions ou état partagé | Round-robin, serverless, edge |
| Enregistrement client OAuth | DCR | CIMD (DCR déprécié) |
| ✅ Avantages | ❌ Inconvénients |
|---|---|
| Scalabilité horizontale sans état partagé | Rupture de compatibilité dans les deux sens |
| Déploiement serverless et edge possible | Refactor réel pour tout serveur qui exploitait la session |
| Routing et quotas au niveau infra | Charge utile plus lourde (capacités répétées à chaque requête) |
| Traçabilité distribuée de bout en bout | Écosystème hétérogène pendant la fenêtre de transition |
| Cache de prompt amont stabilisé | Interaction utilisateur plus verbeuse (deux allers-retours) |
Quand migrer maintenant : serveur distant multi-instances, contrainte de scalabilité ou de coût d'infra, besoin de déployer en serverless, besoin de confirmation utilisateur sur un serveur déjà sans état.
Quand attendre : serveur stdio local mono-utilisateur, ou dépendance à un SDK hors Tier 1 dont le support n'est pas confirmé.
2026-07-28.Mcp-Session-Id et remplacer l'état de session par des handles explicites retournés par les tools.Mcp-Method et Mcp-Name, et vérifier leur cohérence avec le corps côté serveur.ttlMs et cacheScope sur les réponses */list, avec un ordre stable.iss, application_type, trajectoire vers CIMD.Le socle conceptuel (topologie Host / Client / Server, primitives Tools / Resources / Prompts, JSON Schema comme contrat lu par le modèle) reste inchangé : voir mcp-model-context-protocol. Cette révision touche le transport, l'autorisation et le cycle de vie, pas le modèle de programmation.
⚡ TL;DR — chaque concept en une ligne
Cœur stateless ✓ Chaque requête porte sa version, son identité et ses capacités, donc elle est traitable par n'importe quelle instance. ⚠ Ne rend pas l'application sans état : il faut des handles explicites si le métier a besoin de continuité.
MRTR
✓ Le serveur répond input_required, le client rejoue l'appel avec les réponses attachées.
⚠ Le contexte n'est pas mémorisé côté serveur : c'est le client qui doit rejouer l'appel complet.
Mcp-Method / Mcp-Name
✓ Routing, quotas et autorisation au niveau infra sans ouvrir le corps JSON.
⚠ Information dupliquée en-tête et corps : divergence à contrôler côté serveur.
ttlMs / cacheScope
✓ Cache des catalogues côté client et ordre déterministe des listes.
⚠ Un ordre instable casse le cache de prompt en amont, pas seulement le cache de liste.
Extensions ✓ Cadre versionné et nommé pour Tasks, MCP Apps et EMA sans toucher au cœur. ⚠ Une extension n'est pas garantie présente : elle se négocie, elle ne se suppose pas.
Politique de dépréciation ✓ Douze mois minimum entre dépréciation et retrait, plus une suite de conformité. ⚠ Roots, Sampling, Logging et HTTP+SSE sont déjà dans la fenêtre : ne rien construire dessus.
🎓 À retenir
application_type est un correctif rétroactif — il est ajouté à DCR alors même que DCR est déprécié, parce que la trajectoire vers CIMD prendra des mois et que les clients CLI cassaient dès aujourd'hui.