90b2c89ff1
Deux mesures nouvelles du 25/08/2026 aggravent le dossier : une requête Reading ordinaire query_wms_entities(Products, limit 200) pèse 957 234 caractères et search_wms_data(PAL) 847 543 — le dépassement du seuil de rejet client (~70 000, D24) ne se limite donc pas au Writing ni aux workflows. Les points ouverts correspondants deviennent un lot 5 planifié dans la ROADMAP (L5.1 fenêtre verbatim sur le blob data, L5.2 garde de taille commune aux trois outils de requête avec le cas limite d'une ligne seule au-dessus du plafond, L5.3 balayage du champ tool). Pas de nouveau numéro de décision : le lot étend D24. D27 réservable si besoin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
213 lines
12 KiB
Markdown
213 lines
12 KiB
Markdown
# Passation — lot 5 (clore la famille D24)
|
|
|
|
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) —
|
|
en particulier **D24**, le contrat de troncature que ce lot généralise — avant
|
|
de toucher au code.
|
|
|
|
**Mission** : éliminer les derniers cas connus de réponses d'outils dépassant
|
|
le seuil de rejet des clients MCP (~70 000 caractères, D24) — lot 5 de
|
|
[../ROADMAP.md](../ROADMAP.md) : pagination du blob `data` de
|
|
`get_workflow_details` (L5.1), garde de taille sur les trois outils de requête
|
|
(L5.2), champ `tool` manquant dans les enveloppes d'erreur locales (L5.3).
|
|
|
|
**Hors périmètre** : tout le reste de la roadmap (L4.3, L4.4, L4.5,
|
|
exploration Metrics). **`QueryExecuteStream` est explicitement interdit** —
|
|
piste long terme consignée en L4.5, pas ce lot. Aucun nouvel outil : le compte
|
|
reste à 23. 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`.
|
|
- **Le profil `AD` est cassé et c'est diagnostiqué — ne le réinvestigue pas**
|
|
(tenant introuvable côté STS, point ouvert de la ROADMAP). 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 (la mesure qui fait foi est la
|
|
longueur de `content[0].text` via le protocole) :
|
|
|
|
```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 (D6).
|
|
2. Contrat d'erreur : `{ success: false, error, tool }`, `isError: true` —
|
|
c'est précisément l'objet de L5.3.
|
|
3. Messages et hints actionnables.
|
|
4. Aucun accès base de données (D1).
|
|
5. **D23** : le wrapper valide les **noms** de paramètres et les requis contre
|
|
les schémas — tout paramètre ajouté (fenêtre de L5.1…) doit être déclaré
|
|
dans l'`inputSchema`. Le wrapper ne valide pas les **valeurs** : les gardes
|
|
de valeur vivent dans le code de l'outil.
|
|
6. **D24** : signal commun — `truncated: true` **uniquement** quand la réponse
|
|
est coupée, `hint` actionnable, `returned` vs total avant coupe. `omitted`
|
|
en plus quand des éléments entiers sont écartés. Réutilise ce vocabulaire
|
|
exactement ; ne crée pas un second dialecte.
|
|
7. **Pas de nouveau numéro de décision attendu** : ce lot **étend D24**
|
|
(complète son texte : les outils de requête et `get_workflow_details`
|
|
entrent dans son périmètre). Si une décision réellement nouvelle s'impose,
|
|
vérifie le dernier numéro (D26 à ce jour) et réserve D27 dans le commit qui
|
|
l'acte.
|
|
|
|
---
|
|
|
|
## Phase 0 — Confirmer les mesures
|
|
|
|
Toutes rejouées le 25/08/2026 (protocole, LIMAGRAIN) avec la commande de
|
|
mesure ci-dessus. À **confirmer**, pas à réinvestiguer :
|
|
|
|
| Appel | Taille constatée |
|
|
|---|---:|
|
|
| `query_wms_entities` `{"entity_type":"Products","limit":200}` (Reading) | **957 234** |
|
|
| `search_wms_data` `{"keyword":"PAL"}` | **847 543** |
|
|
| `call_query_api` `{"entity_type":"Products","query_type":1,"limit":1}` — une seule ligne Writing | **95 288** |
|
|
| `get_workflow_details` sur `CST_SendRejectContainersToPK` (`application: "CustomApp"`) | ~101 800 |
|
|
| `get_workflow_details` `{"workflow_id":"2a320000-0642-47df-aa52-3b89c27c016f"}` (StackerCrane, EasyWMS) | 79 092 (dont blob `data` : 71 512) |
|
|
|
|
Attention aux noms de paramètres (D23 les fait respecter) : `search_workflows`
|
|
prend `query`, `search_wms_data` et `search_logs` prennent `keyword`. Pour
|
|
trouver l'id du workflow CST :
|
|
`search_workflows {"query":"CST_SendRejectContainersToPK","application":"CustomApp"}`.
|
|
|
|
---
|
|
|
|
## L5.1 — Paginer le blob `data` de `get_workflow_details`
|
|
|
|
**Problème.** La réponse embarque la définition complète du workflow (blob
|
|
`data`, XML/JSON EasyBuilder) : 71 512 caractères sur le StackerCrane mesuré,
|
|
davantage sur les gros `CST_*` — la réponse dépasse le seuil client.
|
|
|
|
**À faire.**
|
|
- Deux paramètres de fenêtre sur le blob `data` (au schéma, D23) : une taille
|
|
max de tranche (défaut de l'ordre de **20 000** caractères, cohérent avec
|
|
D24) et un offset (défaut 0). Nommage à ta main (`max_data_chars` /
|
|
`data_offset` ou équivalent), documenté dans les descriptions.
|
|
- La réponse porte toujours la taille **totale** du blob ; quand la fenêtre
|
|
tronque : `truncated: true` + `hint` donnant l'offset suivant.
|
|
- La tranche est **verbatim** : découpe de chaîne, rien d'autre.
|
|
- Les métadonnées du workflow (`id`, `name`, `commonInfo`…) restent complètes
|
|
dans chaque réponse ; seule `data` est fenêtrée.
|
|
|
|
**Pente naturelle interdite** : ne résume pas, ne reformule pas, ne « parse »
|
|
pas le blob pour n'en renvoyer que des morceaux jugés utiles — la définition
|
|
EasyBuilder doit rester reconstituable à l'octet près en concaténant les
|
|
tranches.
|
|
|
|
**Vérification attendue** (protocole, LIMAGRAIN) :
|
|
- `get_workflow_details` sur le StackerCrane (`2a320000-0642-47df-aa52-3b89c27c016f`)
|
|
sans paramètre de fenêtre → réponse < ~30 000 caractères, `truncated: true`,
|
|
taille totale annoncée = 71 512, hint avec l'offset suivant.
|
|
- En enchaînant les tranches (offset 0, 20 000, 40 000, 60 000) : la somme des
|
|
longueurs des tranches = 71 512, et la concaténation est identique au blob
|
|
d'origine (compare au moins les longueurs et les 100 premiers/derniers
|
|
caractères).
|
|
- Un workflow à petit blob (< défaut) → réponse strictement inchangée, pas de
|
|
`truncated`.
|
|
|
|
## L5.2 — Garde de taille sur les outils de requête
|
|
|
|
**Problème.** Aucune borne de volume sur `query_wms_entities`, `call_query_api`
|
|
et `search_wms_data` : 957 234 caractères pour 200 lignes Reading, 847 543
|
|
pour une recherche, et **95 288 pour une seule ligne Writing** (le modèle
|
|
Writing sérialise l'agrégat complet — navigations, `$id`… — là où la même
|
|
ligne Reading fait ~4 500).
|
|
|
|
**À faire.**
|
|
- Garde de taille commune aux trois outils : plafond en variable
|
|
d'environnement avec défaut (style `MAX_LOG_SEARCH_CHARS`, 25 000 — même
|
|
ordre de grandeur, nom à ta main, documenté dans CLAUDE.md).
|
|
- Au-delà du plafond : écarter des **lignes entières** (pour `search_wms_data` :
|
|
des résultats entiers, par entité), et signaler D24 : `truncated: true`,
|
|
`returned`, `omitted`, `hint` (réduire `limit`, ajouter un `filter` ; et pour
|
|
`query_type != 0` : rappeler que les lignes Writing sont des agrégats
|
|
complets et suggérer Reading si l'usage le permet).
|
|
- **Cas limite à traiter explicitement** : une seule ligne dépasse le plafond
|
|
(réel en Writing). La réponse est alors `returned: 0`, `omitted: <n>`,
|
|
`truncated: true`, avec un hint qui explique pourquoi et quoi faire — c'est
|
|
moins bon qu'un résultat, mais c'est mieux qu'un rejet client opaque.
|
|
- **Ne change ni `MAX_QUERY_ROWS`, ni les limites par défaut des outils, ni le
|
|
comportement sous le plafond** : une requête qui tient aujourd'hui doit
|
|
renvoyer exactement la même réponse.
|
|
- L'avertissement de volume mérite une phrase dans la description du paramètre
|
|
`query_type` (D25).
|
|
|
|
**Vérification attendue** (protocole, LIMAGRAIN) :
|
|
- `query_wms_entities` `{"entity_type":"Products","limit":200}` → réponse sous
|
|
le plafond (+ marge d'enveloppe), `truncated: true`, `returned` < 200,
|
|
`omitted` cohérent, hint présent.
|
|
- `search_wms_data` `{"keyword":"PAL"}` → borné et signalé de même.
|
|
- `call_query_api` `{"entity_type":"Products","query_type":1,"limit":1}` →
|
|
`returned: 0`, `omitted: 1`, `truncated: true`, hint expliquant le volume
|
|
Writing.
|
|
- `query_wms_entities` `{"entity_type":"Container","limit":1}` → réponse
|
|
**strictement identique** à aujourd'hui (~4 500 caractères, pas de
|
|
`truncated`).
|
|
- `count_wms_entities` → non concerné, inchangé.
|
|
|
|
## L5.3 — Champ `tool` dans les enveloppes d'erreur locales
|
|
|
|
**Problème.** Les `catch` locaux d'`api-tools.js` (deux blocs, vers les lignes
|
|
145-162 et 191-208) renvoient `{ success: false, error }` **sans** le champ
|
|
`tool` du contrat. Preuve : `call_query_api` `{"entity_type":"Products","query_type":7}`
|
|
répond aujourd'hui `{"success": false, "error": "query_type invalide : …"}` —
|
|
pas de `tool`.
|
|
|
|
**À faire.** Balayer **tous** les modules de `src/tools/` à la recherche
|
|
d'enveloppes `success: false` construites localement : soit y ajouter `tool`,
|
|
soit laisser l'erreur remonter au wrapper de `src/index.js` (qui l'ajoute) —
|
|
au choix selon le cas, mais le résultat observable est uniforme. Attention à ne
|
|
pas perdre les champs additionnels utiles des enveloppes locales (le `warning`
|
|
de résolution d'`api-tools`, les listes de profils de `profile-tools`…).
|
|
|
|
**Vérification attendue** : `call_query_api` avec `query_type: 7` → l'enveloppe
|
|
contient `"tool": "call_query_api"` ; un `grep` sur `src/tools/` ne montre plus
|
|
d'enveloppe `success: false` sans `tool` (colle le résultat du grep dans le
|
|
compte-rendu).
|
|
|
|
---
|
|
|
|
## Méthode
|
|
|
|
1. Phase 0 d'abord (cinq mesures, rejouées telles quelles).
|
|
2. L5.1, puis L5.2, puis L5.3 — chaque bloc vérifié **en exécution via le
|
|
protocole** avant de passer au suivant.
|
|
3. Rappel : le serveur traite les `tools/call` en concurrence (point ouvert de
|
|
la ROADMAP) — pour les vérifications qui comparent des réponses successives,
|
|
envoie les requêtes séquentiellement.
|
|
4. Les schémas changent : reboucler sur les 23 noms de `tools/list` (aucun
|
|
`Unknown tool` ; `execute_command` vérifié statiquement, non appelé) et
|
|
vérifier qu'un paramètre inconnu est toujours rejeté (D23).
|
|
5. Baseline avant/après : handshake (23/6) + `npm test` (4/4, exit 0).
|
|
6. « 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 bloc (L5.1, L5.2, L5.3), messages expliquant le pourquoi,
|
|
**mesures avant/après dans le corps du message** (tailles en caractères).
|
|
- Documentation dans les mêmes commits : **compléter D24** (périmètre étendu
|
|
aux outils de requête et à `get_workflow_details`) ; CLAUDE.md — la nouvelle
|
|
variable d'environnement dans les réglages partagés, la fenêtre de
|
|
`get_workflow_details` si elle change l'usage documenté ; ROADMAP.md —
|
|
retirer le lot 5.
|
|
- 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 bloc, la vérification attendue rejouée et
|
|
son résultat **mesuré** (colle les tailles et les sorties), plus la baseline
|
|
finale. Laisse `handoff-lot5.md` en place pour la révision.
|