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>
This commit is contained in:
@@ -0,0 +1,175 @@
|
|||||||
|
# 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 000–25 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.
|
||||||
Reference in New Issue
Block a user