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 :

  1. La version. Si le serveur ne prend pas en charge la version demandée, il répond 400 avec une UnsupportedProtocolVersionError qui liste les versions qu'il prend en charge.
  2. Les en-têtes face au corps. Mcp-Method est obligatoire sur chaque requête, et Mcp-Name sur tools/call, resources/read et prompts/get. Ils reprennent la méthode et le nom de l'outil, et un écart est une erreur HeaderMismatch.
  3. 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.
  • ttlMs et cacheScope. Les résultats de tools/list, prompts/list, resources/list, resources/read et resources/templates/list doivent 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, un ttlMs long 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/list in 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, avec UnsupportedProtocolVersionError pour une version que vous ne prenez pas en charge.
  • Mcp-Method et Mcp-Name sont vérifiés par rapport au corps.
  • server/discover répond avec les versions, les capacités et l'identité.
  • Les résultats portent resultType ; ceux de liste et de lecture portent ttlMs et cacheScope.
  • tools/list renvoie 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.