MCP expliqué à partir de la spec : ce qu’est vraiment un serveur
Une ligne de JSON vers un sous-processus, treize définitions d’outils en retour, lues avec la révision 2026-07-28.
Dans cet article
Installez un serveur MCP publié, envoyez-lui une ligne de JSON, puis lisez ce qui revient.
npm i @modelcontextprotocol/server-everything@2026.8.31
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| npx @modelcontextprotocol/server-everything stdio{"result":{"tools":[{"name":"echo","title":"Echo Tool","description":"Echoes
back the input string","inputSchema":{"$schema":"http://json-schema.org/draft-07/
schema#","type":"object","properties":{"message":{"type":"string","description":
"Message to echo"}},"required":["message"]},"annotations":{"readOnlyHint":true,
… … 7,663 bytes on one line …
"jsonrpc":"2.0","id":1}Treize définitions d’outils, sur une seule ligne, venues d’un processus qui a lu une ligne depuis son entrée standard. Vous venez de parler le Model Context Protocol, sans SDK, sans bibliothèque client et sans framework. C’est tout : un transport, un format de message et un petit ensemble de méthodes nommées.
Le chapitre 18 définissait un outil comme deux choses : un JSON Schema que le modèle voit, et un endpoint dans votre code que le modèle ne voit jamais. Le chapitre 23 construisait un harness qui en tient un catalogue. Aucun des deux ne répondait à la question qui décide si quoi que ce soit est réutilisable : qui écrit le schéma, et comment passe-t-il de la personne qui l’a écrit jusque dans votre prompt ? MCP est une réponse à cette question, et il vaut la peine de le lire à la source, parce que presque tout ce qui a été écrit à son sujet décrit une révision qui n’existe plus.
Trois choses dans la commande que vous venez d’exécuter sont fausses, et chacune est une section de ce chapitre. Elle ne transportait aucune version de protocole, donc un serveur conforme l’aurait refusée. Elle a quand même obtenu une réponse, pour une raison que la spécification qualifie de danger plutôt que de fonctionnalité. Et elle a demandé l’une des trois primitives sans jamais découvrir que les deux autres existent.
Le problème qu’il résout, et l’analogie que la spec fait elle-même
Lien vers la section : Le problème qu’il résout, et l’analogie que la spec fait elle-mêmeAvant le câble, l’arithmétique. Vous avez applications d’AI et choses qu’elles devraient pouvoir atteindre : un calendrier, un outil de tickets, une base de données d’entrepôt, un outil de design. Sans contrat commun, quelqu’un écrit intégrations, et chacune d’elles est un schéma plus un endpoint plus une histoire d’authentification plus une charge de maintenance. Avec un contrat, le fournisseur de l’outil écrit un serveur, le fournisseur de l’application écrit un client, et le total est .
Ce n’est pas une observation nouvelle, et la spécification dit de qui vient l’idée :
MCP takes some inspiration from the Language Server Protocol, which standardizes how to add support for programming languages across a whole ecosystem of development tools. In a similar way, MCP standardizes how to integrate additional context and tools into the ecosystem of AI applications.1
Prenez cette comparaison au pied de la lettre, plutôt que comme un compliment. Avant ce protocole, prendre en charge un langage dans un éditeur voulait dire un plugin par éditeur ; après, une équipe langage livrait un serveur et chaque éditeur en bénéficiait. La mesure du succès n’était pas l’élégance, mais le fait que le nombre d’intégrations cesse de se multiplier. Il en va de même ici : la valeur est dans le nombre d’implémentations, pas dans le design. Un protocole que deux produits parlent est un format de données avec de la cérémonie en plus.
Ce qui passe réellement sur le fil
Lien vers la section : Ce qui passe réellement sur le filLes messages MCP sont du JSON-RPC 2.0. Une requête est un objet avec jsonrpc, un id, une method et des params optionnels ; une réponse transporte le même id et soit result, soit error ; une notification est une requête sans id et ne reçoit aucune réponse. La spécification ajoute trois contraintes par-dessus : le id doit être une chaîne ou un nombre et ne doit pas être null, il ne doit pas entrer en collision avec une autre requête encore en vol, et chaque résultat doit transporter un champ resultType.2
Sur le transport stdio — celui que la commande ci-dessus a utilisé — la règle de cadrage est une ligne par message :
Messages are delimited by newlines, and MUST NOT contain embedded newlines. […] The server MUST NOT write anything to its
stdoutthat is not a valid MCP message.3
Cette dernière clause est la manière la plus courante dont un serveur maison casse, et il casse silencieusement : un console.log perdu, une barre de progression, un avertissement de dépréciation venu d’une dépendance, et le parseur ligne par ligne du client tombe sur quelque chose qui n’est pas du JSON. L’échappatoire est dans la même section : le serveur peut écrire ce qu’il veut dans stderr, et le client ne devrait pas traiter cela comme une erreur. Le serveur de référence ci-dessus imprime Starting default (STDIO) server... à chaque lancement, sur stderr, ce qui explique pourquoi le pipe a quand même fonctionné.
L’autre transport standard est Streamable HTTP : chaque message est un POST vers un endpoint unique, et la réponse est soit un objet JSON, soit un flux de Server-Sent Events limité à la requête — le format de fil que le chapitre 14 a parsé à la main. La sémantique est identique sur les deux, parce qu’un transport est un binding : il définit le cadrage et la livraison, pas le sens.4
La première chose qui était fausse : il n’y avait pas de version
Lien vers la section : La première chose qui était fausse : il n’y avait pas de versionLa commande ci-dessus a envoyé tools/list et rien d’autre. Dans la révision actuelle, cette requête est malformée, et un serveur conforme doit la rejeter.
Depuis le 2026-07-28, MCP est un protocole sans état, et la spécification l’énonce sans réserve :
The Model Context Protocol (MCP) is a stateless protocol: all the information needed to process a request is contained in the request itself. A server processes each request independently; no state should be inferred from previous requests, even those on the same connection or stream.2
Chaque requête transporte donc sa propre version de protocole et ses propres capacités client, dans un objet réservé _meta à l’intérieur de params. Deux de ces champs sont requis sur chaque requête ; une requête à laquelle manque l’un ou l’autre est malformée et le serveur doit répondre -32602 :2
clé _meta | requis | ce que c’est |
|---|---|---|
io.modelcontextprotocol/protocolVersion | oui | la révision que parle cette requête, par exemple "2026-07-28" |
io.modelcontextprotocol/clientCapabilities | oui | ce que le client peut faire pour le serveur sur cette requête |
io.modelcontextprotocol/clientInfo | non (mais devrait) | nom et version du client, uniquement pour l’affichage et les logs |
io.modelcontextprotocol/logLevel | non | le niveau minimal de log que le serveur devrait émettre pour cette requête |
Écrit en entier, un tools/list correct ressemble à ceci — et c’est la dernière fois que ce chapitre montre les métadonnées en entier, parce qu’elles sont sur chaque requête à partir d’ici :
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{"elicitation":{"form":{}}},
"io.modelcontextprotocol/clientInfo":{"name":"bare-hands","version":"0.0.1"}}}}L’objet de capacités est la négociation. Il n’y a plus d’étape de négociation séparée : le client déclare ce qu’il peut faire sur chaque requête, le serveur déclare ce qu’il peut faire dans le résultat, et aucune des deux parties ne peut utiliser une fonctionnalité que l’autre n’a pas revendiquée. Un serveur qui a besoin d’une capacité que le client n’a pas déclarée doit répondre -32021 et nommer la capacité manquante dans data.requiredCapabilities. Un serveur qui ne parle pas la version demandée doit répondre -32022 et lister les versions qu’il parle.2
Les clients qui veulent la réponse d’emblée peuvent la demander : server/discover est un RPC obligatoire qui renvoie les versions prises en charge, les capacités, l’identité et un bloc optionnel de instructions en un aller-retour.5 L’appeler est optionnel. L’implémenter ne l’est pas.
La deuxième chose qui était fausse : le serveur était legacy
Lien vers la section : La deuxième chose qui était fausse : le serveur était legacyLa commande a fonctionné. Dans la révision actuelle, elle n’aurait pas dû, et la raison mérite une mesure plutôt qu’un paragraphe, parce qu’elle résume l’état de tout l’écosystème en une ligne.
Sondez le serveur de référence comme la spécification dit à un client moderne de le faire :
echo '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}}}}' \
| npx @modelcontextprotocol/server-everything stdio{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}C’est la troisième branche de la règle de compatibilité : un DiscoverResult veut dire moderne, une erreur moderne reconnue veut dire moderne-mais-mauvaise-version, et tout le reste — y compris -32601 — veut dire legacy, revenez au handshake initialize.3 Faites-le donc, en demandant la révision actuelle :
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28",
"capabilities":{},"clientInfo":{"name":"bare-hands","version":"0.0.1"}}}
← {"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{"listChanged":true},
"prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},
"logging":{},"tasks":{…},"completions":{}},"serverInfo":{"name":"mcp-servers/everything",
"title":"Everything Reference Server","version":"2.0.0"},"instructions":"…"}}Le client a demandé 2026-07-28 et le serveur a répondu 2025-11-25. Le 7 septembre 2026, le serveur de référence officiel — package npm @modelcontextprotocol/server-everything, version 2026.8.31, publié le 31 août 2026 — n’implémente pas la révision actuelle. Pas plus, à ces dates, que le SDK TypeScript sur lequel il est construit : la release 1.30.0 est sortie le 27 juillet 2026, la veille de la révision.
Lisez la conséquence plutôt que le ragot. Presque tout ce qui est écrit sur MCP décrit un protocole avec un handshake initialize, une session, une requête roots/list que le serveur envoie au client, et un transport HTTP+SSE. Les quatre ont disparu ou sont en train de disparaître. Quand vous lisez quoi que ce soit sur MCP, y compris cette page, la première chose à chercher est un numéro de révision.
Et la raison pour laquelle la toute première commande a fonctionné est décrite dans la spécification comme un danger, pas comme une fonctionnalité :
some legacy servers do not validate that a request arrives after
initializeand would process an era-ambiguous method (such astools/call) under legacy semantics. Probing yields a deterministic failure instead.3
Mesure : envoyer tools/list à ce serveur sans aucun handshake renvoie tout le catalogue. Une méthode qui aurait dû être refusée a été servie, ce qui est exactement la raison pour laquelle la spécification dit de sonder avec server/discover d’abord, même si vous ne prenez en charge que les versions modernes.
Trois rôles, et la phrase à citer de tout le document
Lien vers la section : Trois rôles, et la phrase à citer de tout le documentMCP a trois parties, et la distinction entre les deux premières est celle que les gens fusionnent :
Host. L’application : le produit de chat, l’éditeur, l’agent. Elle possède la conversation, le modèle, les identifiants et le consentement de l’utilisateur. Elle crée les clients et impose la frontière de sécurité entre eux.
Client. Un connecteur à l’intérieur du host. Chaque client parle à exactement un serveur — une relation stricte 1:1 — et attache la version du protocole et les capacités à chaque requête qu’il route.
Server. Un processus ou un service qui expose des ressources, des outils et des prompts. Il peut être local ou distant, il fonctionne indépendamment, et tout son rôle est un domaine ciblé.6
Cette règle « exactement un serveur » n’est pas de la comptabilité. C’est ce qui rend le principe de design ci-dessous implémentable, et voici la phrase à retenir de la spécification si vous n’en gardez qu’une :
Servers should not be able to read the whole conversation, nor "see into" other servers. Servers receive only necessary contextual information. Full conversation history stays with the host. Each server maintains isolation. Cross-server interactions are controlled by the host.6
Cela renverse le modèle mental avec lequel la plupart des gens arrivent. Un serveur météo que vous connectez à votre assistant ne voit pas ce que vous avez demandé. Il voit un tools/call avec les arguments que le modèle a choisis, et rien d’autre : pas les tours précédents, pas votre system prompt, pas les résultats que le serveur calendrier a renvoyés un instant plus tôt. Si deux serveurs doivent coopérer, le host transporte délibérément une valeur de l’un vers l’autre, parce que le modèle l’a demandé. C’est pourquoi l’isolation est la propriété de sécurité sur laquelle s’appuie le chapitre 30 : un serveur compromis a un rayon d’explosion petit et défini, et l’agrandir exige que le host coopère.
La troisième chose : trois primitives, triées selon qui décide
Lien vers la section : La troisième chose : trois primitives, triées selon qui décideLa première commande a demandé des outils à ce serveur et en a reçu treize. Posez-lui les deux autres questions et il y répond aussi : resources/list en renvoie sept, prompts/list en renvoie quatre. Aucun n’est apparu, parce que rien ne l’a demandé. Ce qui nous amène à l’ossature pédagogique de MCP, présente dans la spécification sous forme de tableau que presque personne ne cite :
| Primitive | Contrôle | Description | Exemple |
|---|---|---|---|
| Prompts | Contrôlé par l’utilisateur | Modèles interactifs invoqués par choix de l’utilisateur | Commandes slash, options de menu |
| Resources | Contrôlé par l’application | Données contextuelles attachées et gérées par le client | Contenu de fichiers, historique git |
| Tools | Contrôlé par le modèle | Fonctions exposées au LLM pour effectuer des actions | Requêtes API POST, écriture de fichiers |
Pas « trois façons d’exposer une capacité ». Trois réponses à qui décide que cela se produit. Le modèle décide d’appeler un outil. L’application décide d’attacher une ressource. La personne décide d’exécuter un prompt. Si vous vous trompez, la fonctionnalité fonctionne quand même, mais elle fonctionne au mauvais moment et pour la mauvaise raison.
La façon la plus claire de le sentir est un calendrier. Voici un serveur qui expose le même calendrier trois fois, une fois comme chaque primitive, en cent lignes de Node pur sans dépendances :
const TOOL = {
name: "create_event",
description: "Create a calendar event. Writes to the calendar.",
inputSchema: {
type: "object",
properties: {
title: { type: "string", description: "Event title." },
startsAt: { type: "string", format: "date-time", description: "Start, ISO 8601 UTC." },
},
required: ["title"],
},
};
switch (method) {
case "resources/read":
return ok(id, { contents: [{ uri: "calendar://week",
mimeType: "application/json", text: JSON.stringify(EVENTS) }],
ttlMs: 60000, cacheScope: "private" });
case "prompts/get":
return ok(id, { description: PROMPT.description, messages: [{ role: "user",
content: { type: "text", text: `Read calendar://week and draft a plan. ` +
`Focus: ${params.arguments?.focus ?? "balance"}.` } }] });
case "tools/list":
return ok(id, { tools: [TOOL], ttlMs: 300000, cacheScope: "public" });
}Lancez-le et interrogez-le des trois façons. Sortie réelle, un message par ligne sur le fil, repliée ici pour la page, avec le _meta de la requête et le bloc d’identité du serveur omis :
→ resources/read {"uri":"calendar://week"}
← {"resultType":"complete","contents":[{"uri":"calendar://week",
"mimeType":"application/json","text":"[{\"id\":\"e1\",\"title\":\"Standup\",
\"startsAt\":\"2026-09-07T09:00:00Z\"},{\"id\":\"e2\",\"title\":\"Design review\",
\"startsAt\":\"2026-09-09T15:00:00Z\"}]"}],"ttlMs":60000,"cacheScope":"private"}
→ prompts/get {"name":"prepare_week","arguments":{"focus":"deep work"}}
← {"resultType":"complete","description":"Read the week and draft a plan.",
"messages":[{"role":"user","content":{"type":"text",
"text":"Read calendar://week and draft a plan. Focus: deep work."}}]}
→ tools/call {"name":"create_event","arguments":{"title":"Dentist",
"startsAt":"2026-09-10T08:30:00Z"}}
← {"resultType":"complete","content":[{"type":"text",
"text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],
"structuredContent":{"id":"e3","title":"Dentist","startsAt":"2026-09-10T08:30:00Z"},
"isError":false}Trois méthodes, trois formes, un calendrier. Maintenant, le point :
Lire la semaine est une ressource
Lien vers la section : Lire la semaine est une ressourceElle est adressée par une URI, elle est inerte, et l’application décide de l’attacher ou non à la conversation. Rien dans le protocole ne permet au modèle d’aller la chercher tout seul. Le résultat transporte ttlMs et cacheScope, nouveaux dans cette révision, pour que le client puisse mettre la semaine en cache une minute au lieu de poller.
Créer un événement est un outil
Lien vers la section : Créer un événement est un outilIl a un schéma, il a des effets de bord, et le modèle décide quand l’appeler. Son résultat transporte isError, qui est le champ que le chapitre 18 réclamait : un échec de validation revient comme un résultat d’outil que le modèle peut lire et corriger, pas comme une erreur de protocole.
« Prépare ma semaine » est un prompt
Lien vers la section : « Prépare ma semaine » est un promptC’est un modèle nommé, avec arguments, que la personne invoque : la commande slash dans le menu. Il renvoie des messages, pas une réponse. C’est une manière pour l’auteur d’un serveur de livrer la formulation qui fonctionne avec ses propres outils, ce qui est exactement la connaissance que l’auteur du serveur possède et que l’utilisateur n’a pas.
Presque tout le monde transforme ces trois choses en outils. Le résultat est un catalogue où une lecture que l’application aurait dû attacher silencieusement concurrence l’attention du modèle avec une écriture qui nécessite une approbation, et où la seule chose pour laquelle une personne voulait un bouton est enterrée dans un schéma. Bien le faire ne coûte rien, et cela se décide avant d’écrire une ligne.
Le serveur ne peut pas vous appeler
Lien vers la section : Le serveur ne peut pas vous appelerL’outil calendrier a un argument requis, title, et un argument optionnel, startsAt. Demandez-lui de créer un événement sans date, et quelque chose d’intéressant revient :
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"}}
← {"resultType":"input_required",
"inputRequests":{"when":{"method":"elicitation/create","params":{"mode":"form",
"message":"When should \"Dentist\" start?",
"requestedSchema":{"type":"object",
"properties":{"startsAt":{"type":"string","format":"date-time"}},
"required":["startsAt"]}}}},
"requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}Le serveur n’a pas envoyé de requête. Il a répondu à celle qu’on lui a donnée, avec resultType: "input_required" et une description de ce dont il a encore besoin. Le client recueille la réponse auprès de la personne, puis renvoie l’appel original — avec un nouveau id, transportant inputResponses et renvoyant tel quel le requestState opaque :
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"},
"inputResponses":{"when":{"action":"accept",
"content":{"startsAt":"2026-09-10T08:30:00Z"}}},
"requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}
← {"resultType":"complete","content":[{"type":"text",
"text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],"isError":false}C’est Multi Round-Trip Requests, introduit dans la révision actuelle, et cela a remplacé l’ancien design où les serveurs envoyaient des requêtes JSON-RPC aux clients. La spécification de transport énonce désormais la règle sans détour : « servers do not initiate JSON-RPC requests and clients do not send JSON-RPC responses ».4 Il n’y a qu’une direction d’initiative, et elle appartient au host.
Deux fonctionnalités côté client reposent sur ce mécanisme, et l’une d’elles porte un nom qui va vous piéger.
Elicitation est le serveur qui demande quelque chose à la personne : un formulaire avec un JSON Schema délibérément restreint — objets plats, propriétés primitives, pas d’imbrication — afin que n’importe quel client puisse le rendre sans moteur de mise en page. Elle porte une règle stricte : les serveurs ne doivent pas utiliser le mode formulaire pour demander des « passwords, API keys, access tokens, or payment credentials », et doivent utiliser le mode URL pour cela, qui envoie l’utilisateur vers une page que le client ne lit jamais.7
Sampling est le serveur qui demande une génération au modèle du host, afin qu’un serveur puisse être intelligent sans détenir d’API key. Et voici l’avertissement de vocabulaire, parce que ce mot signifie déjà autre chose dans ce cours : ce n’est pas le sampling du chapitre 17. Rien ici ne concerne la température, top-p ou la forme d’une distribution de probabilité. C’est un appel de modèle imbriqué qui remonte à travers un protocole.
Il y a une deuxième raison de ne pas s’y précipiter : dans cette révision, sampling est deprecated, aux côtés de roots et logging, dans SEP-2577, avec une migration suggérée sans détour : « integrate directly with LLM provider APIs instead of Sampling ».8 L’idée n’a pas échoué techniquement ; elle n’a pas justifié sa surface, et un protocole capable de retirer des choses est plus sain qu’un protocole qui ne le peut pas.
Cassez-le exprès : les connexions ne sont pas des sessions
Lien vers la section : Cassez-le exprès : les connexions ne sont pas des sessionsL’absence d’état ressemble à un détail de format de fil jusqu’à ce que vous la testiez. Prenez l’échange en trois messages ci-dessus et exécutez chaque message dans un processus séparé : un node calendar.mjs frais, aucune mémoire partagée, rien de transporté :
process A tools/call (no date) → resultType: input_required
requestState: eyJ0aXRsZSI6IkRlbnRpc3QifQ==
process B tools/call (with the answer, same requestState)
→ resultType: complete
"Created e3: Dentist at 2026-09-10T08:30:00Z"
process C resources/read calendar://week
→ events: 2 (Standup, Design review)Le processus B, qui n’a jamais vu la question, a terminé un appel multi-round-trip commencé par le processus A. C’est tout l’intérêt de requestState : la continuation voyage dans le message, donc rien ne dépend du fait que le processus soit le même.
Le processus C est l’échec. L’événement a été créé et il n’est pas là, parce que le serveur jouet garde EVENTS dans un tableau au niveau du module, et un tableau au niveau du module est de l’état de connexion. La note de la spécification nomme précisément l’erreur :
an open connection, such as a STDIO process, is not a conversation or session: clients may interleave unrelated requests on the same transport, and a server must not treat connection or process identity as a proxy for conversation or session continuity.2
La correction prescrite n’est pas une session. C’est un handle explicite : un outil de création renvoie un identifiant opaque, et chaque appel ultérieur le prend comme argument ordinaire. Le protocole n’en a aucun concept : « from the wire's perspective a handle is an ordinary string in a tool result and an ordinary argument to subsequent tool calls ».9 Ce qui met le modèle aux commandes pour le transporter, et le serveur aux commandes pour vérifier que cet appelant a le droit de l’utiliser à chaque appel, parce qu’un handle est un nom et non une permission.
Ce que coûte un serveur avant de faire quoi que ce soit
Lien vers la section : Ce que coûte un serveur avant de faire quoi que ce soitChaque outil qu’un serveur expose est un schéma qui entre dans votre prompt à chaque requête, et le chapitre 24 a mesuré ce que cela fait à une fenêtre. MCP ajoute un deuxième poste de coût facile à manquer, donc il vaut la peine de compter les deux sur le serveur de référence ci-dessus.
13 tool definitions (name + description + inputSchema): 1,307 tokens
cheapest tool, get-tiny-image 52
costliest tool, gzip-file-as-resource 235
server `instructions`, returned by discovery: 312 tokens
------
one server, connected, before it is used: 1,619 tokensDeux observations. La première est arithmétique : connectez cinq serveurs de cette taille et environ huit mille tokens de votre fenêtre sont réservés à chaque tour, pour toujours, que le modèle les utilise ou non — c’est le mécanisme derrière la réduction de 150 000 à 2 000 citée au chapitre 24, et la raison d’être de la découverte d’outils just-in-time.
La deuxième est une note de sécurité déguisée en comptabilité. instructions est du texte en langage naturel, écrit par l’auteur du serveur, qui atterrit dans le prompt du host, et les descriptions d’outils à côté de lui sont identiques. La spécification dit quoi faire à ce sujet dans ses propres principes de sécurité : les annotations et descriptions d’outils « should be considered untrusted, unless obtained from a trusted server », et les hosts « must obtain explicit user consent before invoking any tool ».1 Connecter un serveur MCP n’est pas ajouter une dépendance. C’est accorder à un inconnu 1 619 tokens de votre system prompt et le droit d’être appelé. Le chapitre 30 raconte ce qui arrive quand cet inconnu est hostile.
Section datée : la révision 2026-07-28, et ce qu’elle casse
Lien vers la section : Section datée : la révision 2026-07-28, et ce qu’elle casseTout dans cette section est vrai pour la révision de protocole 2026-07-28, la révision actuelle, lue le 7 septembre 2026. Les révisions sont datées YYYY-MM-DD et la date est la dernière fois qu’un changement rétro-incompatible a été effectué.10 Le document normatif est un fichier TypeScript, schema/2026-07-28/schema.ts ; le JSON Schema à côté est généré depuis lui, ce qui explique pourquoi la spécification est lue ici en TypeScript et pourquoi enseigner MCP depuis autre chose revient à enseigner une traduction.
| Ce qui a changé | Avant | Maintenant | Ce que cela casse |
|---|---|---|---|
| Le handshake | initialize + notifications/initialized, une fois par connexion | supprimé ; chaque requête transporte version _meta et capacités | tout client écrit avant cette révision |
| Sessions | en-tête Mcp-Session-Id, état attaché à la connexion | supprimées ; l’état voyage dans des handles explicites émis par le serveur | endpoints de liste qui variaient par connexion |
| Discovery | déduite du résultat initialize | server/discover, que les serveurs doivent implémenter | rien, mais il est désormais obligatoire de l’implémenter |
| Appels serveur vers client | le serveur envoyait roots/list, sampling/createMessage, elicitation/create | InputRequiredResult et un nouvel essai client | tout serveur qui poussait une requête vers un client |
| Forme du résultat | n’importe quel objet | resultType requis : "complete" ou "input_required" | rien : un champ absent doit être lu comme "complete" |
| Subscriptions | flux HTTP GET, resources/subscribe | un flux subscriptions/listen avec types opt-in | l’endpoint GET disparaît |
| Reprise de flux | replay Last-Event-ID sur Streamable HTTP | supprimé ; un flux cassé perd la requête, réémettez avec un nouveau id | clients qui comptaient sur la redelivery |
| Roots | une fonctionnalité client que les serveurs pouvaient demander | deprecated (SEP-2577) ; passez les chemins comme arguments d’outil ou URI de ressource | rien pour l’instant — fenêtre de douze mois |
| Sampling et logging | fonctionnalités client | deprecated (SEP-2577) | rien pour l’instant — fenêtre de douze mois |
| Transport HTTP+SSE | deprecated depuis 2025-03-26 | Deprecated selon la politique de cycle de vie (SEP-2596) | migrer vers Streamable HTTP |
| Enregistrement client | OAuth 2.0 Dynamic Client Registration, RFC 7591 | deprecated au profit de Client ID Metadata Documents | conservé pour les serveurs d’autorisation qui n’en disposent pas |
| Codes d’erreur | -32002 pour ressource introuvable | -32602 ; -32020–-32099 réservés à la spec | nouveaux codes -32020, -32021, -32022 |
Le changement de gouvernance sous ce tableau compte plus que n’importe quelle ligne. Cette révision a adopté une politique de cycle de vie et de dépréciation des fonctionnalités : les fonctionnalités sont Active, Deprecated ou Removed, une fonctionnalité deprecated documente son chemin de migration et reste dans la spécification au moins douze mois avant de devenir éligible à la suppression, et un registre liste tout ce qui est actuellement en état Deprecated.8 Avant cette politique, « deprecated » dans un protocole d’AI voulait dire ce que disait le dernier billet de blog. Maintenant, cela veut dire une date.
Afficher les détails
Les extensions, dont personne n’a encore parlé.
Au-delà du noyau, MCP définit des extensions optionnelles — « always opt-in and require explicit support from both client and server », déclarées via un champ extensions dans les capacités du client et du serveur.1 Trois méritent d’être connues par leur nom :
- Tasks (
io.modelcontextprotocol/tasks), sorties du protocole cœur vers une extension officielle dans cette révision : exécution asynchrone d’opérations longues, avec polling viatasks/get, entrée en cours d’exécution viatasks/updateet handles durables. C’est la réponse à un outil qui prend vingt minutes, ce que le chapitre 23 gérait avec un événement de progression et un signal qui atteint l’outil. - Skills over MCP, un groupe de travail qui rend les skills d’agent — sujet du chapitre 28 — découvrables et consommables via le protocole.
- MCP Apps, UI interactives rendues inline dans la conversation : graphiques, formulaires, lecteurs vidéo.
Et notez ce que « négocié » signifie désormais : il n’y a pas d’initialisation où négocier, donc une extension est déclarée par requête comme tout le reste.
Où se situe MCP, face à tout ce avec quoi on le confond
Lien vers la section : Où se situe MCP, face à tout ce avec quoi on le confondVoici le vocabulaire de tout ce bloc en un seul endroit.
| Ce que c’est | Qui parle à qui | Quand c’est la bonne réponse | |
|---|---|---|---|
| Une API simple | Une interface pour un programme | votre code ↔ un service | Vous écrivez l’appelant. Vous contrôlez le schéma, l’auth et la gestion des erreurs, et il n’y a pas de problème de découverte à résoudre. |
| MCP | Un protocole pour exposer outils, données et modèles à une application d’AI | host ↔ serveur, un client chacun | Quelqu’un d’autre a écrit la capacité et de nombreux hosts devraient pouvoir l’utiliser sans intégration sur mesure. |
| RAG | Une technique pour trouver du texte et le mettre dans le prompt | votre code ↔ votre index | Le modèle a besoin de savoir quelque chose. Chapitre 19. MCP est une façon de livrer un retriever ; ce n’est pas un retriever. |
| Agent skills | Un dossier avec un SKILL.md que le modèle lit | modèle ↔ un document | La connaissance est procédurale — comment nous faisons cela — et c’est de la prose, pas une fonction. Chapitre 28. |
| A2A | Un protocole permettant aux agents de collaborer comme pairs | agent ↔ agent | L’autre côté raisonne, planifie et conserve de l’état sur une tâche longue, plutôt que de répondre à un appel. |
| ACP | Était un protocole séparé de communication entre agents | — | Ce n’est plus une comparaison vivante. Voir ci-dessous. |
Deux d’entre eux méritent une phrase chacun, parce que c’est là que vit réellement la confusion.
MCP face à A2A n’est pas une rivalité, et les deux spécifications le disent. La documentation A2A trace la ligne selon ce qui se trouve à l’autre bout : MCP « defines how an AI agent interacts with and utilizes individual tools and resources, such as a database or an API », où un outil exécute des « specific, often stateless, functions » ; A2A s’adresse aux agents, des « more autonomous systems » qui « reason, plan, use multiple tools, maintain state over longer interactions, and engage in complex, often multi-turn dialogues ». Son propre résumé est la phrase à retenir : « A2A is about agents partnering on tasks, while MCP is more about agents using capabilities. »11 Les deux s’emboîtent : une application utilise A2A pour atteindre d’autres agents, et chaque agent utilise MCP pour atteindre ses propres outils. Le chapitre 25 traçait cette ligne dans un seul processus, entre demander à un sub-agent et lui remettre la conversation ; A2A la trace entre organisations.
MCP face à ACP est une comparaison avec une prémisse périmée, et c’est exactement pourquoi elle mérite une réponse. L’Agent Communication Protocol était un standard ouvert séparé pour la messagerie agent-à-agent. Sa propre documentation s’ouvre désormais sur l’avis : « ACP is now part of A2A under the Linux Foundation! »12 La réponse honnête à « MCP ou ACP ? » en septembre 2026 est que la question a une option de moins que ne le suggèrent les pages qui se classent dessus.
Et la comparaison que les gens demandent le plus, mcp vs api, a la réponse la moins intéressante : MCP est une API. Ce qu’il ajoute n’est pas de la puissance, mais des conventions : un ensemble fixe de noms de méthodes, un appel de découverte, une hiérarchie de contrôle sur les primitives, et un modèle d’isolation. Vous renoncez à la liberté de concevoir votre propre interface et vous gagnez tous les hosts qui parlent le protocole, ce qui est le compromis que tout protocole a toujours proposé.
Où cela va ensuite
Lien vers la section : Où cela va ensuiteVous pouvez désormais lire la spécification sans traducteur, distinguer une ressource d’un outil et d’un prompt selon qui en a le contrôle, taper une requête à la main quand une bibliothèque client vous ment, et dater n’importe quel article MCP que vous lisez selon les fonctionnalités deprecated qu’il enseigne encore comme actuelles.
Ce que vous n’avez pas fait, c’est en livrer un. Le chapitre 27 écrit le même serveur deux fois — TypeScript et Python, côte à côte, parce que MCP est le seul territoire vraiment bilingue de ce cours et que les chiffres le disent dans les deux sens. Il couvre correctement les deux transports live, l’inspector, le packaging et la moitié du protocole que ce chapitre a volontairement laissée de côté : l’autorisation. Parce qu’au moment où votre serveur est distant plutôt qu’un sous-processus sur votre propre laptop, le client d’un inconnu présentera un token, et la règle de la spécification sur ce que vous pouvez en faire est exceptionnellement stricte.
Ce qui soulève la question à laquelle le prochain chapitre doit répondre, et elle n’est pas sympathique : si un token arrive à votre serveur et qu’il a été émis pour l’audience de quelqu’un d’autre, qu’est-ce qui vous empêche exactement de le transmettre ?
Sources et méthode
Lien vers la section : Sources et méthodeChaque citation, nom de méthode, code d’erreur et règle de ce chapitre a été lu dans la spécification Model Context Protocol, révision 2026-07-28, le 7 septembre 2026. Chaque trace a été produite localement sur Node 22 : le serveur calendrier jouet fait 101 lignes sans dépendances, et le serveur de référence est le package npm publié nommé ci-dessous. Aucune API payante n’a été appelée pour écrire ce chapitre — rien ici n’a besoin d’un modèle, ce qui est précisément le point.
Les mesures : @modelcontextprotocol/server-everything@2026.8.31, publié le 31 août 2026, construit sur @modelcontextprotocol/sdk@1.30.0, publié le 27 juillet 2026 — un jour avant la révision que ce chapitre décrit. Il répond à server/discover par -32601, négocie 2025-11-25 quand on lui demande 2026-07-28, et sert tools/list sans aucun handshake. Son catalogue contient 13 outils en 7 663 octets ; les comptes de tokens sont o200k_base via tiktoken, sur les name, description et inputSchema de chaque définition, ce qu’un fournisseur rend dans votre prompt et non ce que pèse la trame JSON-RPC.
Anthropic, Code execution with MCP: building more efficient agents, 4 novembre 2025, est la source du chiffre 150 000-vers-2 000, cité et utilisé au chapitre 24 et seulement référencé ici.
Références
Lien vers la section : Références-
Specification,
modelcontextprotocol.io/specification/latest(redirigeant vers/2026-07-28), lu le 7 septembre 2026. Source de la comparaison avec le Language Server Protocol ; de l’affirmation selon laquelle la spécification est « based on the TypeScript schema inschema.ts» ; du résumé du protocole de base (« Stateless, self-contained requests », « Per-request capability negotiation ») ; de la liste d’extensions (Tasks, Skills over MCP, MCP Apps) et de l’affirmation selon laquelle les extensions « are always opt-in and require explicit support from both client and server » ; ainsi que des principes Security et Trust & Safety, notamment « Hosts must obtain explicit user consent before invoking any tool » et le traitement des annotations d’outils comme non fiables. ↩ ↩2 ↩3 -
Base Protocol,
modelcontextprotocol.io/specification/2026-07-28/basic. Source des contraintes JSON-RPC (id non null, pas de réutilisation d’id,resultTyperequis) ; de la section Statelessness et de sa note indiquant qu’un processus stdio ouvert n’est pas une session ; du tableau de clés réservées_metaet du statut requis/optionnel de chaque champ par requête ; de la règle-32602pour un champ requis manquant ; de la règleMissingRequiredClientCapability(-32021) ; et de la politique d’allocation des codes d’erreur. ↩ ↩2 ↩3 ↩4 ↩5 -
stdio transport,
modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio. Source des règles de cadrage délimité par nouvelles lignes, de l’exigence de pureté destdout, de l’autorisationstderr, et de la sonde de rétrocompatibilité à trois issues — y compris l’avertissement selon lequel certains serveurs legacy traitent des méthodes ambiguës d’époque sans handshake, ce que la mesure de ce chapitre reproduit. ↩ ↩2 ↩3 -
Transports overview,
modelcontextprotocol.io/specification/2026-07-28/basic/transports. Source de la formulation « a transport is a binding » et de l’affirmation selon laquelle les serveurs n’initient pas de requêtes JSON-RPC et les clients n’envoient pas de réponses JSON-RPC. ↩ ↩2 -
Discovery,
modelcontextprotocol.io/specification/2026-07-28/server/discover. Source du statut obligatoire deserver/discover, de la forme deDiscoverResult, et du champinstructionsdécrit comme « optional natural-language guidance for LLMs on how to use this server effectively ». ↩ -
Architecture,
modelcontextprotocol.io/specification/2026-07-28/architecture. Source des définitions de host/client/server, de la règle 1:1 client-vers-serveur, des quatre principes de design, dont le principe d’isolation est cité ici sans son cinquième bullet, « Host process enforces security boundaries », et de la section sur la négociation de capacités. ↩ ↩2 -
Elicitation,
.../client/elicitation, et Sampling,.../client/sampling. Source des deux modes d’elicitation et de leur schéma restreint ; de l’interdiction de demander des credentials via le mode formulaire ; de la définition de sampling, de son exigence human-in-the-loop et de l’avertissement de dépréciation qui y est attaché. ↩ -
Key Changes,
modelcontextprotocol.io/specification/2026-07-28/changelog, et Feature lifecycle and deprecation policy,.../community/feature-lifecycle. Source de chaque ligne du tableau des changements : suppression des sessions et de l’en-têteMcp-Session-Id(SEP-2567) ; statelessness et suppression deinitialize(SEP-2575) ;server/discover(SEP-2575) ;subscriptions/listen(SEP-2575) ; Multi Round-Trip Requests etresultType(SEP-2322) ; suppression de la capacité de reprise de flux (SEP-2575) ; dépréciation de Roots, Sampling et Logging (SEP-2577) ; reclassement de HTTP+SSE (SEP-2596) ; dépréciation de Dynamic Client Registration au profit de Client ID Metadata Documents ; renumérotation des codes d’erreur ; et fenêtre de dépréciation de douze mois. ↩ ↩2 -
Tools,
modelcontextprotocol.io/specification/2026-07-28/server/tools, et Server Features,.../server. Source du tableau de hiérarchie de contrôle reproduit ci-dessus ; des formestools/listettools/call; de la distinctionisErrorentre erreurs de protocole et erreurs d’exécution d’outil ; des règles de noms d’outils et de la note de namespace recommandant « prefixing tool names with a server identifier » ; ainsi que du guide non normatif « Stateful Tools » sur les handles explicites. ↩ -
Versioning,
modelcontextprotocol.io/specification/versioning. Source du schémaYYYY-MM-DD, des états de révision Draft/Current/Final, de la confirmation que 2026-07-28 est actuelle, et des règles de négociation par requête. Le tableau des tiers de SDK surmodelcontextprotocol.io/docs/sdkliste TypeScript, Python, C#, Go et Rust en Tier 1, Java et Ruby en Tier 2, et Swift, PHP et Kotlin en Tier 3. ↩ -
A2A Protocol, version 1.0.0,
a2a-protocol.org— la spécification et la page A2A and MCP: Relationship and Distinction, lues le 7 septembre 2026. Source de la distinction outils-contre-agents, de l’affirmation selon laquelle les deux protocoles « address distinct but highly complementary needs », et de la formulation partnering/using. ↩ -
Agent Communication Protocol,
agentcommunicationprotocol.dev, lu le 7 septembre 2026 : « ACP is now part of A2A under the Linux Foundation! », une bannière ajoutée au-dessus d’une spécification encore servie en entier — architecture, manifeste d’agent, découverte d’agent, structure de message, agents avec état, cycle de vie d’exécution et liste d’endpoints REST répondent encore tous 200. La spécification n’a pas disparu ; le projet, si. ↩