Tutoriel
Qu’est-ce que Stateless MCP ? Architecture sans état 2026, JSON-RPC et Remote Server expliqués
La spec MCP 2026-07-28 rend la couche protocole sans état : chaque requête JSON-RPC embarque version et capabilities ; les Remote Servers passent derrière un équilibreur HTTP ordinaire. Les arguments restent du JSON — validez localement en production.
Model Context Protocol (MCP) permet aux clients IA de découvrir outils, ressources et prompts et de les brancher au contexte du modèle. Le plus grand changement d’architecture en 2026 : passer d’un protocole bidirectionnel avec état — handshake puis Session ID — à du JSON-RPC sans état où chaque requête se décrit et se route seule. Si vous appelez déjà des Remote MCP Servers depuis Claude Desktop, Cursor ou un Agent maison, cela touche déploiement, montée en charge et rate limiting à la passerelle. À la fin, vous séparez sans état protocole et avec état applicatif — et vous savez que le JSON des arguments exige encore une validation locale.
Ce que MCP et le sans état résolvent
MCP répond à « comment le modèle appelle des capacités externes de façon sûre et découvrable ». Les clients (Claude, ChatGPT, agents IDE) ont besoin d’un standard pour lister vos outils, lire des ressources, tirer des modèles de prompts ; les serveurs (GitHub, bases, APIs internes via un adaptateur MCP) ont besoin d’un standard pour exposer ces capacités sans plugin sur mesure par client.
Le MCP early gardait des sessions au transport : le client envoie d’abord initialize, le serveur renvoie les capabilities, et les requêtes suivantes portent Mcp-Session-Id, fixant le trafic sur une instance ou un Session Store partagé. OK en stdio local ; dès qu’un Remote Server doit scaler horizontalement, tourner sur Cloud Run / Lambda ou passer par une passerelle API pour limiter par outil, la sticky session devient le goulot.
La spec 2026-07-28 (Release Candidate) rend la couche protocole sans état : les métadonnées pour traiter une requête vivent dans la requête ; n’importe quelle instance derrière un round-robin ordinaire peut la prendre. Notes officielles : annonce de la spec MCP 2026-07-28 et la section Statelessness.
Ce que l’ère avec état a laissé
Dans l’ancien flux, les clients Streamable HTTP faisaient d’abord un handshake :
- Envoyer
initialize, échanger version de protocole et capabilities client/serveur. - Recevoir la notification
initialized; le serveur renvoie l’en-têteMcp-Session-Id. - Ensuite,
tools/calletresources/readdoivent porter le même Session ID, sinon la passerelle ou la mémoire de l’instance ne retrouve pas le contexte.
Coûts typiques en prod : sticky session sur l’équilibreur ; Redis partagé entre réplicas ; Sessions mortes après cold start Serverless ; des serveurs populaires comme GitHub MCP ont dû maintenir une couche Redis. Google, dans Scaling AI Agent Infrastructure, qualifie ce changement de « plus grand shift de spec depuis le lancement de MCP » — cœur : supprimer la gestion de session au transport.
| Dimension | Ère avec état (2025 et avant) | Cœur sans état (2026-07-28) |
|---|---|---|
| Handshake | initialize / initializedObligatoire |
Retiré ; optionnelserver/discover |
| Identifiant de session | Mcp-Session-Id en-tête de réponse |
Supprimé (SEP-2567) |
| Négociation des capabilities | Échangé une fois à l’ouverture | Chaque requête : _meta les porte |
| Montée horizontale | Routage sticky + Session Store partagé | Un round-robin ordinaire suffit |
Cœur sans état 2026-07-28
La définition « sans état » est stricte : le serveur ne doit pas s’appuyer sur des requêtes antérieures sur la même connexion pour déduire version, identité client ou capabilities ; chaque requête doit porter ces infos dans _meta. Des requêtes de tâches, threads ou conversations peuvent s’entrelacer sur le même transport ; la connexion ou le processus stdio n’est pas une frontière de session.
Le client met dans chaque requête, dans params._meta (ou équivalent) :
io.modelcontextprotocol/protocolVersion— obligatoire, ex.2026-07-28.io.modelcontextprotocol/clientCapabilities— obligatoire ; objet vide = pas de capabilities optionnelles.io.modelcontextprotocol/clientInfo— recommandé pour logs et debug (le serveur ne doit pas s’en servir pour la sécurité).
Pour connaître d’abord les capabilities serveur, le client peut appeler le nouveau server/discover RPC, mais ce n’est pas obligatoire — toute requête peut être la première sur n’importe quelle instance. Le serveur peut aussi ajouter tools/list aux réponses comme ttlMs, pour que le client mette la liste d’outils en cache dans le TTL.
L’état métier qui doit survivre à plusieurs appels d’outil (panier, session navigateur, brouillon de ticket) ne doit pas se cacher dans une Session transport. Comme une API HTTP : l’outil renvoie un handle explicite (basket_id et draft_id), que le modèle repasse dans les tools/call arguments JSON. Le modèle voit le handle — plus simple à déboguer qu’une Session boîte noire.
Comment JSON-RPC tourne dans MCP
La couche message MCP reste JSON-RPC 2.0 : chaque requête a jsonrpc et id et method et params ; les réponses portent result ou error ; les notifications n’ont pas de id. Même forme que « nom d’outil + objet d’arguments » dans l’article Apple Agent — MCP standardise juste le nom de méthode en tools/call, avec name et la arguments.
Un tools/call sans état typique ressemble à ceci (en-têtes HTTP au § suivant) :
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: searchInvoices
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "searchInvoices",
"arguments": {
"status": "unpaid",
"limit": 10
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "jsonvue-demo",
"version": "1.0.0"
}
}
}
}
En succès, result contient souvent un tableau content de sortie d’outil (souvent une chaîne JSON type: text ou un bloc structuré). En échec, JSON-RPC error porte code et la message ; sous Streamable HTTP, si en-têtes HTTP et method/name du body divergent, la spec exige une erreur -32020 de type header mismatch.
Pour les lecteurs JSONVue, le champ à surveiller : arguments — les Agents externes envoient des nombres en chaînes ou oublient des clés requises. Le MCP sans état ne valide pas votre JSON métier — même partage : Structured Output pour la sortie modèle, MCP pour les appels d’outil. Sur le site, Agents Apple et JSON explique : une même fonction métier peut servir App Intent, Foundation Models Tool et MCP ; seule l’adaptation change.
Remote Server et Streamable HTTP
Un Remote MCP Server est un endpoint MCP atteint en HTTPS, pas un sous-processus stdio local. Streamable HTTP est le transport principal Remote : un POST peut finir un RPC ; les longues tâches peuvent renvoyer un flux de notifications ouvert, mais l’état reste scoped à la requête, pas à une Session de connexion.
À partir de 2026-07-28, les requêtes Streamable HTTP doivent porter trois en-têtes alignés sur le body (SEP-2243), pour router sans parser le JSON :
MCP-Protocol-Version— doit correspondre à_metaprotocolVersion, sinon 400.Mcp-Method— correspond aumethodJSON-RPC, ex.tools/call.Mcp-Name— nom d’outil, prompt ou ressource, ex.searchInvoices.
Le déploiement se simplifie : même image Docker, plusieurs réplicas, round-robin ALB/nginx ; Cloud Run / Cloud Functions sans Redis Session dédié MCP ; quotas QPS par Mcp-Name moins chers qu’une inspection profonde du body. Des services prod comme GitHub MCP Server migrent déjà vers la spec sans état.
Le serveur stdio local reste valide ; la spec est claire : des requêtes sans lien peuvent s’entrelacer sur le même processus stdio ; le serveur ne doit pas traiter l’identité du processus comme session ID. Dev local et Remote cloud partagent la même implémentation d’outils — seul l’adaptateur transport diffère.
L’application peut rester avec état
« Sans état au protocole » ≠ « sans état métier ». Paniers, approbations multi-étapes, formulaires à moitié remplis peuvent et doivent rester avec état — mais l’état doit être explicite, pas lié à Mcp-Session-Id.
Modèle recommandé :
- Premier appel d’outil : créer la ressource, renvoyer
{ "draftId": "dr_8k2", ... }. - Description d’outil : les étapes suivantes doivent passer
draftId. - Le serveur consulte
draftIden base ou cache ; en cas d’absence, erreur métier JSON-RPC, pas un 404 Session mystérieux.
Pour les longues tâches, Tasks et extensions supportent MRTR (Multi-Request Task Routing) : l’outil peut d’abord renvoyer status: input_required ; le client attache la suite utilisateur dans _meta des requêtes suivantes. Toujours requête/réponse sur protocole sans état — la réponse peut s’étaler sur plusieurs tours.
Lien avec Structured Output
MCP et Structured Output règlent des couches différentes, mais les formes JSON se croisent souvent dans le même pipeline Agent :
| Couche | Mécanisme | Ce qui est contraint |
|---|---|---|
| Sortie modèle | Structured Output + JSON Schema | Champs et types de la réponse finale ou de l’extraction |
| Appel d’outil | MCP tools/call + inputSchema d’outil |
L’objet arguments envoyé au Server |
| API métier | REST / GraphQL JSON body | Charge utile réelle côté serveur ou HTTP aval |
Bonnes pratiques : tenir une table de champs, générer inputSchema MCP, OpenAPI REST et Structured Output Schema modèle. Sur le site, le guide JSON de l’API Gemini couvre le côté modèle ; le tutoriel Gemini Structured Output a des exemples cloud. Après le sans état MCP, la liste d’outils peut être en cache — si la version de Schema change, bump nom d’outil ou version protocole pour éviter d’envoyer une mauvaise forme dans arguments.
Comment inspecter le JSON en production
Pour déboguer un Remote MCP Server, alignez trois JSON : les tools/call arguments client, le body HTTP reçu par votre service métier, et le result.content renvoyé au modèle. Si les formes divergent, le bug est presque toujours dans l’adaptateur, pas « le modèle n’est pas assez malin ».
Passez par le navigateur : Formatage JSON pour confirmer le parse ; Validation JSON pour virgules finales et types ; JSON Schema pour valider les champs communs inputSchema et body API ; JSON DiffComparez « arguments modèle » et « body HTTP réel ». Trois fixtures : mcp-args.valid.json et http-body.valid.json et mcp-tool-error.json, même Schema en CI.
FAQ
Le MCP sans état a-t-il encore besoin d’une connexion WebSocket longue ?
Le Remote repose sur Streamable HTTP : un POST finit l’RPC ; les longs flux de notifications sont des flux de réponse par requête, pas l’ancien « handshake puis Session ». Le stdio local reste un processus longévif, mais chaque requête est indépendante au niveau protocole.
Les anciens clients avec Mcp-Session-Id peuvent-ils joindre les nouveaux Servers ?
Les Servers 2026-07-28 ne reconnaissent plus de Session ID protocole. Les clients doivent envoyer protocolVersion et clientCapabilities dans _meta à chaque requête, plus les en-têtes HTTP requis. En mix de versions, répartir à la passerelle par MCP-Protocol-Version.
Faut-il appeler tools/list à chaque fois ?
Non. Le serveur peut renvoyer ttlMs ; le client cache dans le TTL. Si outils ou Schema changent, raccourcir le TTL ou changer nom/version d’outil.
MCP valide-t-il les arguments pour le Server ?
Un outil peut déclarer inputSchema, mais le Server doit valider côté serveur. Les Agents externes se trompent souvent de type ; le sans état n’y change rien. Une erreur JSON-RPC structurée aide le modèle à réessayer mieux qu’un 500 silencieux.
Conclusion et suite
Stateless MCP ramène les Remote Servers 2026 à l’exploitation HTTP ordinaire : JSON-RPC 2.0 porte les méthodes, _meta le contexte protocole, Mcp-Method / Mcp-Name les en-têtes lisibles par la passerelle. initialize et Mcp-Session-Id s’effacent pour n’importe quelle instance, Serverless-friendly, rate limit par outil plus simple.
Passez l’état métier en ID explicites dans arguments ; validez encore les contrats JSON localement. Suite : vérifier votre endpoint Remote contre la spec 2026-07-28 (_meta et en-têtes complets) ; fixer arguments MCP et body REST sur un Schema ; vérifier l’aller-retour JSON dans le navigateur avec JSONVue.