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

Tester un serveur MCP

Après cette leçon: Je peux tester un serveur avec des clients scriptés.

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

Tester un serveur MCP avec des clients scriptés

Un client scripté est un petit programme qui envoie à un serveur une liste fixe de messages et vérifie chaque réponse. Il ne se fatigue jamais et ne saute aucune étape.

C’est utile, car un serveur se casse facilement sans le vouloir. Un seul caractère changé peut modifier une réponse, et personne ne le voit avant qu’un vrai client échoue.

À la fin de cette leçon

Remarque

Je peux tester un serveur avec des clients scriptés.

Mots clés

MotCe que ça veut direExemple
Scénarioune liste de messages à envoyer, chacun avec la réponse attendue
Attendula réponse que veut le test
Reçula réponse que le serveur a vraiment envoyée
Écartle premier endroit où les deux ne correspondent pas
Jokerun repère qui correspond à toute valeur<tout>

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

Un scénario associe chaque message à sa réponse

Un scénario est une liste d’étapes. Chaque étape a un message à envoyer et la réponse attendue. Une étape de notification n’attend aucune réponse.

Certaines valeurs changent, comme un numéro de version. Le test les marque d’un joker : un changement à cet endroit n’est pas un échec. Tout le reste doit correspondre exactement.

Exemple : un scénario pour un outil qui additionne

ÉtapeEnvoyéRéponse attendue
1initializeLa version demandée, tools annoncé, n’importe quelle version du serveur
2La notification prêtAucune réponse
3tools/listUn outil nommé additionner
4additionner 2 et 3Le texte 5, isError faux
5additionner « 2 » et 3Une erreur d’outil, isError vrai

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

Dire le premier écart, avec son endroit

Un test qui dit seulement « échec » fait perdre du temps. Un bon test dit où. Le chemin result.content[0].text désigne une valeur précise de la réponse.

Le vérificateur parcourt la réponse attendue dans un ordre fixe et s’arrête au premier écart. Il dit ce qu’il attendait et ce qu’il a reçu.

Une erreur courante

Attention

Beaucoup lancent un test seulement sur un serveur qui marche. Un test qui n’a jamais échoué n’a jamais prouvé qu’il attrape quoi que ce soit. Cassez le serveur exprès et vérifiez que le test échoue. Puis défaites la casse.

Écrire l’assistant du modèle

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function demander(messages, echantillon, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", { method: \"POST\", headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" }, body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 80 }), signal: AbortSignal.timeout(30000) });' '    if (res.ok === false) throw new Error(\"statut \" + res.status);' '    const donnees = await res.json();' '    return { texte: donnees.choices[0].message.content, enregistre: false };' '  } catch (erreur) {' '    console.log(\"Échantillon enregistré : aucun modèle n’a répondu (\" + erreur.message + \").\");' '    return { texte: echantillon, enregistre: true };' '  }' '}' 'module.exports = { demander };' > model.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, partie 1

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function envoyer(m) { process.stdout.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, m)) + \"\n\"); }' 'const SCHEMA = { type: \"object\", properties: { a: { type: \"integer\" }, b: { type: \"integer\" } }, required: [\"a\", \"b\"] };' 'function mots(texte, mauvais) { return { content: [{ type: \"text\", text: texte }], isError: mauvais }; }' > serveur6.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, partie 2

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function traiter(msg) {' '  if (msg.id === undefined) return;' '  const p = msg.params || {};' '  const args = p.arguments || {};' '  if (msg.method === \"initialize\") return envoyer({ id: msg.id, result: { protocolVersion: p.protocolVersion, capabilities: { tools: {} }, serverInfo: { name: \"additionneur\", version: \"1.0\" } } });' >> serveur6.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, partie 3

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  if (msg.method === \"tools/list\") return envoyer({ id: msg.id, result: { tools: [{ name: \"additionner\", description: \"Additionner deux entiers\", inputSchema: SCHEMA }] } });' '  if (msg.method === \"tools/call\" && p.name === \"additionner\") {' '    if (Number.isInteger(args.a) && Number.isInteger(args.b)) return envoyer({ id: msg.id, result: mots(String(args.a + args.b), false) });' '    return envoyer({ id: msg.id, result: mots(\"a et b doivent être des entiers\", true) });' '  }' '  if (msg.method === \"tools/call\") return envoyer({ id: msg.id, error: { code: -32602, message: \"Outil inconnu : \" + p.name } });' '  envoyer({ id: msg.id, error: { code: -32601, message: \"Méthode inconnue : \" + msg.method } });' '}' 'require(\"readline\").createInterface({ input: process.stdin }).on(\"line\", function (ligne) { traiter(JSON.parse(ligne)); });' >> serveur6.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 vérificateur, partie 1

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const serveur = require(\"child_process\").spawn(\"node\", [process.argv[2] || \"serveur6.js\"]);' 'const TOUT = \"<tout>\";' 'const un = function (id, method, params) { return { jsonrpc: \"2.0\", id: id, method: method, params: params }; };' 'const somme = function (id, args) { return un(id, \"tools/call\", { name: \"additionner\", arguments: args }); };' 'const ok = function (id, result) { return { jsonrpc: \"2.0\", id: id, result: result }; };' 'const texte = function (t, mauvais) { return { content: [{ type: \"text\", text: t }], isError: mauvais }; };' > verif6.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 vérificateur, partie 2

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const etapes = [' '  [un(1, \"initialize\", { protocolVersion: \"2025-03-26\" }), ok(1, { protocolVersion: \"2025-03-26\", capabilities: { tools: {} }, serverInfo: { name: \"additionneur\", version: TOUT } })],' '  [{ jsonrpc: \"2.0\", method: \"notifications/initialized\" }, null],' '  [un(2, \"tools/list\"), ok(2, { tools: [{ name: \"additionner\", description: TOUT, inputSchema: TOUT }] })],' '  [somme(3, { a: 2, b: 3 }), ok(3, texte(\"5\", false))],' '  [somme(4, { a: \"2\", b: 3 }), ok(4, texte(\"a et b doivent être des entiers\", true))],' '  [un(5, \"tools/call\", { name: \"rien\" }), { jsonrpc: \"2.0\", id: 5, error: { code: -32602, message: \"Outil inconnu : rien\" } }],' '];' >> verif6.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 vérificateur, partie 3

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function estObj(x) { return Boolean(x) && typeof x === \"object\"; }' 'function nom(chemin, k, liste) { return liste ? chemin + \"[\" + k + \"]\" : (chemin ? chemin + \".\" + k : k); }' 'function ecart(voulu, recu, chemin) {' '  if (voulu === TOUT) return null;' '  const deux = estObj(voulu) && estObj(recu) && Array.isArray(voulu) === Array.isArray(recu);' '  const dit = \" : attendu \" + JSON.stringify(voulu) + \", reçu \" + JSON.stringify(recu);' '  if (deux === false) return JSON.stringify(voulu) === JSON.stringify(recu) ? null : (chemin || \"(racine)\") + dit;' >> verif6.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 vérificateur, partie 4

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  const liste = Array.isArray(voulu);' '  if (liste && (voulu.length === recu.length) === false) return (chemin || \"(racine)\") + \" : attendu \" + voulu.length + \" éléments, reçu \" + recu.length;' '  const cles = Object.keys(voulu).sort();' '  for (const k of cles) {' '    if ((k in recu) === false) return nom(chemin, k, liste) + \" : absent\";' '    const d = ecart(voulu[k], recu[k], nom(chemin, k, liste));' '    if (d) return d;' '  }' '  const en_trop = Object.keys(recu).sort().filter(function (k) { return cles.indexOf(k) < 0; });' '  return en_trop.length > 0 ? nom(chemin, en_trop[0], liste) + \" : en trop\" : null;' '}' >> verif6.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 vérificateur, partie 5

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const lignes = [];' 'const attente = [];' 'require(\"readline\").createInterface({ input: serveur.stdout }).on(\"line\", function (l) {' '  if (attente.length > 0) attente.shift()(JSON.parse(l)); else lignes.push(JSON.parse(l));' '});' 'function suivante() { return lignes.length > 0 ? Promise.resolve(lignes.shift()) : new Promise(function (r) { attente.push(r); }); }' >> verif6.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 vérificateur, partie 6

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  let reussis = 0, total = 0, premier = null;' '  for (let i = 0; i < etapes.length; i++) {' '    serveur.stdin.write(JSON.stringify(etapes[i][0]) + \"\n\");' '    if (etapes[i][1] === null) continue;' '    total += 1;' '    const d = ecart(etapes[i][1], await suivante(), \"\");' '    console.log((d ? \"ÉCHEC étape \" + (i + 1) + \" à \" + d : \"RÉUSSI étape \" + (i + 1)));' '    if (d === null) reussis += 1; else if (premier === null) premier = d;' '  }' >> verif6.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 vérificateur, partie 7

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  serveur.stdin.end();' '  console.log(\"réussis \" + reussis + \" sur \" + total);' '  if (premier === null) return;' '  const q = \"Un test de mon serveur a échoué avec cet écart. En une phrase, quelle est la cause probable ? \" + premier;' '  const r = await demander([{ role: \"user\", content: q }], \"Le serveur a donné un autre texte que celui que le test attendait.\", 60);' '  console.log(\"le modèle dit : \" + r.texte);' '}' 'main();' >> verif6.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 vérificateur sur le bon serveur

Commande
docker exec -w /home/node/project agent node verif6.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.

Faire une copie avec une mauvaise modification

Commande
docker exec -w /home/node/project agent sh -c "sed 's/args.a + /args.a - /' serveur6.js > mauvais6.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 vérificateur sur la mauvaise copie

Commande
docker exec -w /home/node/project agent node verif6.js mauvais6.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.

Les deux Runs côte à côte

RunÉtapes vérifiéesRésultat
Bon serveur1, 3, 4, 5 et 6réussis 5 sur 5
Mauvaise copie1, 3, 4, 5 et 6réussis 4 sur 5
Mauvaise copie, étape 4additionner 2 et 3result.content[0].text : attendu "5", reçu "-1"

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

À 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.