Plus facile à lire avec un fond crème, une teinte, plus de contraste ou un texte plus grand ? Choisissez vos couleurs et votre texte, une fois pour toutes les pages.

Leçon gratuite

MCP à la main : JSON-RPC sur stdio

Après cette leçon: Je peux lire et écrire les messages qu’échangent un serveur d’outils et son client.

Leçon de 70 min6 commandesLab agent de code IA : utiliser opencode pour expliquer et corriger du code, et vérifier son travail

Ce qu’un serveur d’outils envoie vraiment

Le Model Context Protocol (MCP) est une façon standard, pour un agent, de trouver et d’appeler les outils d’un autre programme. Ses messages sont du JSON simple, un par ligne, sur l’entrée et la sortie standard. Une fois les messages lisibles pour vous, vous pouvez déboguer n’importe quel serveur d’outils.

Un serveur d’outils qui marche seul mais pas avec votre agent envoie souvent un message sous la mauvaise forme.

À la fin de cette leçon

Remarque

Je peux lire et écrire les messages qu’échangent un serveur d’outils et son client.

Mots-clés

MotCe que ça veut direExemple
JSON-RPCun format de message pour appeler des méthodes, en JSON
Requêteun message avec un id qui attend une réponse
Notificationun message sans id et sans réponse
stdiol’entrée et la sortie standard d’un processus
Code d’erreurun nombre qui dit quelle sorte d’échec-32601

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

JSON-RPC a trois sortes de messages

Une requête a un id, un nom de méthode et des paramètres facultatifs. Une réponse recopie cet id et contient soit un résultat, soit une erreur. Une notification n’a pas d’id : rien n’y répond.

Sur stdio, chaque message tient sur une ligne de JSON. Le serveur n’écrit que des messages sur sa sortie et envoie ses journaux sur son flux d’erreur.

Exemple travaillé : trois messages

SorteExempleRéponse
Requête{"jsonrpc":"2.0","id":2,"method":"tools/list"}Une réponse avec l’id 2
Réponse{"jsonrpc":"2.0","id":2,"result":{"tools":[]}}Aucune
Notification{"jsonrpc":"2.0","method":"notifications/initialized"}Aucune

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

C’est la forme, pas tout le protocole

Important

Le serveur ci-dessous reprend la forme du vrai protocole, en plus petit. Un vrai serveur utilise le SDK officiel, qui gère aussi les versions, l’annulation, d’autres types de contenu et beaucoup d’autres méthodes. Rien n’est installé ici.

Écrire le serveur d’outils, partie 1

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const PRIX = { baguette: 110, croissant: 130 };' 'const OUTILS = [{ name: \"lire_prix\", description: \"Prix d’un produit de boulangerie en centimes\", inputSchema: { type: \"object\", properties: { produit: { type: \"string\" } }, required: [\"produit\"] } }];' 'function envoyer(message) { process.stdout.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, message)) + \"\n\"); }' 'function repondre(id, result) { envoyer({ id: id, result: result }); }' 'function echouer(id, code, texte) { envoyer({ id: id, error: { code: code, message: texte } }); }' 'function texte(mots, isError) { return { content: [{ type: \"text\", text: mots }], isError: isError }; }' > serveur.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire le serveur d’outils, partie 2

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function appeler(id, params) {' '  if (params.name === \"lire_prix\") {' '    const prix = PRIX[(params.arguments || {}).produit];' '    if (prix === undefined) return repondre(id, texte(\"Produit inconnu\", true));' '    return repondre(id, texte(prix + \" centimes\", false));' '  }' '  return echouer(id, -32602, \"Outil inconnu : \" + params.name);' '}' 'function traiter(msg) {' '  if (msg.id === undefined) return;' '  if (msg.method === \"initialize\") return repondre(msg.id, { protocolVersion: \"2024-11-05\", capabilities: { tools: {} }, serverInfo: { name: \"boulangerie\", version: \"0.1\" } });' '  if (msg.method === \"tools/list\") return repondre(msg.id, { tools: OUTILS });' '  if (msg.method === \"tools/call\") return appeler(msg.id, msg.params || {});' '  return echouer(msg.id, -32601, \"Méthode introuvable : \" + msg.method);' '}' >> serveur.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire le serveur d’outils, partie 3

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'require(\"readline\").createInterface({ input: process.stdin }).on(\"line\", function (ligne) {' '  try { traiter(JSON.parse(ligne)); } catch (erreur) { echouer(null, -32700, \"Erreur d’analyse\"); }' '});' >> serveur.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire le client scripté, partie 1

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const serveur = require(\"child_process\").spawn(\"node\", [\"serveur.js\"]);' 'const appel = function (n, nom, args) { return { id: n, method: \"tools/call\", params: { name: nom, arguments: args } }; };' 'const scenario = [' '  { id: 1, method: \"initialize\", params: { protocolVersion: \"2024-11-05\", capabilities: {}, clientInfo: { name: \"demo\", version: \"0.1\" } } },' '  { method: \"notifications/initialized\" },' '  { id: 2, method: \"tools/list\" },' '  appel(3, \"lire_prix\", { produit: \"croissant\" }),' '  appel(4, \"lire_prix\", { produit: \"kiwi\" }),' '  appel(5, \"tout_effacer\", {}),' '  { id: 6, method: \"resources/list\" },' '];' > client.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire le client scripté, partie 2

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'let attente = 6;' 'require(\"readline\").createInterface({ input: serveur.stdout }).on(\"line\", function (ligne) {' '  console.log(\"<- \" + ligne);' '  attente -= 1;' '  if (attente === 0) serveur.stdin.end();' '});' 'scenario.forEach(function (m) {' '  const complet = Object.assign({ jsonrpc: \"2.0\" }, m);' '  console.log(\"-> \" + JSON.stringify(complet));' '  serveur.stdin.write(JSON.stringify(complet) + \"\n\");' '});' >> client.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Lancer le client contre le serveur

Commande
docker exec -w /home/node/project agent node client.js

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Une sortie possible

RequêteRéponse
initializeUn résultat avec la version du protocole et le nom du serveur
tools/listUn résultat qui liste lire_prix
lire_prix pour un croissantUn résultat avec le texte 130 centimes
lire_prix pour un kiwiUn résultat avec isError à true et Produit inconnu
tout_effacerUne erreur de code -32602
resources/listUne erreur de code -32601

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

Une session suit un ordre

Le client envoie d’abord initialize et le serveur répond avec ce qu’il sait faire. Le client envoie ensuite la notification qui dit qu’il est prêt. Alors seulement il demande tools/list et utilise tools/call.

L’agent montre au modèle les outils listés. Quand le modèle en demande un, le client envoie tools/call.

Deux sortes d’échec

Une erreur de protocole est une réponse d’erreur JSON-RPC. La requête elle-même était mauvaise : méthode inconnue, outil inconnu ou JSON illisible. Chacune a un code, comme -32601 pour une méthode absente.

Une erreur d’outil est un résultat normal avec isError à true. L’appel était correct, mais l’outil n’a pas pu faire le travail. Le modèle lit le texte et peut essayer autre chose.

Une erreur fréquente

Attention

Beaucoup pensent que tout échec doit être une erreur JSON-RPC. Une réponse d’erreur dit que la requête est mal formée. Un outil qui a tourné sans trouver le produit doit rendre un résultat avec isError à true. Ainsi le modèle voit la raison et peut se rattraper.

À propos de ce labo et de sa source: Lab agent de code IA : utiliser opencode pour expliquer et corriger du code, et vérifier son travail

Vous enseignez à une classe ? Proposez ce module à vos étudiants avec un guide de l'enseignant, des devoirs corrigés et un laboratoire pour chaque étudiant.