From c4d6b5650e41759d06e7b798b11ce67f762f24aa Mon Sep 17 00:00:00 2001 From: Arthur Ria Date: Tue, 25 Aug 2026 10:34:23 +0200 Subject: [PATCH] L3.1a : pagine get_system_parameters (limit/offset, signal truncated) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La sortie sans filtre (168 paramètres fusionnés) atteignait 70 141 caractères et se faisait rejeter par le client MCP. Ajout de limit (défaut 50) et offset (défaut 0), déclarés au schéma (D23), appliqués après filtres et tri. Quand la pagination coupe, la réponse porte truncated: true et un hint donnant l'offset suivant et les filtres pour réduire. totalParameters change de sens : c'était le nombre brut d'entités Parameter chargées (toujours 168), c'est désormais le total correspondant aux filtres AVANT pagination — le signal truncated se vérifie ainsi depuis la réponse (offset + returned < totalParameters). Sans filtre les deux définitions coïncident. Mesures via le protocole (LIMAGRAIN, content[0].text) : - {} : avant 70 141 chars / 168 renvoyés ; après 21 404 chars, returned 50, totalParameters 168, truncated true, hint présent. - {"limit":200} : 70 156 chars, les 168, pas de truncated (opt-in explicite au volume complet). - {"offset":160} : 3 503 chars, 8 renvoyés, pas de truncated. - {"search":"PICK"} : 10 041 chars, 23/23, pas de truncated — comportement inchangé sur petit résultat. - {"lines":5} : toujours rejeté par le wrapper (D23 intact). Co-Authored-By: Claude Fable 5 --- src/tools/config-tools.js | 52 ++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/tools/config-tools.js b/src/tools/config-tools.js index 6187ea5..5a2955b 100644 --- a/src/tools/config-tools.js +++ b/src/tools/config-tools.js @@ -49,6 +49,16 @@ Examples: description: 'If true, return only parameters that have at least one warehouse override (default: false).', default: false, }, + limit: { + type: 'number', + description: 'Maximum number of parameters returned per call (default: 50). The full unfiltered list is ~70,000 characters — raise this only if you really need everything at once.', + default: 50, + }, + offset: { + type: 'number', + description: 'Number of matching parameters to skip, for pagination (default: 0). Combine with limit to walk the full list.', + default: 0, + }, }, }, }, @@ -64,9 +74,18 @@ async function executeTool(name, args) { } } +const DEFAULT_PARAMS_LIMIT = 50; + async function getSystemParameters(args) { const { warehouse, param_class, search, only_overridden = false } = args || {}; + // Bornes de pagination — valeurs invalides ramenées aux défauts, la + // validation du wrapper (D23) ne contrôle que les noms de paramètres. + const rawLimit = Number(args && args.limit); + const limit = Number.isFinite(rawLimit) && rawLimit >= 1 ? Math.floor(rawLimit) : DEFAULT_PARAMS_LIMIT; + const rawOffset = Number(args && args.offset); + const offset = Number.isFinite(rawOffset) && rawOffset >= 0 ? Math.floor(rawOffset) : 0; + try { // Both entities are small (a few hundred rows max) — fetch fully and merge // client-side to avoid LINQ string-injection and null-field pitfalls. @@ -124,18 +143,33 @@ async function getSystemParameters(args) { rows.sort((a, b) => String(a.code).localeCompare(String(b.code))); + // Pagination (L3.1) : sans elle la sortie sans filtre atteint ~70 000 + // caractères et se fait rejeter par les clients MCP. totalParameters est + // le total correspondant aux filtres, AVANT pagination — le signal + // truncated se vérifie donc depuis la réponse : offset + returned < total. + const matched = rows.length; + const page = rows.slice(offset, offset + limit); + const truncated = offset + page.length < matched; + + const payload = { + success: true, + warehouse: warehouse || '(none — effective value = default)', + filters: { param_class: param_class || null, search: search || null, only_overridden }, + totalParameters: matched, + totalOverrides: Array.isArray(paramValues) ? paramValues.length : 0, + returned: page.length, + offset, + }; + if (truncated) { + payload.truncated = true; + payload.hint = `Showing parameters ${offset + 1}-${offset + page.length} of ${matched}. Call again with offset=${offset + page.length} for the next page, or narrow the result with param_class / search.`; + } + payload.parameters = page; + return { content: [{ type: 'text', - text: JSON.stringify({ - success: true, - warehouse: warehouse || '(none — effective value = default)', - filters: { param_class: param_class || null, search: search || null, only_overridden }, - totalParameters: Array.isArray(parameters) ? parameters.length : 0, - totalOverrides: Array.isArray(paramValues) ? paramValues.length : 0, - returned: rows.length, - parameters: rows, - }, null, 2), + text: JSON.stringify(payload, null, 2), }], }; } catch (err) {