L2.2 : rejette les paramètres inconnus et les requis manquants (D23)
Mesure V1 (24/08/2026) : le SDK MCP ignore additionalProperties: false —
read_recent_logs({lines: 5}) avec la clause sur le schéma répondait
success: true, returnedLines: 100 (retombée silencieuse sur le défaut).
La validation vit donc dans le wrapper tools/call de src/index.js,
pilotée par les schémas de la table de routage (D22) : paramètre inconnu
ou requis manquant -> erreur structurée nommant le fautif et les
paramètres valides, avant tout dispatch.
Les 23 schémas portent additionalProperties: false — inerte côté SDK,
mais c'est le contrat que lisent les clients. Pas de renommage de
paramètres (écarté, cf. ROADMAP).
Mesures (via le protocole) :
- read_recent_logs({"lines": 5}) -> "Paramètre(s) inconnu(s) pour
read_recent_logs : lines. Paramètres valides : count, log_file."
- read_recent_logs({"count": 5}) -> succès, returnedLines: 5
- boucle sur 22 outils avec {} (execute_command vérifié statiquement) :
22/22 répondent, aucun Unknown tool, les 11 outils à paramètres requis
échouent avec le message actionnable
- handshake : 23 outils, 6 resources
Docs : D23 dans DECISIONS.md, L2.2 retirée de la ROADMAP.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -414,3 +414,29 @@ c'est un bug de développement, pas un cas d'exécution.
|
||||
La table ne présume rien de la signature des outils : `(name, args)` est
|
||||
transmis tel quel au `executeTool()` du module. Ajouter un paramètre à un
|
||||
outil ne la concerne pas.
|
||||
|
||||
---
|
||||
|
||||
## D23 — Le SDK ne valide pas les arguments : validation dans le wrapper
|
||||
|
||||
**Piège mesuré (24/08/2026).** Le SDK MCP (`@modelcontextprotocol/sdk` 1.x)
|
||||
ne valide **pas** les arguments d'appel contre l'`inputSchema` déclaré :
|
||||
`additionalProperties: false` est ignoré, et un paramètre inconnu
|
||||
(`read_recent_logs(lines: 60)`) retombe silencieusement sur les défauts
|
||||
(`count = 100`) sans le moindre signal.
|
||||
|
||||
**Décision.** Le wrapper `tools/call` de `src/index.js` valide chaque appel
|
||||
contre le schéma de la table de routage (D22) avant le dispatch — schéma
|
||||
déclaré = contrat appliqué, pour les 23 outils d'un coup :
|
||||
|
||||
- **paramètre inconnu** → erreur structurée nommant le paramètre fautif **et**
|
||||
les paramètres valides de l'outil ;
|
||||
- **paramètre `required` manquant** → même forme d'erreur.
|
||||
|
||||
Les 23 schémas portent aussi `additionalProperties: false` : inerte côté SDK,
|
||||
mais c'est le contrat que lisent les clients. La validation reste volontairement
|
||||
superficielle (noms et présence, pas les types) : le but est de supprimer le
|
||||
silence, pas de réimplémenter JSON Schema.
|
||||
|
||||
Le renommage des paramètres (`entity_type`/`query` uniformisés) a été **écarté**
|
||||
au profit de cette validation — voir ROADMAP « Écarté ».
|
||||
|
||||
-12
@@ -17,18 +17,6 @@ contient que ce qui reste à faire.
|
||||
La cause racine commune du lot (résolution `Name` -> `TableName` via l'API
|
||||
Metadata) est livrée — la règle et ses mesures vivent en **D21**.
|
||||
|
||||
### L2.2 — Rejeter les paramètres inconnus
|
||||
|
||||
Le SDK MCP ignore silencieusement les paramètres non déclarés : un appel
|
||||
`read_recent_logs(lines: 60)` retombe sur le défaut `count = 100` sans le
|
||||
moindre signal, et l'appelant conclut à un paramètre ignoré.
|
||||
|
||||
Ajouter `additionalProperties: false` aux 23 schémas d'outils.
|
||||
|
||||
C'est le correctif retenu **à la place** d'une uniformisation des noms de
|
||||
paramètres : renommer casse les usages existants pour un gain cosmétique, alors
|
||||
que la cause réelle est l'absence de signal.
|
||||
|
||||
### L2.3 — Arguments manquants : deux garde-fous
|
||||
|
||||
Découverts lors de la révision du lot 1 (24/08/2026), en bouclant sur les 23
|
||||
|
||||
@@ -94,6 +94,36 @@ for (const { moduleName, module } of TOOL_MODULES) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide les arguments d'un appel d'outil contre son inputSchema (D23).
|
||||
* Le SDK MCP ne valide pas les schémas d'entrée — mesuré le 24/08/2026 :
|
||||
* `additionalProperties: false` est ignoré et un paramètre inconnu retombe
|
||||
* silencieusement sur les défauts. La validation vit donc ici, pilotée par la
|
||||
* même table que tools/list : schéma déclaré = contrat appliqué.
|
||||
*/
|
||||
function validateToolArgs(definition, args) {
|
||||
const schema = definition.inputSchema || {};
|
||||
const properties = schema.properties || {};
|
||||
const validNames = Object.keys(properties);
|
||||
const validList = validNames.length > 0 ? validNames.join(', ') : '(aucun)';
|
||||
|
||||
const unknown = Object.keys(args || {}).filter(key => !(key in properties));
|
||||
if (unknown.length > 0) {
|
||||
throw new Error(
|
||||
`Paramètre(s) inconnu(s) pour ${definition.name} : ${unknown.join(', ')}. ` +
|
||||
`Paramètres valides : ${validList}.`
|
||||
);
|
||||
}
|
||||
|
||||
const missing = (schema.required || []).filter(key => args?.[key] === undefined);
|
||||
if (missing.length > 0) {
|
||||
throw new Error(
|
||||
`Paramètre(s) requis manquant(s) pour ${definition.name} : ${missing.join(', ')}. ` +
|
||||
`Paramètres valides : ${validList}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create MCP Server
|
||||
const server = new Server(
|
||||
{
|
||||
@@ -193,6 +223,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
`Unknown tool: ${name}. Available tools: ${Array.from(toolRegistry.keys()).join(', ')}`
|
||||
);
|
||||
}
|
||||
validateToolArgs(entry.definition, args);
|
||||
return await entry.module.executeTool(name, args);
|
||||
} catch (error) {
|
||||
console.error(`[Server] Error executing tool ${name}:`, error.message);
|
||||
|
||||
@@ -15,6 +15,7 @@ function listTools() {
|
||||
description: 'Get summary of Application Dictionary elements. Shows count of cached elements per type (Commands, Queries, Dialogs, Views, etc.). Only counts already-loaded types to avoid long waits.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
@@ -23,6 +24,7 @@ function listTools() {
|
||||
description: 'Get all elements of a specific type from Application Dictionary. Supports: Command, Query, Dialog, View, Entity, Event, Hook, Report, Dashboard, and 11 other types (20 total). Elements are lazy-loaded and cached for 1 hour.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
element_type: {
|
||||
type: 'string',
|
||||
@@ -42,6 +44,7 @@ function listTools() {
|
||||
description: 'Search Application Dictionary elements by name, description, or code. Searches within a specific element type.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
element_type: {
|
||||
type: 'string',
|
||||
@@ -65,6 +68,7 @@ function listTools() {
|
||||
description: 'Get detailed information about a specific AD element by ID or name',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
element_type: {
|
||||
type: 'string',
|
||||
@@ -83,6 +87,7 @@ function listTools() {
|
||||
description: 'List all available Application Dictionary element types',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ function listTools() {
|
||||
description: 'Appelle l\'API Query du WMS pour interroger des entités (Containers, Stocks, Tasks, Products, etc.)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
entity_type: {
|
||||
type: 'string',
|
||||
@@ -44,6 +45,7 @@ function listTools() {
|
||||
description: 'Exécute une commande WMS (ATTENTION: peut modifier des données). Toujours récupérer la commande via get_ad_elements/get_ad_element_details avant d\'exécuter.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
command_name: {
|
||||
type: 'string',
|
||||
|
||||
@@ -30,6 +30,7 @@ Examples:
|
||||
- get_system_parameters(search="CROSSDOCK") — parameters whose code/description matches`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
warehouse: {
|
||||
type: 'string',
|
||||
|
||||
@@ -15,6 +15,7 @@ function listTools() {
|
||||
description: 'Lit les dernières lignes des fichiers de logs',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
count: {
|
||||
type: 'number',
|
||||
@@ -33,6 +34,7 @@ function listTools() {
|
||||
description: 'Liste tous les fichiers de logs disponibles sous LOGS_PATH avec leur taille et date de modification',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
@@ -41,6 +43,7 @@ function listTools() {
|
||||
description: 'Recherche un mot-clé dans les fichiers de logs avec contexte',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
keyword: {
|
||||
type: 'string',
|
||||
|
||||
@@ -16,6 +16,7 @@ Use this to discover the exact field names and types for any entity before build
|
||||
- With entity_name (partial match ok, case-insensitive): returns field names + types for that entity`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
entity_name: {
|
||||
type: 'string',
|
||||
@@ -34,6 +35,7 @@ Examples:
|
||||
- generic_search() — list available search categories`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
|
||||
@@ -16,6 +16,7 @@ function listTools() {
|
||||
description: 'List all WMS profiles configured in .env (AD, LIMAGRAIN, ...) with their host and tenant. Use this to see which WMS backends are available.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
@@ -24,6 +25,7 @@ function listTools() {
|
||||
description: 'Return the currently active WMS profile (name, host, tenant, application). If no profile is active, returns an error explaining that switch_wms_profile must be called first.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
@@ -32,6 +34,7 @@ function listTools() {
|
||||
description: 'Switch the active WMS profile. Resets the OAuth token and clears workflow/AD caches so the next API call targets the new backend. Use list_wms_profiles to see valid names.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
profile: {
|
||||
type: 'string',
|
||||
|
||||
@@ -24,6 +24,7 @@ IMPORTANT — before building a filter with a status/enum field:
|
||||
Never guess enum string values — they differ between Reading and Writing models.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
entity_type: {
|
||||
type: 'string',
|
||||
@@ -52,6 +53,7 @@ Never guess enum string values — they differ between Reading and Writing model
|
||||
description: 'Get the schema/structure of a WMS entity by querying one sample record',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
entity_type: {
|
||||
type: 'string',
|
||||
@@ -84,6 +86,7 @@ Verified values (curl-tested):
|
||||
(sur un emplacement: ajouter && z.LocationCode == "X")`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
entity_type: {
|
||||
type: 'string',
|
||||
@@ -102,6 +105,7 @@ Verified values (curl-tested):
|
||||
description: 'Search for a keyword across multiple WMS entities',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
keyword: {
|
||||
type: 'string',
|
||||
|
||||
@@ -15,6 +15,7 @@ function listTools() {
|
||||
description: 'Search workflows by name, description, or code. Returns matching workflows with metadata. Workflows are lazy-loaded from API on first request and cached for 1 hour.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
@@ -37,6 +38,7 @@ function listTools() {
|
||||
description: 'Get full details of a specific workflow by ID or code',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
workflow_id: {
|
||||
type: 'string',
|
||||
@@ -51,6 +53,7 @@ function listTools() {
|
||||
description: 'List workflow groupings by applicationName. Workflows have no category field in the AD API — applicationName is the only grouping available, and all workflows of the active application share the same value.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user