Le 28 juillet 2026, le Model Context Protocol a publié une nouvelle révision de sa spécification. Le changelog s'ouvre sur deux points qui changent le fonctionnement de tout serveur distant : les sessions au niveau du protocole disparaissent, et le handshake initialize aussi. Chaque requête transporte désormais tout ce dont le serveur a besoin pour y répondre.
Pour beaucoup de serveurs, c'est une migration. Pour un serveur qui lit des pièces d'identité, cela ressemble plutôt à une confirmation, car ce type de serveur avait déjà de bonnes raisons d'être sans état. Cet article parcourt la révision du point de vue d'un serveur d'outils : quels changements il peut ignorer, lesquels il doit construire et lesquels valent la peine d'être adoptés tôt. Les exemples viennent d'un serveur que nous exploitons, donc les manques cités plus bas sont aussi les nôtres.
Ceci est le blog de doc.cheap, une API OCR pour passeports et pièces d'identité avec un serveur MCP en façade. Lisez les passages sur le produit en gardant cela à l'esprit.
La version courte
| Changement dans 2026-07-28 | Ce que fait un serveur d'outils sans état |
|---|---|
Plus de Mcp-Session-Id, plus de sessions |
Rien, s'il n'en a jamais gardé |
Plus de handshake initialize ; version et capacités dans le _meta de chaque requête |
Les lire à chaque requête, les comparer à l'en-tête MCP-Protocol-Version |
server/discover |
L'implémenter : c'est un MUST |
En-têtes Mcp-Method et Mcp-Name obligatoires |
Vérifier qu'ils correspondent au corps |
resultType dans chaque résultat |
Envoyer "complete" |
ttlMs et cacheScope dans les résultats de liste et de lecture |
Choisir une durée de cache et une portée |
| Outils dans un ordre déterministe | Garder stable l'ordre d'enregistrement |
| Flux HTTP GET et reprise supprimés | Répondre à GET et DELETE par 405 |
| Roots, Sampling et Logging dépréciés | Recevoir les fichiers comme arguments d'outil, journaliser vers stderr ou OpenTelemetry |
La suite de l'article explique chaque ligne et pourquoi elle compte davantage pour un serveur qui traite des documents.
Les sessions disparaissent, et cela convient aux serveurs de documents
Le premier point du changelog supprime "protocol-level sessions and the Mcp-Session-Id header from the Streamable HTTP transport". Les endpoints de liste comme tools/list "no longer vary per-connection", et les serveurs qui ont besoin d'un état entre les appels "use explicit, server-minted handles passed as ordinary tool arguments", c'est-à-dire des identifiants explicites émis par le serveur et passés comme des arguments d'outil ordinaires.
Un serveur de documents a rarement besoin d'une session. Pensez à ce qu'elle contiendrait : une clé d'API, peut-être une image en attente de lecture, peut-être le dernier résultat. Ce sont autant de données qu'on préfère ne pas garder dans la mémoire d'un processus entre deux appels, là où un bug peut les remettre à l'appelant suivant.
Notre serveur hébergé à https://mcp.doc.cheap/mcp était déjà construit ainsi avant cette révision. Le code le dit "stateless on purpose" : chaque POST reçoit un serveur neuf et un transport neuf, répond, puis est jeté, de sorte que "no session lives in memory, a restart loses nothing, and one caller's key can never be seen by another caller's request". Le transport est créé sans générateur d'id de session, ce qui, dans le SDK TypeScript, le rend sans état, et il répond en JSON simple plutôt qu'en flux.
Le modèle d'identifiants décrit par la révision est lui aussi déjà là. Quand un agent veut récupérer un résultat enregistré, il ne compte pas sur une session pour s'en souvenir. scan_document renvoie un id de scan, et les outils get_scan et delete_scan prennent cet id comme argument ordinaire scan_id. L'id est l'identifiant. N'importe quelle instance du serveur peut y répondre, tout comme un client qui se reconnecte demain.
Si votre serveur garde quelque chose par session, la question est la même : cela peut-il devenir un id que le serveur renvoie et que le client lui retransmet ? Pour le travail sur des documents, la réponse est généralement oui, et c'est généralement aussi un gain de confidentialité.
Plus de handshake : la version voyage avec chaque requête
Le deuxième point rend MCP sans état au niveau du protocole. L'échange initialize et notifications/initialized est supprimé, et "every request now carries its protocol version and client capabilities in _meta". En HTTP, la version figure aussi dans un en-tête. La page Streamable HTTP indique que chaque POST "MUST include an MCP-Protocol-Version header", et qu'il doit correspondre à la valeur de _meta ; sinon, le serveur répond 400 avec une erreur HeaderMismatch.
Voici à quoi ressemble un appel à un outil de documents sous la nouvelle forme. Les en-têtes et les clés de _meta suivent l'exemple de la spécification elle-même ; l'outil et ses arguments sont les nôtres, avec des valeurs fictives :
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: scan_document
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "scan_document",
"arguments": { "image_base64": "<base64 image>", "retain_hours": 0 },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "<client>", "version": "<version>" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
Pour le serveur, cela signifie trois vérifications à chaque requête au lieu d'un seul handshake :
- La version. Si le serveur ne prend pas en charge la version demandée, il répond 400 avec une
UnsupportedProtocolVersionErrorqui liste les versions qu'il prend en charge. - Les en-têtes face au corps.
Mcp-Methodest obligatoire sur chaque requête, etMcp-Namesurtools/call,resources/readetprompts/get. Ils reprennent la méthode et le nom de l'outil, et un écart est une erreurHeaderMismatch. - Les capacités. Tout ce que le serveur retenait de
initialize, il le lit désormais dans la requête en cours.
Les en-têtes comptent plus qu'il n'y paraît pour un serveur de documents. Un appel à scan_document transporte une image entière en base64 ; notre serveur hébergé accepte des corps de requête jusqu'à 36 Mio. Avec Mcp-Method et Mcp-Name dans les en-têtes, une passerelle peut router, compter ou limiter le débit de l'outil coûteux séparément des outils bon marché sans lire ce corps. La spécification ajoute un avertissement pour ces passerelles : elles "SHOULD verify" que l'en-tête de version désigne une version qui impose la validation des en-têtes et du corps, et rejeter la requête sinon, plutôt que de se fier à un en-tête que personne n'a vérifié.
server/discover est obligatoire
Le troisième point ajoute server/discover, et "servers MUST implement this RPC to advertise their supported protocol versions, capabilities, and identity". Les clients peuvent l'appeler en premier pour choisir une version, ou s'en servir comme test de compatibilité sur stdio.
C'est le seul changement qu'un serveur sans état ne peut pas sauter. Notre serveur expose toujours un petit document de découverte sur GET / (nom, version, transport et endpoint), mais c'est une simple page web, pas la méthode du protocole. Un serveur qui veut parler 2026-07-28 répond lui-même à server/discover.
Résultats : resultType, cache et ordre
Trois points plus modestes changent la forme d'un résultat.
resultType. Chaque résultat le porte désormais :"complete"pour une réponse ordinaire,"input_required"pour la réponse intermédiaire d'une requête à plusieurs allers-retours. Les clients "MUST treat results from earlier-protocol servers that omit the field as"complete"", donc un ancien serveur continue de fonctionner, mais un nouveau l'envoie.ttlMsetcacheScope. Les résultats detools/list,prompts/list,resources/list,resources/readetresources/templates/listdoivent porter une indication de fraîcheur en millisecondes et une portée,"public"ou"private". Pour un serveur dont la liste d'outils ne change qu'au déploiement et dont les ressources sont de la documentation embarquée, unttlMslong avec"public"va de soi. Une ressource qui dépend de la clé de l'appelant serait"private".- Ordre déterministe des outils. Les serveurs "SHOULD return tools from
tools/listin a deterministic order", pour aider le cache du client et les succès de cache du prompt. Si les outils sont enregistrés dans le code dans un ordre fixe, gardez-le ; le nôtre en enregistre six, toujours dans le même ordre.
Demander plus à l'utilisateur : les requêtes à plusieurs allers-retours
Les révisions précédentes laissaient un serveur envoyer ses propres requêtes au client au milieu d'un appel : elicitation/create pour poser une question à l'utilisateur, sampling/createMessage pour emprunter le modèle du client, roots/list pour connaître les dossiers autorisés. La révision remplace cela par le modèle Multi Round-Trip Requests. Le serveur répond avec resultType: "input_required" et une liste d'inputRequests, et le client renvoie la requête initiale avec inputResponses.
Pour un outil de documents, c'est l'endroit propre pour une question comme « la photo est coupée à gauche, en envoyer une autre ? ». Comme la nouvelle tentative est la même requête renvoyée, rien ne doit être conservé sur le serveur entre les deux appels. Un serveur qui n'utilisait aucune des requêtes initiées par le serveur, comme le nôtre, n'a rien à migrer ici.
Ce qui est déprécié, et quoi utiliser à la place
Roots, Sampling et Logging sont dépréciés. Ils continuent de fonctionner pendant une période de dépréciation d'au moins douze mois, mais "new implementations should not add support for them". Les remplacements proposés conviennent bien à un serveur de documents :
- Au lieu de Roots, passez les fichiers comme arguments d'outil ou par configuration. Notre serveur local ne lit un chemin de fichier que si un répertoire d'images est défini dans sa configuration, et seulement à l'intérieur de ce répertoire ; un chemin en dehors est refusé. Le modèle choisit l'argument ; l'opérateur fixe la clôture.
- Au lieu de Logging, journalisez vers stderr ou OpenTelemetry. La révision supprime aussi
logging/setLevel; le niveau de log voyage désormais par requête dans_meta, et les serveurs ne doivent pas émettre de messages de log pour les requêtes qui ne l'ont pas demandé. Le contexte de trace (traceparent) peut lui aussi passer dans_meta.
Les anciens clients continueront d'appeler
Tous les clients ne migrent pas le même jour, la page du transport décrit donc ce qu'un serveur qui ne prend en charge que 2026-07-28 doit faire du trafic plus ancien :
- GET ou DELETE sur l'endpoint MCP : 405 Method Not Allowed.
- Un en-tête
Mcp-Session-Id: l'ignorer, et ne jamais en générer ni en renvoyer. - Un en-tête
Last-Event-ID: l'ignorer, car les flux ne peuvent plus être repris.
Notre serveur hébergé répond déjà à GET et DELETE sur son endpoint par 405 avec un en-tête Allow: POST, OPTIONS, et ne génère aucun id de session. Un client qui parle encore une révision antérieure l'apprend par le corps de l'erreur : la spécification lui dit de revenir à initialize uniquement quand le corps du 400 n'est pas une erreur moderne reconnue.
Où en est notre serveur
Pour être précis : le serveur hébergé repose sur la version 1.30.0 du SDK TypeScript, et notre propre code ne mentionne encore 2026-07-28 nulle part. Ce qui, parmi les points ci-dessus, est déjà vrai pour lui vient de sa conception : pas de sessions, des ids comme identifiants, 405 pour GET et DELETE, des fichiers bornés par la configuration. server/discover, les vérifications d'en-têtes, resultType et les champs de cache constituent le travail restant. La page du serveur MCP d'OCR de passeports et le guide du serveur MCP décrivent ce à quoi les clients se connectent aujourd'hui.
Si vous choisissez une API de documents pour des agents, le comparatif des API OCR de passeports liste les services ; la partie MCP mérite d'être demandée à chacun séparément.
Une checklist pour votre propre serveur d'outils
- Aucun état ne vit entre les requêtes ; ce dont un appel ultérieur a besoin est un id renvoyé par le serveur.
- La version de chaque requête est vérifiée par rapport à l'en-tête
MCP-Protocol-Version, avecUnsupportedProtocolVersionErrorpour une version que vous ne prenez pas en charge. Mcp-MethodetMcp-Namesont vérifiés par rapport au corps.server/discoverrépond avec les versions, les capacités et l'identité.- Les résultats portent
resultType; ceux de liste et de lecture portentttlMsetcacheScope. tools/listrenvoie toujours le même ordre.- GET et DELETE reçoivent 405 ; les en-têtes de session et d'id d'événement sont ignorés.
- Les fichiers arrivent comme arguments à l'intérieur d'une clôture configurée, pas par Roots.
- Les logs vont vers stderr ou OpenTelemetry, pas vers le client.
Le texte complet se trouve sur modelcontextprotocol.io, et l'article de publication donne le contexte. Plus d'informations sur notre serveur figurent sur la page du serveur MCP d'OCR de passeports.
Une question pour vous : votre serveur MCP gardait-il en session quelque chose que vous devez maintenant transformer en identifiant ? Dites-le-nous dans les commentaires ci-dessous.