Modifications de la spécification MCP : les nouveautés et ce qui casse
La révision 2026-07-28 du Model Context Protocol rend MCP sans état : plus de poignée de main initialize, plus de Mcp-Session-Id, de nouveaux en-têtes de routage, des listes pouvant être mises en cache et un mécanisme de nouvelle tentative pour l’elicitation. Cet article recense chaque changement, ce qui casse et l’ordre dans lequel migrer.
Si votre serveur MCP conserve quoi que ce soit d’une requête à l’autre, la dernière révision du protocole transforme cette habitude en bug. La spécification 2026-07-28 supprime la poignée de main initialize, retire l’en-tête Mcp-Session-Id et reconstruit MCP comme un protocole simple de requêtes et de réponses, où chaque message contient ce dont le serveur a besoin pour y répondre. Certains changements sont mineurs : un en-tête par-ci, un code d’erreur renuméroté par-là. D’autres feront planter un serveur qui fonctionnait parfaitement la semaine dernière.
Cet article classe les changements selon leur impact, en prenant comme référence le journal des modifications officiel et l’article d’annonce. Vous y trouverez les noms exacts des champs, les numéros de SEP, un tableau de ce qui est supprimé et de ce qui est simplement déprécié, ainsi qu’un ordre de migration que vous pouvez terminer en un seul sprint.
💡 En bref : les sessions disparaissent, les requêtes initiées par le serveur utilisent désormais un schéma de nouvelle tentative, les résultats de liste sont mis en cache, l’autorisation devient plus stricte et les tâches passent dans une extension. Roots, Sampling et Logging fonctionnent toujours, mais seulement pendant douze mois au minimum.
Pourquoi cette révision est différente
Les révisions précédentes ajoutaient des fonctionnalités. Celle-ci supprime des hypothèses. Le passage d’un échange avec état et bidirectionnel à un échange sans état touche chaque transport, chaque SDK et chaque passerelle placée devant un serveur.
Cinq révisions, une seule direction
Version
Changement principal
2024-11-05
Architecture client-serveur, JSON-RPC 2.0, outils, ressources, prompts, stdio et HTTP avec SSE
Sortie structurée des outils, elicitation, liens de ressources, serveurs classés comme serveurs de ressources OAuth, batching supprimé
2025-11-25
Recherche des métadonnées OpenID Connect, consentement incrémental aux scopes, icônes, Client ID Metadata Documents, tâches expérimentales
2026-07-28
Cœur sans état, aucune session, Multi Round-Trip Requests, en-têtes de routage, listes pouvant être mises en cache, cadre d’extensions
Lisez la colonne de droite de haut en bas et la direction est évidente. Chaque révision éloigne MCP d’une socket longue durée, de type conversation, pour le rapprocher de ce qu’un équilibreur de charge, un CDN et un environnement serverless peuvent gérer sans traitement particulier.
La version est déjà prise en charge là où cela compte. Les quatre SDK de niveau 1 (TypeScript, Python, Go et C#) fonctionnent avec la version 2026-07-28, et le SDK Rust la prend en charge en bêta. Les mainteneurs reconnaissent qu’il y aura « un certain coût de migration, en particulier pour les développeurs qui s’appuyaient sur les identifiants de session », puis ajoutent que les premiers retours de tests ont facilité le processus.
Le cœur sans état
Deux propositions font l’essentiel des dégâts : SEP-2567 supprime les sessions, et SEP-2575 supprime la poignée de main et remanie la manière dont les notifications circulent.
Pas de poignée de main, pas d’identifiant de session
La requête initialize et notifications/initialized disparaissent, tout comme Mcp-Session-Id. Les points de terminaison de liste (tools/list, resources/list, prompts/list) ne peuvent plus varier selon la connexion, car il n’existe plus d’identité de connexion sur laquelle s’appuyer. Lorsqu’un outil a besoin d’un état entre plusieurs appels, le serveur crée un identifiant explicite, comme un identifiant de panier ou d’espace de travail, et le modèle le renvoie comme un argument d’outil ordinaire.
À la place de la poignée de main, chaque requête transporte son propre contexte dans _meta. Cet extrait montre la forme, sans reprendre la spécification mot pour mot :
Une incompatibilité de version renvoie UnsupportedProtocolVersionError, et les serveurs s’identifient dans chaque résultat via io.modelcontextprotocol/serverInfo.
⚠️ L’état caché est le vrai risque. Les tables en mémoire indexées par identifiant de session, les niveaux de log par connexion et les listes d’abonnements par connexion cessent de fonctionner. Ils sont faciles à manquer lors d’une recherche dans le code, car ils ne contiennent que rarement le mot « session ».
Annoncer les versions et les capacités. Les serveurs doivent désormais implémenter une nouvelle RPC dans l’espace de noms server/ qui indique leurs versions de protocole prises en charge, leurs capacités et leur identité. Les clients peuvent l’appeler avant tout le reste pour choisir une version dès le départ, et sur STDIO elle sert de sonde de rétrocompatibilité. Le journal des modifications la place juste après la suppression de la poignée de main ; vérifiez donc la page du schéma pour le nom exact de la méthode et la forme de la réponse avant de l’intégrer.
Ce qui remplace le flux GET
Le point de terminaison GET HTTP, resources/subscribe et resources/unsubscribe sont remplacés par un seul appel : subscriptions/listen. Il ouvre un flux de réponse POST unique et de longue durée, et les clients choisissent les types de changements qui les intéressent :
toolsListChanged
promptsListChanged
resourcesListChanged
resourceSubscriptions
Le serveur accuse réception de chaque notification et l’étiquette avec io.modelcontextprotocol/subscriptionId. Les messages liés à une requête, comme notifications/progress et notifications/message, restent sur le flux de réponse de la requête à laquelle ils appartiennent.
Trois autres suppressions suivent : ping, logging/setLevel et notifications/roots/list_changed. Le niveau de log se définit désormais par requête grâce à io.modelcontextprotocol/logLevel, et un serveur ne doit pas émettre notifications/message pour une requête qui ne le contient pas.
La reprise SSE disparaît aussi. Il n’existe plus d’en-tête Last-Event-ID ni d’identifiants d’événements : un flux de réponse interrompu fait perdre la requête en cours, et le client doit la renvoyer avec un nouvel identifiant de requête. Un appel d’outil qui dure quatre-vingt-dix secondes sur une liaison instable nécessite désormais l’extension des tâches, plutôt que de la chance.
Multi Round-Trip Requests expliquées
Pourquoi les requêtes du serveur devaient disparaître
Avant cette révision, un serveur pouvait envoyer elicitation/create, sampling/createMessage ou roots/list au milieu d’un appel, sur un flux ouvert. Cela rivait le client à une instance de serveur et imposait un équilibrage de charge avec affinité de session ou un stockage partagé.
Multi Round-Trip Requests (SEP-2322) remplacent cette conception. La spécification est catégorique : les serveurs doivent envoyer ces requêtes via le mécanisme MRTR, l’ancien schéma n’est plus pris en charge, et il s’agit d’un changement cassant. Chaque résultat contient désormais aussi un champ resultType obligatoire. Une valeur signifie input_required, l’autre marque un résultat final ordinaire, et les clients traitent l’absence de ce champ venant d’un serveur plus ancien comme le type ordinaire.
Fonctionnement de la boucle de nouvelle tentative
Le flux comporte quatre étapes :
Le client envoie une requête normale, par exemple tools/call.
Le serveur ne peut pas terminer, il renvoie donc un InputRequiredResult listant ce dont il a besoin.
Le client recueille les réponses auprès de l’utilisateur ou d’une autre source.
Le client relance la requête d’origine avec inputResponses joint, en utilisant un nouvel identifiant JSON-RPC.
Seules trois requêtes client peuvent recevoir ce résultat : prompts/get, resources/read et tools/call. Chaque InputRequiredResult exige au moins l’un de inputRequests ou requestState, et un serveur ne doit pas demander une capacité que le client n’a jamais déclarée. Comme la nouvelle tentative indique au client comment les choses se sont terminées, la notification de fin d’elicitation et le champ elicitationId de la version 2025-11-25 sont supprimés.
Traitez requestState comme une saisie utilisateur
La chaîne requestState transite par le client ; la spécification demande donc de la considérer comme contrôlée par un attaquant. Si elle influence l’autorisation, l’accès aux ressources ou la logique métier, protégez son intégrité avec un HMAC ou un AEAD et rejetez tout ce qui échoue à la vérification.
Pour la protection contre le rejeu, placez trois éléments dans la charge protégée et vérifiez chacun à la réception :
le principal authentifié
une expiration courte
un identifiant de la requête d’origine, comme le nom de la méthode et un condensat de ses paramètres principaux
Ces mesures limitent le rejeu sans garantir un usage unique. Une consommation à usage unique doit être imposée côté serveur.
En-têtes, mise en cache et codes d’erreur
Les en-têtes de routage pour les passerelles
SEP-2243 impose Mcp-Method et Mcp-Name sur chaque requête POST Streamable HTTP. L’enjeu est opérationnel : les passerelles et les WAF peuvent désormais router, mesurer et limiter le débit du trafic MCP sans analyser le corps JSON. Des en-têtes personnalisés peuvent aussi être dérivés des paramètres d’outil grâce à x-mcp-header, et une incohérence entre l’en-tête et le corps remonte sous forme d’erreur HeaderMismatch, désormais présente dans le schéma.
💡 Si vous exploitez une passerelle d’API devant un serveur MCP, c’est le changement qui rapporte en premier. Écrivez vos règles sur les deux en-têtes plutôt que sur des expressions régulières appliquées au corps des requêtes.
Indications de cache et codes d’erreur
SEP-2549 ajoute une interface CacheableResult. Les résultats de tools/list, prompts/list, resources/list, resources/read et resources/templates/list doivent désormais inclure :
ttlMs : une indication de fraîcheur en millisecondes, pour que les clients puissent mettre en cache au lieu d’interroger en boucle
cacheScope : "public" ou "private", qui indique aux intermédiaires partagés s’ils peuvent stocker la réponse
Les deux complètent les notifications listChanged existantes. Les serveurs doivent aussi renvoyer les outils dans un ordre déterministe, ce qui aide les caches clients et augmente les taux de succès du cache de prompts côté modèle.
Les codes d’erreur ont également changé, selon une nouvelle politique d’attribution : de -32000 à -32019 reste défini par l’implémentation, et de -32020 à -32099 est réservé à la spécification.
Erreur
Ancien code
Nouveau code
Ressource introuvable
-32002
-32602 (Invalid Params)
HeaderMismatch
-32001
-32020
MissingRequiredClientCapability
-32003
-32021
UnsupportedProtocolVersion
-32004
-32022
Si un client se fonde sur les anciens numéros, il interprétera mal les nouvelles erreurs.
L’autorisation devient plus stricte
Vérifications de l’émetteur et identifiants liés
Trois changements renforcent le flux OAuth :
SEP-2468 : les serveurs d’autorisation devraient inclure le paramètre iss de la RFC 9207, et les clients doivent valider un iss présent par rapport à l’émetteur enregistré avant d’échanger le code d’autorisation.
SEP-2352 : les identifiants clients sont liés au serveur d’autorisation qui les a émis. Stockez-les par identifiant d’émetteur, ne les réutilisez jamais avec un autre serveur et réenregistrez-vous lorsque le serveur change.
SEP-837 : les clients doivent envoyer un application_type adapté lors de l’enregistrement dynamique du client, ce qui évite les conflits d’URI de redirection OpenID Connect sur localhost.
L’enregistrement dynamique est en sursis
Le protocole d’enregistrement dynamique de client OAuth 2.0 (RFC 7591) est déprécié au profit des Client ID Metadata Documents. Il continue de fonctionner pour les serveurs d’autorisation qui ne proposent pas l’option plus récente, mais l’annonce indique qu’il sera supprimé dans une version ultérieure de la spécification. Prévoyez la bascule dès maintenant, plutôt que pendant un incident.
Tâches, schémas et extensions
Les tâches passent dans une extension
Les tâches expérimentales ont quitté le protocole de base pour devenir l’extension officielle io.modelcontextprotocol/tasks (SEP-2663). La refonte remplace la méthode bloquante tasks/result par une interrogation avec tasks/get, ajoute tasks/update pour qu’un client puisse envoyer une saisie à une tâche en cours, et supprime tasks/list. Les serveurs peuvent désormais renvoyer un descripteur de tâche sans que le client en ait fait la demande.
Ce dernier point compte pour les longs traitements. Un générateur de rapports lent n’a plus besoin de garder un flux de réponse ouvert : il renvoie un descripteur, le client interroge, et une connexion interrompue ne coûte rien.
Schémas assouplis et nouveaux emplacements d’extension
Des ajouts plus modestes, qui méritent chacun une ligne :
inputSchema et outputSchema peuvent utiliser n’importe quelle construction de JSON Schema 2020-12, et structuredContent peut être n’importe quelle valeur JSON (SEP-2106), avec de nouvelles règles pour la résolution de $ref et des bornes de ressources sur les constructions de composition.
ClientCapabilities et ServerCapabilities gagnent un champ extensions pour des fonctionnalités optionnelles au-delà du cœur.
Le contexte de trace OpenTelemetry circule dans _meta via traceparent, tracestate et baggage (SEP-414).
Le billet de Cloudflare sur cette version indique que son point de terminaison /mcp accepte à la fois les nouvelles requêtes sans état et les clients de l’ère 2025, ce qui est un schéma judicieux pour quiconque exploite un serveur public.
Ce qui casse et comment le corriger
Fonctionnalités supprimées
Ces éléments échoueront purement et simplement face à un pair en 2026-07-28 :
Supprimé
Remplacement
initialize et notifications/initialized
Champs _meta par requête
En-tête Mcp-Session-Id
Identifiants créés par le serveur dans les arguments d’outil
Point de terminaison GET HTTP, resources/subscribe, resources/unsubscribe
elicitation/create, sampling/createMessage, roots/list initiés par le serveur
InputRequiredResult et inputResponses
Fonctionnalités dépréciées
Déprécié ne signifie pas supprimé. Ces éléments fonctionnent encore pendant douze mois au minimum selon la nouvelle politique de cycle de vie des fonctionnalités (SEP-2596), qui définit les états Actif, Déprécié et Supprimé, ainsi qu’un registre public :
Déprécié
Piste suggérée
Roots (SEP-2577)
Paramètres d’outil, URI de ressources ou configuration du serveur
Sampling (SEP-2577)
Appeler directement l’API du fournisseur de LLM
Logging (SEP-2577)
Écrire dans stderr, ou utiliser OpenTelemetry
Transport HTTP+SSE
Streamable HTTP
Valeurs includeContext"thisServer" et "allServers"
"none" ou omettre le champ
Enregistrement dynamique du client
Client ID Metadata Documents
Certains billets rangent Roots, Sampling et Logging parmi les suppressions. Le texte de la spécification dit « déprécié » ; traitez-les donc comme une échéance à inscrire au calendrier, non comme une panne. Seuls ping et logging/setLevel ont réellement disparu.
Un ordre de migration qui fonctionne
Mettez d’abord le SDK à jour. Passez à une version de niveau 1 qui prend en charge la 2026-07-28 et lisez ses notes de migration avant de toucher à votre propre code.
Traquez l’état de session. Recherchez Mcp-Session-Id et toute table indexée par connexion. Remplacez chacune par un identifiant explicite transmis comme argument d’outil.
Acceptez les deux générations. Servez les anciens et les nouveaux clients depuis le même point de terminaison pendant la transition, comme le fait Cloudflare.
Réécrivez les appels initiés par le serveur. Transformez chaque appel d’elicitation, de sampling et de roots en InputRequiredResult avec un requestState signé.
Ajoutez les champs de cache. Renvoyez ttlMs et cacheScope sur les résultats de liste et de lecture, et triez les listes d’outils de façon déterministe.
Mettez à jour les règles de passerelle. Routez sur Mcp-Method et Mcp-Name, puis supprimez les règles d’analyse du corps.
Corrigez l’autorisation. Validez iss, stockez les identifiants par émetteur et planifiez le passage aux Client ID Metadata Documents.
Testez ensuite les cas difficiles : interrompez un flux de réponse en pleine requête, rejouez un requestState périmé et faites tourner deux instances de serveur sans routage collant. Si les trois se comportent correctement, la migration tient.
💡 Astuce rapide : collez le code de gestion des sessions de votre serveur et la section du journal des modifications ci-dessus dans Claude Sonnet 5 sur Picasso IA, puis demandez la liste de tous les endroits où un état fuit d’une requête à l’autre. Relisez le résultat vous-même, car un modèle peut passer à côté d’une table cachée derrière une fonction utilitaire.
Créez vos propres visuels
Les articles techniques comme celui-ci dépendent beaucoup de leurs schémas et de leurs visuels d’en-tête, et vous n’avez pas besoin d’une équipe de design pour les produire. Picasso IA réunit des dizaines de modèles de génération d’images au même endroit, ce qui vous permet de tester le même prompt sur plusieurs d’entre eux et de garder le meilleur résultat.
Essayez Seedream 5 Pro pour des scènes photoréalistes, GPT Image 2 pour un suivi précis du prompt, ou Ideogram v4 Quality lorsque votre image doit contenir du texte lisible. Décrivez la scène, choisissez un format 16:9 et générez quelques variantes avant de vous décider.
Ouvrez Picasso IA, choisissez un modèle et créez dès aujourd’hui la première image de votre prochain article sur MCP.