Logo Accura
Accura
Tous les articles

Serveur MCP sans session : remplacer Mcp-Session-Id par des handles

La spec MCP 2026-07-28 supprime les sessions du protocole. Voici comment trouver l'état caché dans ton serveur et le remplacer par des handles explicites.


La spec MCP 2026-07-28 retire les sessions du niveau protocole. L'en-tête Mcp-Session-Id disparaît du transport Streamable HTTP, et les endpoints de liste ne varient plus par connexion. Un serveur qui a besoin de garder quelque chose entre deux appels doit maintenant le faire de façon explicite, avec des identifiants qu'il émet lui-même et qui circulent comme des arguments d'outil ordinaires.

Le changement de code est simple. Le problème, c'est que la plupart des serveurs ne savent pas où ils stockent de l'état.

Pourquoi la session a sauté

MCP a été conçu pour un cas précis : une application locale qui parle à un processus local, en stdio, avec une connexion persistante. Un handshake, deux parties qui se souviennent l'une de l'autre, tout va bien.

Ce modèle tombe dès qu'on déploie derrière un load balancer, dans un cluster ou sur plusieurs régions. La solution habituelle a été les sticky sessions, parfois un store Redis, parfois une passerelle qui ouvre le corps de la requête pour extraire l'identifiant de session avant de router. Trois façons de faire porter par l'infrastructure un problème créé par le protocole.

La révision 2026-07-28 supprime la cause. Le protocole devient stateless, donc l'infrastructure peut l'être aussi. Un round-robin banal suffit.

Où ton code lit encore la session

C'est l'étape que tout le monde bâcle, et c'est 80 % du travail réel.

L'état ne se présente presque jamais comme une variable qui s'appelle session. Cherche plutôt ces formes :

Curseurs de pagination. list_items renvoie une première page et garde l'offset côté serveur. Le deuxième appel ne fonctionne que s'il atterrit sur la même instance.

Workflows multi-étapes. L'outil A prépare quelque chose, l'outil B le finalise. Entre les deux, l'objet intermédiaire vit en mémoire.

Contexte utilisateur mis en cache au handshake. Le tenant, le plan, les permissions résolus une fois puis réutilisés. Sans handshake, ça n'existe plus.

Uploads en plusieurs morceaux. Le buffer partiel est par définition de l'état.

Abonnements et ressources en tâche de fond. Ils portaient de l'état, ils ne sont pas dans le chemin de requête principal, personne ne pense à les regarder. C'est l'oubli classique.

Résultats coûteux mémorisés. Un cache en mémoire de process n'est pas grave en soi, mais s'il devient une hypothèse de correction plutôt qu'une optimisation, il devient de l'état.

Fais cette recherche sur le serveur, mais aussi sur tes clients maison, tes passerelles et ta configuration de déploiement. Un audit statique ne prouve rien, il te donne la liste des endroits à examiner. La compatibilité réelle se vérifie en exécution.

Le pattern handle

Le remplacement recommandé par la spec est simple : ton serveur fabrique un identifiant opaque, le renvoie dans la réponse, et le client le passe en argument d'un appel suivant.

Le principe :

  1. L'outil qui crée de l'état écrit cet état dans un stockage partagé, avec une clé qu'il génère
  2. Il renvoie cette clé au client, comme une valeur de retour normale
  3. L'outil suivant accepte cette clé comme paramètre déclaré dans son schéma
  4. Il résout la clé, vérifie qu'elle appartient bien à l'appelant, et travaille

Trois règles qui évitent les ennuis :

Le handle est opaque et non devinable. Pas d'ID auto-incrémenté, pas de chemin de fichier encodé. Un identifiant aléatoire, résolu côté serveur.

Le handle porte son propriétaire. À la résolution, tu vérifies que l'appelant courant est bien celui qui l'a obtenu. Un handle qui traîne dans un historique de conversation ne doit rien donner à quelqu'un d'autre. C'est la faille la plus évidente de ce pattern, et c'est celle qu'on retrouve le plus souvent en audit.

Le handle expire. Choisis un TTL par type d'état. Un curseur de pagination vit quelques minutes. Un brouillon de configuration peut vivre une heure. Un upload partiel, le temps de l'upload. Prévois aussi une révocation explicite.

Deux cas concrets

Pagination. Avant, le serveur gardait l'offset. Après, il renvoie un next_cursor opaque qui encode ou référence la position, et l'outil accepte un paramètre cursor optionnel. C'est le cas le plus facile, et souvent celui qui n'a même pas besoin de stockage si le curseur est signé.

Workflow multi-étapes. Avant, prepare_export construisait un objet en mémoire et run_export le retrouvait. Après, prepare_export écrit l'objet en base, renvoie un export_id, et run_export prend export_id en argument obligatoire. Le bénéfice caché : le workflow devient reprenable, observable et testable, ce qu'il n'était pas quand il vivait dans un process.

C'est le point à retenir. Ce refactor n'est pas une taxe protocole. Il rend ton serveur meilleur.

Retirer les sticky sessions

Une fois l'état externalisé, la partie infrastructure est courte :

  • Retirer l'affinité de session du load balancer
  • Retirer l'inspection du corps de requête à la passerelle si tu l'avais mise en place pour router
  • Retirer le store de sessions partagé s'il ne servait qu'à ça
  • Router sur l'en-tête Mcp-Method plutôt que sur le contenu

Ne fais cette étape qu'après l'externalisation, pas avant. Sinon tu passes ta semaine à déboguer des erreurs intermittentes.

Le test qui compte

Un serveur stateless se vérifie d'une seule manière : tu lances un scénario multi-étapes réel, en round-robin sur au moins deux instances, et tu tues une instance au milieu du scénario.

Si le workflow finit correctement, l'externalisation est faite. S'il échoue, il reste de l'état caché, et tu sais exactement dans quel appel.

Ce test doit entrer dans ta CI, pas rester une manip manuelle faite une fois. Les deux modes d'échec de cette migration, l'affinité collante et le sampling mort, sont silencieux jusqu'en production. En développement, une instance unique masque le premier.

FAQ

Est-ce que je peux garder mon store Redis ?

Oui, mais pour stocker des handles, pas des sessions. La différence n'est pas cosmétique : un handle est référencé explicitement par la requête, donc n'importe quelle instance peut le résoudre. Une session est implicite, donc elle impose l'affinité.

Comment gérer un état qui doit vraiment vivre longtemps ?

Ce n'est plus un problème de protocole, c'est une entité de ton domaine. Donne-lui une table, un cycle de vie et un outil pour le lister.

Et si mes clients envoient encore Mcp-Session-Id ?

Un serveur qui répond aux deux révisions peut l'accepter sur l'ancienne. Sur la nouvelle, l'en-tête n'a plus de sens et ne doit pas devenir un chemin de contournement.

Tu ne sais pas où est l'état caché

C'est exactement ce que je cherche en audit. Envoie-moi ton dépôt, tu reçois sous 24 heures ouvrées la liste des endroits où ton serveur suppose encore une session, classés par risque, avec le chiffrage de la remise à niveau.

Prêt à faire le point ? Faire auditer mon serveur MCP →

Lectures liées

Un serveur MCP à faire auditer ?

Cinq minutes pour le décrire. Je réponds sous 24 heures ouvrées avec un premier diagnostic écrit et gratuit.

Faire auditer mon serveur MCP
Serveur MCP sans session : remplacer Mcp-Session-Id par des handles