Files
mcp-wms-api/docs/handoff-lot3.md
T
Arthur Ria 7b25e79e98 Passation lot 3 : bornage des sorties volumineuses (L3.1), D24 réservée
Mesures rafraîchies du 25/08/2026 via le protocole (LIMAGRAIN) :
get_system_parameters {} -> 70 141 caractères pour 168 paramètres,
search_logs Error avec les défauts -> 55 954 caractères pour 50
résultats. Trois blocs : pagination limit/offset, garde-fou de taille
cumulée, cohérence du signal truncated (D24 si contrat commun).
QueryExecuteStream explicitement hors périmètre (piste L4.5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:14:37 +02:00

176 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Passation — lot 3 (L3.1, bornage des sorties volumineuses)
Tu travailles sur `mcp-wms-api` : un serveur MCP (Node.js, CommonJS, stdio) qui
donne à Claude un accès en lecture à un WMS EasyWMS (Mecalux) via ses API REST.
Lis [../CLAUDE.md](../CLAUDE.md) et [../DECISIONS.md](../DECISIONS.md) avant de
toucher au code.
**Mission** : borner les sorties des outils qui produisent aujourd'hui des
réponses de 50 000 à 70 000 caractères, rejetées par les clients MCP —
`get_system_parameters` et `search_logs` — avec un signal `truncated: true`
explicite plutôt qu'un rejet silencieux côté client.
**Hors périmètre** : tout le reste de la roadmap (lot 4 en entier). En
particulier, **n'explore pas `QueryExecuteStream`** — c'est une piste long
terme consignée en L4.5, pas ce lot. Ne touche pas aux limites des outils de
requête (`MAX_QUERY_ROWS` fait déjà le travail). Ne pousse rien
(`git push` interdit), ne touche pas au `.env`, n'appelle jamais
`execute_command` (il écrit dans le WMS).
---
## Contexte matériel
- Profil de travail : `LIMAGRAIN` (par défaut), host `10.255.255.2`, tenant
`LIMAGRAI2512`, `saas=false` — les outils de logs y fonctionnent.
- **Le profil `AD` est cassé et c'est diagnostiqué — ne le réinvestigue pas**
(tenant introuvable côté STS, point ouvert de la ROADMAP). Conséquence :
`npm test -- --all` échoue sur AD ; la baseline se mesure avec `npm test`
(profil par défaut), attendu **4/4, code de sortie 0**.
- Baseline protocolaire à préserver : **23 outils**, **6 resources**, aucune
écriture sur stdout hors JSON-RPC.
Handshake + comptages :
```bash
printf '%s\n%s\n%s\n%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' '{"jsonrpc":"2.0","id":3,"method":"resources/list"}' | node src/index.js 2>/dev/null | node -e "let b='';process.stdin.on('data',d=>b+=d).on('end',()=>{for(const l of b.split('\n').filter(Boolean)){const m=JSON.parse(l);if(m.id===2)console.log('tools:',m.result.tools.length);if(m.id===3)console.log('resources:',m.result.resources.length);}});"
```
Mesurer la taille d'une réponse d'outil via le protocole (c'est la mesure qui
fait foi, pas une estimation) :
```bash
printf '%s\n%s\n%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"NOM","arguments":{}}}' | node src/index.js 2>/dev/null | node -e "let b='';process.stdin.on('data',d=>b+=d).on('end',()=>{for(const l of b.split('\n').filter(Boolean)){const m=JSON.parse(l);if(m.id===2)console.log('chars:',m.result.content[0].text.length);}});"
```
## Contraintes non négociables
1. `console.error()` uniquement — une écriture sur stdout casse Claude Desktop
(D6).
2. Un outil ne plante jamais le serveur : erreurs en réponse structurée
`{ success: false, error, tool }`, `isError: true` (wrapper de
`src/index.js`).
3. Messages d'erreur et hints actionnables : dire quoi faire ensuite.
4. Aucun accès base de données (D1).
5. **D23 : le wrapper `tools/call` valide les arguments contre les schémas.**
Tout paramètre que tu ajoutes (`limit`, `offset`…) doit être déclaré dans
l'`inputSchema` de l'outil, sinon le wrapper le rejettera comme inconnu.
C'est voulu — ne contourne pas la validation.
6. Les schémas portent `additionalProperties: false` — conserve-le.
**Numéro de décision réservé** : **D24** = contrat de troncature (si tu actes
un contrat commun — voir L3.1c). Vérifie que D23 est bien la dernière décision
avant d'écrire.
---
## Phase 0 — Confirmer les deux mesures
Mesures du 25/08/2026 (profil `LIMAGRAIN`, via le protocole) à **confirmer**
avec la commande de mesure ci-dessus, pas à réinvestiguer :
| Appel | Constaté |
|---|---|
| `get_system_parameters` `{}` | **70 141 caractères**, 168 paramètres, aucun `limit`/`offset` au schéma |
| `search_logs` `{"keyword":"Error"}` (défauts : `max_results` 50, `context_lines` 2) | **55 954 caractères**, 50 résultats |
Contexte : une sortie de ~70 000 caractères a déjà été **rejetée par le client
MCP** (constat du 24/08/2026 qui a motivé ce lot). L'ordre de grandeur cible
est ~20 00025 000 caractères par réponse ; c'est un ordre de grandeur, pas un
chiffre sacré — ce qui compte est le comportement (borné + signalé), mesuré via
le protocole.
---
## L3.1a — `get_system_parameters` : pagination
**Problème.** L'outil (`src/tools/config-tools.js`) renvoie les 168 paramètres
fusionnés d'un bloc : 70 141 caractères sans filtre. Les filtres existants
(`warehouse`, `param_class`, `search`, `only_overridden`) réduisent la sortie
mais rien ne borne le cas sans filtre.
**À faire.** Ajouter `limit` (défaut raisonnable, ~50 — à ce volume la réponse
tient vers 21 000 caractères) et `offset` (défaut 0), **déclarés au schéma**
(contrainte 5). La réponse annonce toujours `totalParameters` (le total avant
pagination), le nombre renvoyé, et — quand la pagination a tronqué —
`truncated: true` avec un hint indiquant comment continuer (`offset` suivant)
ou réduire (`param_class`, `search`).
**Vérification attendue** (protocole, LIMAGRAIN) :
- `{}` → taille < 25 000 caractères, 50 paramètres renvoyés,
`totalParameters: 168`, `truncated: true`, hint présent.
- `{"limit": 200}` → les 168, `truncated: false` (ou champ absent — mais alors
cohérent partout).
- `{"offset": 160}` → 8 paramètres, pas de `truncated`.
- `{"search": "CROSSDOCK"}` → comportement inchangé sur petit résultat.
- `{"lines": 5}` → toujours rejeté par le wrapper (D23 intact).
## L3.1b — `search_logs` : garde-fou de taille
**Problème.** `max_results` existe (défaut 50) mais ne borne pas le **volume** :
les `context_lines` multiplient la taille des résultats. Mesuré : 55 954
caractères avec les seuls défauts.
**À faire.** Un garde-fou sur la taille cumulée de la réponse construite
(`src/tools/log-tools.js` / `src/services/log-service.js`) : au-delà du
plafond, couper la liste des résultats — des résultats **entiers**, ne coupe
pas un résultat au milieu de ses lignes de contexte — et poser
`truncated: true` + un compte des résultats retenus/écartés + un hint
(réduire `context_lines`, affiner `keyword`, baisser `max_results`).
Plafond : constante ou variable d'environnement avec défaut (cohérent avec le
style `MAX_QUERY_ROWS`/`QUERY_TIMEOUT` dans le code existant) — documente le
choix dans le commit.
**Pente naturelle interdite** : ne réduis pas silencieusement les défauts
(`max_results` 50, `context_lines` 2 restent tels quels) — le correctif est le
bornage signalé, pas un changement de comportement par défaut qui casserait
les usages existants.
**Vérification attendue** (protocole, LIMAGRAIN) :
- `{"keyword":"Error"}` → taille sous le plafond choisi, `truncated: true`,
compte écarté + hint présents.
- `{"keyword":"Error","max_results":3}` → petit, pas de troncature signalée.
- Un mot-clé sans occurrence → comportement inchangé (0 résultat, pas de
`truncated`).
## L3.1c — Cohérence du signal `truncated`
Si tu factorises un helper de troncature commun aux deux outils, actes le
contrat en **D24** dans DECISIONS.md (forme du signal : `truncated: true`,
compte total vs renvoyé, hint actionnable). Si les deux implémentations restent
locales et divergentes, harmonise au moins les noms de champs — deux
vocabulaires pour le même concept est exactement le genre de dérive qu'on
traque en révision. `read_recent_logs` peut bénéficier du même helper si c'est
gratuit ; ne le complexifie pas pour ça.
---
## Méthode
1. Phase 0 d'abord (deux mesures, rejouées telles quelles).
2. L3.1a puis L3.1b puis L3.1c. Chaque correctif vérifié **en exécution via le
protocole** avant de passer au suivant — la taille en caractères de
`content[0].text` est la mesure qui fait foi.
3. Les schémas changent (nouveaux paramètres) : reboucler sur les 23 noms de
`tools/list` et vérifier qu'aucun ne répond `Unknown tool` (n'appelle pas
`execute_command` — vérifie sa branche statiquement), et vérifier qu'un
paramètre inconnu est toujours rejeté (D23).
4. Baseline avant/après : handshake (23/6) + `npm test` (4/4, exit 0).
5. « Non résolu » est une réponse acceptable pour une investigation time-boxée ;
une hypothèse présentée comme solution ne l'est pas.
## Livraison
- Un commit par correctif (L3.1a, L3.1b, L3.1c si D24), messages expliquant le
pourquoi, **mesures avant/après dans le corps du message** (tailles en
caractères, rejouées via le protocole).
- Documentation dans les mêmes commits : D24 si actée ; ROADMAP.md — retirer
L3.1 (le lot 3 devient vide : retire la section) ; CLAUDE.md ne change que si
tu ajoutes une variable d'environnement (la documenter dans la section des
réglages partagés).
- Ne pousse pas. `.env` intact. Toute anomalie hors périmètre découverte en
route : dans ROADMAP.md, pas dans le code.
- Compte-rendu final : pour chaque correctif, la vérification attendue rejouée
et son résultat **mesuré** (colle les tailles et les sorties), plus la
baseline finale.