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

Qui peut appeler quel outil

Après cette leçon: Je peux limiter les outils d’un serveur selon celui qui appelle.

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

Un outil n’est pas pour tout le monde

Un outil qui annule toutes les commandes ne doit pas être offert à tout appelant. Un serveur lancé sur une machine n’a pas de page de connexion. C’est celui qui le lance qui dit ce que l’appelant a le droit de faire.

Ici, vous écrivez ce contrôle, et le journal qui garde la trace de chaque appel.

À la fin de cette leçon

Remarque

Je peux limiter les outils d’un serveur selon celui qui appelle.

Mots clés

MotCe que ça veut direExemple
Droitune permission écrite comme un nommenu:lire
Jokerun droit qui en couvre plusieurscommandes:*
Journalla trace écrite de chaque appel et de ce qu’il a reçu
Refuserdire non parce que le droit manque
Moindre privilègene donner que les droits dont on a besoin

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

Un droit par outil

Chaque outil exige un droit. L’appelant arrive avec une liste de droits. Si le droit de l’outil est dans la liste, l’appel passe. Sinon, le serveur refuse.

Un joker comme commandes: couvre toute une famille : commandes:ecrire et commandes:admin, et rien d’autre. Le droit tout seul couvre tout.

Exemple : trois outils, deux droits donnés

OutilDroit exigéAvec menu:lire et commandes:ecrire
lire_menumenu:lireautorisé
passer_commandecommandes:ecrireautorisé
tout_annulercommandes:adminrefusé
bruler_cuisine(aucun : il n’existe pas)inconnu

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

Cacher ne suffit pas : il faut aussi refuser

Le serveur de cette leçon ne montre dans tools/list que les outils permis. C’est une aide, pas une protection.

Un appel peut nommer un outil qu’on ne lui a jamais montré. Le serveur contrôle donc le droit à chaque appel. Un droit absent donne une erreur d’outil que le modèle peut lire. Un outil qui n’existe pas donne une erreur de protocole.

Une erreur courante

Attention

Beaucoup croient qu’un outil absent de la liste est un outil protégé. Un appel n’a pas besoin de la liste pour nommer un outil. Le contrôle se fait à chaque appel, et le journal garde la trace des refus comme des succès.

É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' 'const Exigez = { lire_menu: \"menu:lire\", passer_commande: \"commandes:ecrire\", tout_annuler: \"commandes:admin\" };' 'const DROITS = (process.env.DROITS || \"\").split(\",\");' 'let compte = 0;' 'function envoyer(m) { process.stdout.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, m)) + \"\n\"); }' 'function mots(texte, mauvais) { return { content: [{ type: \"text\", text: texte }], isError: mauvais }; }' 'function noter(outil, decision) {' '  compte += 1;' '  process.stderr.write(JSON.stringify({ n: compte, outil: outil, decision: decision, exige: Exigez[outil] || null }) + \"\n\");' '}' > serveur5.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 appeler(msg, p) {' '  if (Object.keys(Exigez).indexOf(p.name) < 0) {' '    noter(p.name, \"inconnu\");' '    return envoyer({ id: msg.id, error: { code: -32602, message: \"Outil inconnu : \" + p.name } });' '  }' '  if (DROITS.indexOf(Exigez[p.name]) < 0) {' '    noter(p.name, \"refuse\");' '    return envoyer({ id: msg.id, result: mots(\"Refusé : \" + p.name + \" exige \" + Exigez[p.name], true) });' '  }' '  noter(p.name, \"autorise\");' '  envoyer({ id: msg.id, result: mots(\"fait : \" + p.name, false) });' '}' >> serveur5.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' 'function visibles() {' '  return Object.keys(Exigez).filter(function (n) { return DROITS.indexOf(Exigez[n]) >= 0; }).map(function (n) {' '    return { name: n, description: \"Outil de cantine \" + n, inputSchema: { type: \"object\" } };' '  });' '}' >> serveur5.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 4

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 || {};' '  if (msg.method === \"initialize\") return envoyer({ id: msg.id, result: { protocolVersion: p.protocolVersion, capabilities: { tools: {} }, serverInfo: { name: \"cantine\", version: \"0.5\" } } });' '  if (msg.method === \"tools/list\") return envoyer({ id: msg.id, result: { tools: visibles() } });' '  if (msg.method === \"tools/call\") return appeler(msg, p);' '  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)); });' >> serveur5.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 { demander } = require(\"./model\");' 'const env = Object.assign({}, process.env, { DROITS: \"menu:lire,commandes:ecrire\" });' 'const serveur = require(\"child_process\").spawn(\"node\", [\"serveur5.js\"], { env: env });' 'const appel = function (id, nom) { return { id: id, method: \"tools/call\", params: { name: nom, arguments: {} } }; };' 'const scenario = [' '  { id: 1, method: \"initialize\", params: { protocolVersion: \"2025-03-26\" } },' '  { id: 2, method: \"tools/list\" },' '  appel(3, \"lire_menu\"), appel(4, \"passer_commande\"), appel(5, \"tout_annuler\"), appel(6, \"bruler_cuisine\"),' '];' > client5.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' 'const vues = [];' 'const journal = [];' 'require(\"readline\").createInterface({ input: serveur.stderr }).on(\"line\", function (ligne) { journal.push(ligne); });' 'require(\"readline\").createInterface({ input: serveur.stdout }).on(\"line\", function (ligne) {' '  const m = JSON.parse(ligne);' '  const noms = m.result && m.result.tools ? \"outils : \" + m.result.tools.map(function (t) { return t.name; }).join(\", \") : null;' '  console.log(\"<- \" + (noms || (m.result && m.result.content ? m.result.content[0].text : JSON.stringify(m.error || m.result))));' '  vues.push(m);' '  if (vues.length === 6) serveur.stdin.end();' '});' 'scenario.forEach(function (m) { serveur.stdin.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, m)) + \"\n\"); });' >> client5.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 3

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'serveur.on(\"close\", async function () {' '  journal.forEach(function (l) { console.log(\"journal \" + l); });' '  const refus = vues[4].result.content[0].text;' '  const q = \"En une phrase courte, explique ce refus à un élève : \" + refus;' '  const r = await demander([{ role: \"user\", content: q }], \"Votre compte n’a pas le droit d’annuler toutes les commandes.\", 60);' '  console.log(\"le modèle dit : \" + r.texte);' '});' >> client5.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 client5.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.

Ce que le script fait

Le script lance le serveur avec deux droits : menu:lire et commandes:ecrire. Il liste les outils, puis appelle les trois outils et un quatrième qui n’existe pas.

À la fin, il écrit le journal que le serveur a tenu sur stderr, puis demande à un modèle d’expliquer un refus. Sans modèle, le Run affiche un Échantillon enregistré.

Ce que chaque requête reçoit

RequêteRéponse
2 tools/listDeux outils seulement : lire_menu et passer_commande
3 lire_menufait : lire_menu
4 passer_commandefait : passer_commande
5 tout_annulerRefusé : tout_annuler exige commandes:admin
6 bruler_cuisineErreur de protocole -32602, outil inconnu

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.