Deux semaines après sa publication, la révision 2026-07-28 du Model Context Protocol commence à se diffuser dans les SDK et les intégrations. Ce n’est pas une révision d’entretien : c’est la plus grosse rupture depuis le lancement du protocole. Le cœur devient sans état, la poignée de main initialize disparaît, les opérations longues passent par les Tasks, et trois primitives historiques — Roots, Sampling, Logging — entrent en dépréciation. Si vous maintenez un serveur MCP en production, vous avez une migration à planifier, pas une mise à jour de dépendance.
Pourquoi cette révision maintenant
MCP a franchi le seuil des 400 millions de téléchargements mensuels de SDK, soit environ quatre fois plus qu’un an auparavant. À cette échelle, le protocole n’est plus consommé principalement par des serveurs locaux lancés en stdio depuis un IDE : il est consommé par des serveurs distants, multi-tenants, exposés derrière un répartiteur de charge et facturés à la requête.
C’est exactement là que le modèle à sessions coinçait. Le transport HTTP streamable imposait une séquence initialize → attribution d’un Mcp-Session-Id → requêtes ultérieures portant cet identifiant. Conséquences opérationnelles bien connues des équipes qui ont tenté de l’héberger :
- Affinité de session obligatoire. L’état de négociation vivait en mémoire dans un processus. Il fallait du sticky routing, ou une couche de partage d’état pour un protocole qui n’en demandait pas explicitement.
- Aller-retour de démarrage systématique. Chaque nouveau client payait un
initializeavant de pouvoir appeler quoi que ce soit. En serverless, cela signifiait un cold start pour rien. - Mise à l’échelle à zéro impossible en pratique. Recycler une instance invalidait des sessions actives.
- Réponses non cacheables.
tools/listétait formellement lié à une session, donc non partageable entre clients, alors que son contenu est le plus souvent identique pour tout le monde.
La révision 2026-07-28 traite la cause plutôt que les symptômes.
Le cœur sans état, concrètement
Trois suppressions structurent tout le reste.
Plus de Mcp-Session-Id. Le protocole ne définit plus d’identifiant de session. Toute information dont le serveur a besoin pour traiter une requête doit être portée par la requête elle-même : en-têtes de transport, jeton d’autorisation, paramètres de l’appel.
Plus de poignée de main initialize. La négociation de version se fait par en-tête (MCP-Protocol-Version), et les capacités ne sont plus échangées dans un message de démarrage bloquant. Le premier message utile qu’un client envoie peut être un tools/call.
Plus d’état implicite entre requêtes. Deux requêtes consécutives du même client peuvent atterrir sur deux instances différentes, dans deux régions différentes, sans que le serveur ait à s’en soucier.
Avant :
POST /mcp HTTP/1.1
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
HTTP/1.1 200 OK
Mcp-Session-Id: 8f3c2a1e-...
POST /mcp HTTP/1.1
Mcp-Session-Id: 8f3c2a1e-...
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
Après :
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Authorization: Bearer eyJ...
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
Deux corollaires méritent attention.
Les réponses de liste deviennent cacheables. tools/list, resources/list et prompts/list ne dépendant plus d’une session, elles peuvent être servies avec ETag et Cache-Control, mises en cache côté client, voire par un CDN. Pour un serveur public à catalogue stable, c’est un changement de profil de charge considérable : la majorité du trafic de découverte n’atteint plus votre code applicatif.
Les échanges multi-aller-retour (MRTR) remplacent les requêtes serveur → client. Auparavant, un serveur qui avait besoin d’une information de l’utilisateur (elicitation) ou d’une complétion du modèle (sampling) initiait une requête vers le client sur le canal ouvert. Sans canal persistant, ce modèle tombe. À la place, le serveur termine sa réponse avec un résultat de type InputRequiredResult : il rend la main au client, en décrivant ce qu’il attend, et le client relance l’opération en fournissant la donnée. Le contrôle de flux appartient désormais toujours au client, ce qui rend le protocole beaucoup plus facile à raisonner — et à auditer.
Conséquence opérationnelle : un serveur MCP devient une charge HTTP ordinaire
C’est le vrai apport de la révision. Un serveur MCP 2026-07-28 se déploie exactement comme une API REST :
- sur AWS Lambda, Cloud Run, Cloudflare Workers ou n’importe quel runtime edge, sans couche de state partagé ;
- derrière un répartiteur de charge en round-robin, sans sticky sessions ;
- avec mise à l’échelle horizontale et retour à zéro instance ;
- avec l’outillage HTTP standard : rate limiting par route, WAF, cache, tracing distribué par requête.
Ce que l’on perd est réel et doit être budgété. Il n’existe plus de canal naturel pour que le serveur pousse spontanément une notification : les */list_changed reposent désormais sur l’invalidation de cache et la revalidation côté client. La progression d’une opération longue ne se streame plus « gratuitement » : elle passe par les Tasks. Et tout contexte serveur qui survivait entre deux appels doit être externalisé explicitement — base de données, cache distribué, ou état encodé dans les arguments et le jeton.
Tasks : le cycle de vie des opérations longues
Les Tasks, contribuées par AWS, formalisent ce que chacun bricolait. Un tools/call qui ne peut pas répondre dans le budget d’une requête HTTP renvoie une référence de tâche, et le client pilote ensuite le cycle de vie.
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"task": {
"taskId": "tsk_01J9Z8",
"status": "working",
"pollInterval": 2000
}
}
}
Le client interroge ensuite l’état, met à jour les paramètres si le serveur demande une entrée, ou annule :
{"jsonrpc":"2.0","id":8,"method":"tasks/get","params":{"taskId":"tsk_01J9Z8"}}
{"jsonrpc":"2.0","id":9,"method":"tasks/update","params":{"taskId":"tsk_01J9Z8","input":{...}}}
{"jsonrpc":"2.0","id":10,"method":"tasks/cancel","params":{"taskId":"tsk_01J9Z8"}}
tasks/list a disparu, et c’est cohérent : énumérer les tâches suppose que le serveur maintienne un registre global attribuable à un « client », c’est-à-dire précisément l’état que la révision supprime. Dans un déploiement multi-tenant, cette méthode était aussi un risque de fuite entre locataires. Le client est propriétaire des identifiants qu’il a créés ; à lui de les persister. Corollaire pratique : votre implémentation doit lier chaque taskId au sujet du jeton d’autorisation et refuser tout accès croisé, sans se reposer sur une quelconque notion de session.
Enfin, le code d’erreur associé aux tâches introuvables a changé par rapport à l’API expérimentale. Si vous avez codé en dur une valeur numérique dans un client ou dans des tests, vérifiez-la dans la spécification plutôt que dans votre mémoire.
Extensions et politique de dépréciation
Le cadre d’Extensions répond à la dérive naturelle de tout protocole à succès : l’inflation du cœur. Une capacité optionnelle vit désormais dans une extension nommée, négociée séparément, versionnée indépendamment. Le cœur reste petit et implémentable intégralement ; les fonctionnalités de niche n’imposent plus leur poids à tous les clients.
Pour les mainteneurs de clients, deux règles deviennent structurantes : déclarer explicitement les extensions supportées, et ignorer proprement — sans erreur — celles que l’on ne connaît pas. Un client qui rejette un champ inconnu cassera dès la première extension déployée par un serveur.
La politique de dépréciation formelle accompagne ce mouvement : une fonctionnalité marquée dépréciée reste fonctionnelle pendant une fenêtre de douze mois. Roots, Sampling et Logging entrent dans cette fenêtre. Vous avez donc un horizon connu, ce qui est nouveau dans l’histoire de MCP, mais un horizon fini.
Durcissement de l’autorisation
Le sans-état a une conséquence directe sur la sécurité : chaque requête doit être autorisée pour elle-même. Il n’existe plus de « session authentifiée » dont on hérite. Concrètement :
- validation de l’audience du jeton à chaque requête, avec l’indicateur de ressource (
resource) attendu ; - métadonnées de ressource protégée exposées pour que le client découvre le bon serveur d’autorisation ;
- interdiction claire du token passthrough : un serveur MCP ne réutilise pas le jeton reçu du client pour appeler une API en aval ; il obtient ses propres identifiants.
Les erreurs de confiance à ne plus commettre sont les mêmes que dans n’importe quelle API, mais elles étaient masquées par la session : traiter l’identifiant de session comme une preuve d’authentification, accepter un jeton valide mais destiné à un autre public, ou dériver des droits d’un état serveur non lié cryptographiquement au porteur.
Guide de migration
Un ordre de travail qui fonctionne :
- Auditer les dépendances à l’état. Cherchez tout ce qui touche à la session ou à des maps en mémoire indexées par client.
bash
rg -n "Mcp-Session-Id|sessionId|getSession|sessions\[" src/
- Externaliser ou éliminer. Chaque entrée trouvée doit finir soit dans un store partagé, soit dans les paramètres de la requête, soit à la poubelle.
- Supprimer la dépendance à
initialize. Votre handler doit pouvoir répondre à untools/callen première requête. Toute initialisation coûteuse doit être paresseuse et partageable entre instances. - Convertir elicitation et sampling initiés par le serveur en
InputRequiredResult. C’est la partie la plus intrusive : elle remonte souvent jusqu’à la logique métier de vos outils. - Quitter l’API Tasks expérimentale. Renommez les méthodes, retirez
tasks/list, corrigez les codes d’erreur codés en dur, et attachez chaque tâche à un sujet d’autorisation. - Rendre les listes cacheables. Ajoutez
ETaget une politique de cache explicite ; le gain est immédiat. - Planifier la sortie de Roots, Sampling et Logging avant la fin de la fenêtre de douze mois, en visant les extensions ou une alternative applicative.
Exemple minimal : avant / après
Avant — transport à sessions, état en mémoire, instance unique de fait :
const sessions = new Map<string, Session>();
app.post("/mcp", async (req, res) => {
const id = req.headers["mcp-session-id"] as string | undefined;
let session = id ? sessions.get(id) : undefined;
if (!session) {
session = createSession(); // initialize
sessions.set(session.id, session);
res.setHeader("Mcp-Session-Id", session.id);
}
await session.transport.handleRequest(req, res, req.body);
});
Après — handler pur, déployable en serverless, aucune mémoire partagée :
// Illustration : vérifiez les noms d'API exacts de votre SDK.
const server = buildServer(); // construit une fois par instance, sans état client
export async function handler(event: HttpEvent): Promise<HttpResponse> {
const claims = await verifyToken(event.headers.authorization, {
audience: RESOURCE_URI, // indicateur de ressource attendu
});
const response = await server.handleRequest(JSON.parse(event.body), {
subject: claims.sub, // tout le contexte vient de la requête
protocolVersion: event.headers["mcp-protocol-version"],
});
return {
statusCode: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify(response),
};
}
La différence n’est pas cosmétique : la seconde version n’a aucune raison de tourner sur une instance plutôt qu’une autre.
Faut-il migrer tout de suite ?
| Type de serveur | Recommandation |
|---|---|
Serveur local stdio (IDE, poste de dev) |
Sans urgence. Le gain opérationnel est nul ; migrez avec la prochaine montée de SDK, mais purgez dès maintenant Roots/Sampling/Logging. |
| Serveur distant hébergé sur VM ou container long-vivant | Migration à planifier ce trimestre. Le bénéfice principal est la fin du sticky routing et le cache des listes. |
| Serveur distant que vous voulez passer en serverless ou en edge | Migrez en priorité. C’est le cas d’usage que la révision débloque. |
| Serveur avec opérations longues déjà bricolées | Migrez : les Tasks remplacent du code maison que vous ne voulez plus maintenir. |
| Serveur dépendant fortement du sampling initié côté serveur | Prototypez d’abord InputRequiredResult : c’est là que se cachent les surprises de conception. |
| Client / hôte MCP | Supportez les deux révisions. Vous n’avez pas le luxe de choisir votre écosystème. |
Dans tous les cas, la fenêtre de douze mois n’est pas une invitation à attendre onze mois : les étapes coûteuses sont la conversion des échanges serveur → client et l’externalisation de l’état, et elles ne se raccourcissent pas.
Ce que cette révision dit du protocole
En supprimant les sessions, MCP arrête d’être un protocole conversationnel qui se déploie mal et devient un protocole de requête/réponse qui se déploie comme tout le reste. Le prix est payé par les mainteneurs de serveurs, sous forme d’une réécriture réelle des chemins qui supposaient un dialogue persistant. Le retour sur investissement est immédiat côté exploitation : mise à l’échelle horizontale triviale, cache du trafic de découverte, autorisation vérifiable par requête, et un cœur assez petit pour être implémenté complètement — le reste passant par les Extensions.
C’est la trajectoire habituelle des protocoles qui survivent à leur phase d’adoption : ils perdent leur état, gagnent une politique de dépréciation, et deviennent ennuyeux à héberger. Pour une brique d’infrastructure, « ennuyeux » est le meilleur compliment disponible.
Comments (0)
Please log in to leave a comment.
Log InNo comments yet. Be the first to comment!