La révision 2026-07-28 de la spécification Model Context Protocol est la plus structurante depuis l'arrivée de Streamable HTTP. Elle supprime les sessions, remplace le handshake initialize, et engage la dépréciation de Dynamic Client Registration au profit des Client ID Metadata Documents (CIMD). Ayant migré des serveurs en production à travers cette révision, je détaille ici ce qui change, ce qui ne change pas, et les pièges que j'ai rencontrés.
Un avertissement d'abord : la majorité des tutoriels MCP que renvoie un moteur de recherche décrivent un handshake et un header de session qui n'existent plus. Lisez le changelog officiel avant de faire confiance à un article daté d'avant l'été 2026.
Ce qui disparaît : Mcp-Session-Id, initialize et le flux GET
Sur le transport HTTP, la révision retire :
- le header
Mcp-Session-Id; - le handshake
initialize; - la reprise de flux SSE et l'endpoint
GETdu transport.
La version du protocole et les capacités du client voyagent désormais dans _meta à chaque requête. Le serveur peut annoncer ses capacités en amont via un nouvel appel server/discover. Les headers Mcp-Method et Mcp-Name deviennent obligatoires sur les POST : ils exposent la méthode appelée aux intermédiaires (proxy, API gateway) sans qu'ils aient à décoder le corps JSON-RPC. Sur ce que JSON-RPC change pour vos APIs et vos gateways, voir Pourquoi l'IA agentique a ressuscité JSON-RPC et ce que ça change pour vos APIs.
Conséquence pratique : n'importe quelle requête peut être routée vers n'importe quelle instance derrière un load balancer round-robin, sans rien de partagé entre elles. Un serveur qui a réellement besoin de continuité entre deux appels émet un handle explicite et le reçoit en argument de tool. L'état passe sous votre contrôle, dans votre schéma, au lieu de vivre dans un header de transport.
Migrer un serveur MCP vers la révision 2026-07-28 : qui est concerné ?
- Un serveur stateful a un vrai chantier : les sessions sur lesquelles il s'appuyait n'existent plus.
- Un serveur déjà stateless (la forme habituelle pour un serveur déployé sur Cloud Run ou un runtime autoscalé) voit son architecture validée. Une montée de version de bibliothèque suffit.
Les clients négocient la version à chaque requête : un ancien serveur continue de répondre aux nouveaux clients pendant la transition. Un serveur qui ne répond pas à server/discover est antérieur à la révision, ce qui explique souvent à lui seul le comportement étrange d'un client récent.
Ce qui ne change pas : votre état OAuth reste stateful
C'est la confusion que je vois le plus. La spécification a cessé de gérer les sessions de transport. Elle n'a jamais géré vos enregistrements de clients OAuth ni les tokens amont chiffrés que votre proxy conserve.
Si votre bibliothèque stocke ces enregistrements sur le disque local par défaut, un déploiement autoscalé produit des échecs d'authentification intermittents, différents selon l'instance qui reçoit la requête. "Stateless" ne veut pas dire "sans store partagé" : vos clients OAuth ont toujours besoin d'un stockage externe à l'instance (Firestore, Redis, base SQL).
Dynamic Client Registration déprécié : passer aux Client ID Metadata Documents (CIMD)
Jusqu'ici, un client MCP qui découvrait un serveur s'enregistrait dynamiquement (DCR, RFC 7591) et recevait un client_id opaque. Avec CIMD, le client_id est une URL HTTPS qui pointe vers un document JSON décrivant le client :
{
"client_id": "https://app.example.com/oauth/client.json",
"client_name": "Mon client MCP",
"redirect_uris": ["http://127.0.0.1/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none"
}Le serveur d'autorisation récupère ce document au moment de l'autorisation, au lieu de maintenir un registre de clients. DCR reste disponible pour les serveurs d'autorisation qui ne supportent pas CIMD, et la fenêtre de dépréciation est de douze mois : rien ne casse ce trimestre.
Côté serveur d'autorisation
Récupérer un document à une URL fournie par le client n'est pas anodin. Le draft IETF impose notamment :
- le
client_iddu document doit être strictement égal à l'URL d'où il provient ; - le
redirect_uride la requête doit correspondre exactement à l'une des URI du document ; - ne pas suivre de redirection lors de la récupération ;
- refuser les adresses privées ou spéciales (SSRF) ;
- limiter la taille du document (de l'ordre de 5 Ko) et ne jamais mettre une erreur en cache ;
- annoncer le support via
client_id_metadata_document_supporteddans les métadonnées.
Côté client
Le document est public par construction. Il ne doit contenir aucun secret partagé ni clé privée, et la méthode d'authentification déclarée doit être cohérente avec ce que le client sait réellement faire. Pour vérifier le document de mon propre client, j'utilise mcp-posture, un scanner open source que j'ai développé :
uvx mcp-posture cimd lint https://app.example.com/oauth/client.jsonTrois pièges rencontrés pendant la migration
Le client_id n'est plus un token opaque
Avec CIMD, le client_id est une URL, avec des slashs et un schéma. Un code qui l'utilise comme clé de stockage ou segment de chemin échoue. Je l'ai vécu : le store clé-valeur refusait le caractère /, et l'erreur remontait en 500 plutôt qu'en erreur OAuth, car le chemin d'erreur relisait le même client. Ce qui ressemblait à un cas marginal quand deux clients le faisaient devient le cas nominal. Assainissez vos clés.
invalid_client_metadata : application_type désormais requis à l'enregistrement
La révision impose aux clients qui utilisent encore DCR d'envoyer application_type. Un serveur qui valide strictement les métadonnées d'enregistrement le rejette avec un invalid_client_metadata qui ne nomme pas le champ fautif. Acceptez-le explicitement.
Le cache change de place, pas de nature
Avec DCR, un client mettait en cache un client_id obtenu avant votre correctif, et vous pensiez que le correctif ne marchait pas. Avec CIMD, il n'y a plus d'enregistrement à mettre en cache, mais le serveur d'autorisation met en cache le document récupéré, selon ses propres règles. La question à se poser reste la même ; seul l'objet à invalider change.
Un cycle de vie formel pour les fonctionnalités
Dernier changement, plus discret mais utile : la spécification a désormais un cycle de vie formel. Chaque fonctionnalité est Active, Deprecated ou Removed, avec au moins douze mois entre les deux derniers états et un registre public des fonctionnalités dépréciées. Roots, Sampling et Logging sont entrés dans cette fenêtre avec cette révision, tout comme le transport HTTP+SSE historique.
Checklist de migration
- Le serveur répond à
server/discoveret accepte la version annoncée dans_meta(sinon :UnsupportedProtocolVersionError). - Un
405 Method Not AllowedsurGET /mcpest normal : l'endpoint GET n'existe plus. - Aucune logique ne dépend de
Mcp-Session-Id; l'état nécessaire passe par des handles explicites. - Les headers
Mcp-*ne sont pas supprimés par un proxy ou un load balancer. - Les enregistrements OAuth sont dans un store partagé, pas sur le disque de l'instance.
- Les clés de stockage acceptent un
client_iden forme d'URL. - L'enregistrement accepte
application_type. - Aucune nouvelle fonctionnalité ne repose sur Roots, Sampling ou Logging.
Conclusion
Cette révision ne fait que formaliser ce que les serveurs déployés en serverless faisaient déjà : pas d'état de transport. Le vrai travail est ailleurs, dans l'état OAuth que vous gérez vous-même et dans le passage à des client_id en forme d'URL. Si vos serveurs étaient stateless, la migration tient en une après-midi. Sinon, c'est le bon moment pour sortir cet état du transport et le mettre dans votre schéma.



