EXEMPLE : une entreprise fictive. Pas un client d'Adwaizer.
Serveur MCP sur mesure : dossier de remise pour Fernlight Supply Co. (exemple)
Préparé par : Adwaizer AI Consulting Inc. · Mandat : Agent sur mesure / serveur MCP, option A (un serveur MCP, jusqu'à 6 outils), USD 1,200 forfaitaire, 7 jours ouvrables · Version du dossier : exemple, octobre 2026
Ce qu'est cette page. Voici le dossier de remise que reçoit un client à la fin d'un mandat d'agent sur mesure / serveur MCP. Fernlight Supply Co., son système de commandes interne « Ledgerline », son personnel, son dépôt, ses hôtes, ses dossiers et tous les chiffres ci-dessous sont inventés pour montrer la forme et le niveau de détail ; rien de tout cela n'existe. Dans un vrai mandat, chaque ligne renvoie au dépôt du client, à son compte de test et à son journal d'exécution.
Comment lire les preuves. Une « preuve » est ce que le client peut ouvrir et vérifier : un fichier dans son dépôt, le nom d'un test, une commande et sa sortie, ou une ligne du journal d'exécution. Rien ici n'a été exécuté sur un système réel.
Le mandat en un paragraphe. Fernlight Supply Co. est un grossiste fictif de fournitures de jardinage et de pépinière, avec une petite équipe de bureau. Son personnel répond toute la journée à « est-ce en stock ? » et « où est ma commande ? » en cherchant dans Ledgerline, son système interne de commandes et d'inventaire doté d'une API REST. Fernlight a demandé un serveur MCP pour que Claude Code et Claude Desktop puissent consulter les produits, les stocks et les commandes, ajouter des notes internes à une commande et préparer des brouillons de réapprovisionnement qu'une personne approuve dans Ledgerline. Rien de ce que fait le serveur ne peut envoyer, payer ou supprimer.
1. Portée écrite (jour 1, approuvée par écrit avant la construction)
Approuvée par la personne-ressource désignée de Fernlight, la responsable des opérations (fictive), par courriel le jour 1. La construction n'a pas commencé avant ce courriel.
Système : API Ledgerline, compte de test seulement (https://ledgerline-test.fernlight.example/api), avec une clé de service que Fernlight a créée dans son propre compte et peut révoquer. Aucun accès à la production.
| Outil | Peut lire | Peut modifier | Ne doit jamais |
|---|---|---|---|
find_product | Nom du produit, SKU, unité, prix de liste, nom du fournisseur, note du fournisseur | Rien | Modifier un produit, un prix ou un fournisseur |
get_stock | Quantités en main, réservées et à recevoir par entrepôt pour un SKU | Rien | Ajuster le stock ou le déplacer d'un entrepôt à l'autre |
list_low_stock | Les SKU au seuil de réapprovisionnement ou en dessous, jusqu'à 50 par appel | Rien | Modifier un seuil de réapprovisionnement |
get_order | Statut, lignes, date d'expédition et note du client d'une commande | Rien | Modifier, annuler ou rembourser une commande ; renvoyer des données de paiement (le bloc payment de l'API est retiré par le serveur) |
add_order_note | La commande où il écrit | Ajoute une note interne à une commande (invisible pour le client), 1,000 caractères au maximum | Modifier ou supprimer une note existante ; écrire une note visible par le client ; envoyer un courriel à qui que ce soit |
draft_reorder | Le SKU et son fournisseur | Crée un brouillon de bon de commande dans Ledgerline, 3 lignes au maximum | Soumettre, approuver ou envoyer un bon de commande à un fournisseur ; payer quoi que ce soit |
Limites d'appels et de dépenses (appliquées dans le serveur, pas dans l'invite) :
- 30 appels d'outil par minute et 500 par jour par jeton ; le 501e appel de la journée est refusé avec
rate_limited. add_order_note: 50 par jour.draft_reorder: 10 par jour, au plus 3 lignes et USD 2,500 de valeur estimée par brouillon (quantité × prix de liste ; la limite d'achat propre à Fernlight) ; un brouillon plus important est refusé.- Chaque écriture exige une
idempotency_key; une nouvelle tentative avec la même clé renvoie le premier résultat et n'écrit rien de nouveau. - L'utilisation du modèle passe par la propre clé d'API Anthropic de Fernlight ; le journal d'exécution indique les jetons, les USD et les secondes de chaque exécution. Fernlight a fixé une limite de dépenses mensuelle sur cette clé dans sa propre console.
Points d'approbation. Deux éléments laissent une personne aux commandes :
- Un réapprovisionnement n'est jamais qu'un brouillon. Un acheteur de Fernlight l'ouvre dans Ledgerline, le modifie au besoin et le soumet au fournisseur à partir de là. Le serveur n'a aucun outil pour soumettre, approuver ou envoyer, et sa clé n'a pas la permission de le faire.
- Aucun outil n'envoie de message, ne paie, n'annule ni ne supprime. Toute action de ce genre reste une étape manuelle dans Ledgerline.
Texte externe. Les notes des fournisseurs et des clients sont écrites par des personnes extérieures à l'équipe de Fernlight ; le serveur ne les renvoie donc que sous untrusted_supplier_note et untrusted_customer_note, et les descriptions des outils se terminent par UNTRUSTED. Les outils reçoivent des SKU et des numéros de commande, jamais des URL à récupérer.
Portées (un jeton par usage) :
| Portée | Outils | Remise à |
|---|---|---|
catalog:read | find_product, get_stock, list_low_stock | Chaque jeton du personnel |
orders:read | get_order | Chaque jeton du personnel |
orders:note | add_order_note | Les jetons du service à la clientèle |
po:draft | draft_reorder | Le jeton de l'acheteur seulement |
Hors portée (selon l'offre) : un deuxième système, plus de 6 outils, une interface web, l'hébergement après la remise, le déploiement en production et toute action qui envoie, paie ou supprime sans une personne.
2. Les outils
Tirés de notre gabarit de départ testé (JSON-RPC sur HTTP, jeton porteur comparé en temps constant, outils limités par portée). Dépôt : fernlight-supply/ledgerline-mcp (fictif), livré dans l'organisation GitHub de Fernlight. Révisé dans la pull request #4 par une session de révision distincte, par rapport à la portée écrite.
| Outil | Portée | Entrée | Renvoie | Écrit ? | Limite |
|---|---|---|---|---|---|
find_product | catalog:read | query (SKU ou mots, 2-80 caractères) | Jusqu'à 10 produits : sku, name, unit, list_price_usd, supplier, untrusted_supplier_note | Non | Partagée 30/min, 500/jour |
get_stock | catalog:read | sku | Par entrepôt : on_hand, reserved, incoming, next_delivery_date | Non | Partagée |
list_low_stock | catalog:read | warehouse (facultatif), limit (1-50) | Les SKU au seuil de réapprovisionnement ou en dessous, avec les quantités | Non | Partagée |
get_order | orders:read | order_number (FS- et 6 chiffres) | status, lines, ship_date, untrusted_customer_note ; jamais de données de paiement | Non | Partagée |
add_order_note | orders:note | order_number, note (1-1,000 caractères), idempotency_key | note_id, created_at, replayed (vrai lors d'une nouvelle tentative) | Oui, note interne | 50/jour |
draft_reorder | po:draft | sku, lines (1-3 de sku + quantity), idempotency_key | draft_po_id, status: "draft", estimated_value_usd, lien vers le brouillon dans Ledgerline | Oui, brouillon seulement | 10/jour, USD 2,500 par brouillon |
Chaque schéma d'entrée a additionalProperties: false ; un argument que la portée ne nomme pas (par exemple status ou submit) est donc refusé, et non ignoré. Une entrée invalide revient comme une erreur d'outil (isError: true) avec un message clair, jamais comme un plantage.
Journal d'exécution. Chaque appel d'outil écrit une ligne dans logs/calls.jsonl (heure, libellé du jeton, outil, résultat, millisecondes ; jamais les arguments ni le texte des notes). L'exécuteur de cas du guide d'exploitation ajoute une ligne par exécution dans logs/runs.jsonl avec les appels d'outil effectués, les jetons d'entrée et de sortie, les USD et les secondes.
3. Résultats des tests (modèle simulé, aucune API payante appelée)
Commande : npm test dans le dépôt (l'exécuteur de tests intégré de Node). L'API Ledgerline est remplacée par une fausse API locale avec des enregistrements de test, et le modèle par une simulation scriptée ; la suite s'exécute donc hors ligne et sans frais.
Un test par outil, plus les vérifications propres au gabarit de départ :
| Outil | Test | Résultat |
|---|---|---|
find_product | "returns matching products and puts the supplier note under untrusted_supplier_note" | Réussi |
get_stock | "returns quantities per warehouse for a known SKU and a tool error for an unknown one" | Réussi |
list_low_stock | "lists only SKUs at or below the reorder point, capped at the limit" | Réussi |
get_order | "returns status and lines, drops the payment block, marks the customer note untrusted" | Réussi |
add_order_note | "writes one internal note; a retry with the same idempotency key writes nothing new" | Réussi |
draft_reorder | "creates a draft only; refuses a 4th line, a draft over USD 2,500 and an extra submit argument" | Réussi |
Résumé de la suite :
$ npm test
...
# tests 34
# suites 9
# pass 34
# fail 0
# cancelled 0
# skipped 0
# duration_ms 612
Les 34 tests : les 6 tests d'outils ci-dessus, 12 tests de portée (chaque outil listé et appelable avec sa portée, non listé et refusé sans elle), 7 tests d'authentification et de protocole issus du gabarit de départ, 5 tests de limites (limite de débit, plafonds quotidiens, plafond de valeur des brouillons) et 4 tests du journal d'exécution (aucun argument ni texte de note journalisé ; jetons, USD et secondes présents dans runs.jsonl).
Autres vérifications sur le commit fusionné :
| Vérification | Résultat |
|---|---|
Lint (npm run lint) | 0 erreur |
Analyse des secrets dans les commits (gitleaks) | Aucune fuite trouvée |
| Session de révision distincte, par rapport à la portée écrite | Approuvé ; chaque outil, limite et refus de la section 1 a un test |
4. Exécution sur le compte de test (jour 6)
Chaque outil exécuté une fois sur un exemple réel dans le compte de test Ledgerline de Fernlight, par Claude Code connecté avec la commande du guide d'exploitation. Les enregistrements sont des enregistrements de test créés par Fernlight ; les chiffres sont inventés.
| Exécution | Demande dans Claude Code | Appels d'outil | Ce qui s'est passé | Jetons (entrée / sortie) | USD | Secondes |
|---|---|---|---|---|---|---|
| 1 | « Avons-nous le compost sans tourbe de 25 L ? » | find_product | 2 correspondances ; SKU CMP-25-PF, note du fournisseur affichée et marquée non fiable | 4,180 / 210 | 0.02 | 4.1 |
| 2 | « Combien de CMP-25-PF dans chaque entrepôt ? » | get_stock | Nord : 140 en main, 32 réservés ; Sud : 0, 200 à recevoir | 3,960 / 180 | 0.01 | 3.4 |
| 3 | « Qu'est-ce qui est bas dans l'entrepôt Sud ? » | list_low_stock | 7 SKU au seuil de réapprovisionnement ou en dessous | 4,720 / 390 | 0.02 | 5.0 |
| 4 | « Où en est la commande FS-104233 ? » | get_order | Emballée, expédiée demain ; aucune donnée de paiement renvoyée | 4,050 / 240 | 0.02 | 3.8 |
| 5 | « Note sur FS-104233 : le client a demandé une livraison le matin » | add_order_note | Note interne ajoutée ; invisible sur la page de commande du client | 4,310 / 160 | 0.02 | 4.6 |
| 6 | « Prépare un réapprovisionnement de 300 CMP-25-PF pour Sud » | get_stock, draft_reorder | Brouillon de bon de commande DPO-0418 créé, statut brouillon, USD 1,740 estimé ; l'acheteur l'a vu dans la liste des brouillons de Ledgerline | 8,900 / 420 | 0.04 | 7.9 |
Action interdite, refus démontré. Demande dans Claude Code : « Soumets maintenant DPO-0418 au fournisseur. » Claude n'a aucun outil pour cela et le dit. Pour tester le serveur lui-même, le réviseur a ensuite appelé l'outil directement avec un argument que la portée interdit :
tools/call draft_reorder {"sku":"CMP-25-PF","lines":[{"sku":"CMP-25-PF","quantity":300}],
"idempotency_key":"day6-forbidden-1","submit":true}
→ isError: true
"Unknown argument: submit. This server creates draft purchase orders only;
a person submits them in Ledgerline."
Le même appel avec un jeton du service à la clientèle (sans la portée po:draft) a été refusé avec insufficient_scope, et draft_reorder n'apparaissait pas dans le tools/list de ce jeton. La liste des brouillons de Ledgerline ne montrait ni nouveau brouillon ni commande soumise après l'un ou l'autre appel.
Corrections issues de l'exécution. Une seule : get_stock renvoyait une liste vide pour un SKU tapé en minuscules. Le serveur met maintenant les SKU en majuscules avant la recherche, et un cas a été ajouté au test de get_stock. Rien d'imprévu par la portée n'est survenu.
Répétition de l'acceptation. Les cinq vérifications de la section 6 ont été exécutées sur le compte de test par notre session de révision le même jour : les cinq ont réussi.
5. Guide d'exploitation (une page)
Ce que c'est. Un serveur MCP qui permet à Claude Code ou à Claude Desktop de consulter les produits, les stocks et les commandes de Ledgerline, d'ajouter des notes internes aux commandes et de préparer des brouillons de réapprovisionnement. Il ne peut pas envoyer, payer, annuler ni supprimer.
Le lancer (Node 20 ou plus récent, sur l'hôte que choisit Fernlight ; l'hébergement relève de Fernlight) :
git clone [email protected]:fernlight-supply/ledgerline-mcp.git && cd ledgerline-mcp
npm ci
cp .env.example .env # set LEDGERLINE_API_URL, LEDGERLINE_API_KEY, MCP_TOKENS_FILE
node --env-file=.env src/index.js # serves POST /mcp and GET /health on :8787
Les secrets résident dans l'environnement de l'hôte ou dans le coffre à secrets de Fernlight, jamais dans le dépôt. MCP_TOKENS_FILE liste un jeton par personne avec son libellé et ses portées (section 1).
Connecter Claude Code :
claude mcp add --transport http ledgerline https://mcp.fernlight.example/mcp \
--header "Authorization: Bearer $LEDGERLINE_MCP_TOKEN"
Exécutez ensuite claude mcp list : ledgerline apparaît comme connecté, et /mcp dans Claude Code liste les outils que permettent les portées de votre jeton. Claude Desktop : ajoutez la même URL et le même en-tête comme connecteur personnalisé.
Exécuter un cas avec le journal : npm run case -- "Where is order FS-104233?" exécute une demande avec votre clé d'API Anthropic (ANTHROPIC_API_KEY) et ajoute les appels d'outil, les jetons, les USD et les secondes dans logs/runs.jsonl.
Entretien courant :
- Ajouter une personne : ajoutez une ligne de jeton avec ses portées ; redémarrez le serveur.
- Retirer une personne ou renouveler un jeton : supprimez ou modifiez sa ligne ; redémarrez.
- Tout révoquer : révoquez la clé de service dans les paramètres de Ledgerline ; chaque outil renvoie alors une erreur.
- Modifier une limite : modifiez
config/limits.json(par minute, par jour, plafond de valeur des brouillons), exécuteznpm test, redémarrez. - Vérification de l'état : pointez la surveillance de l'hôte vers
GET /health.
Si quelque chose semble anormal : lisez logs/calls.jsonl pour l'outil et le résultat, puis la ligne du cas dans logs/runs.jsonl. Une vérification d'acceptation qui échoue est corrigée dans les 5 jours ouvrables, sans frais.
6. Liste de vérification d'acceptation (Fernlight l'exécute le jour de la remise)
Sur le compte de test Ledgerline, avec le guide d'exploitation :
- La suite de tests réussit sans appel à une API payante (
npm test: 34 pass, 0 fail). - Chacun des 6 outils à portée limitée s'exécute une fois sur un exemple réel et fait ce que la section 1 indique.
- Une action interdite est refusée (par exemple : demandez à Claude de soumettre un brouillon de bon de commande, puis appelez
draft_reorderavec"submit": true; les deux sont refusés, aucune commande soumise dans Ledgerline). - La ligne de chaque exécution dans
logs/runs.jsonlindique les appels d'outil et les jetons, USD et secondes. - Le serveur se connecte dans Claude Code ou Claude Desktop avec la commande
claude mcp addci-dessus et liste ses outils.
Une vérification qui échoue est corrigée dans les 5 jours ouvrables, sans frais. L'acceptation est écrite (un courriel suffit) ; sans réponse dans les 5 jours ouvrables suivant la remise et si rien n'a échoué, la construction est acceptée. La deuxième facture (50%, USD 600) est émise à l'acceptation.
7. Texte de remise du jalon Upwork
Publié avec le travail remis pour le deuxième jalon (jour 7) :
Jalon 2 : remise du serveur MCP Ledgerline (USD 600)
Le serveur MCP est dans votre dépôt
fernlight-supply/ledgerline-mcp(pull request #4, fusionnée par vous). Il comporte les 6 outils de la portée écrite que vous avez approuvée le jour 1, avec les limites d'appels et de dépenses et les deux points d'approbation dans le code du serveur.Pièce jointe : le dossier de remise (portée écrite, liste des outils avec leurs limites, résultats des tests, l'exécution du jour 6 sur le compte de test, le guide d'exploitation et la liste de vérification d'acceptation).
- Tests : 34 réussis, 0 échec, avec le modèle simulé ; aucune API payante n'est appelée.
- Exécution sur le compte de test : chaque outil exécuté une fois ; la demande de soumettre un bon de commande a été refusée, et aucune commande n'a été soumise.
- Chaque exécution journalise ses appels d'outil, ses jetons, ses USD et ses secondes.
Veuillez exécuter les 5 vérifications de la section 6 du dossier sur votre compte de test. Si une vérification échoue, nous la corrigeons dans les 5 jours ouvrables, sans frais. Lorsqu'elles réussissent, approuvez ce jalon. L'appel de présentation est fixé au moment convenu.
Exemple préparé à partir de notre trousse de livraison et de notre fiche d'offre pour l'agent sur mesure / serveur MCP. L'entreprise, le système, les personnes, le dépôt, les hôtes et les chiffres sont fictifs.