{"record":{"id":"59f0c71cadd77109","repo":"abhigyanpatwari/GitNexus","slug":"unknown-noun-quoted-for-tool-toolname-suggestion-did-you","errorCode":null,"errorMessage":"Unknown ${noun} ${quoted} for tool \"${toolName}\".${suggestion ? ` Did you mean \"${suggestion}\"?` : ` The advertised inputSchema ${verb} not include ${unknownKeys.length === 1 ? 'this key' : 'these keys'}.`}\"","messagePattern":"Unknown (.+?) (.+?) for tool \"(.+?)\"\\.(.+?)\"\\?` : ` The advertised inputSchema (.+?) not include (.+?)\\.`\\}\"","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/mcp/tool-arguments.ts","lineNumber":125,"sourceCode":"  toolName: string,\n  args: Record<string, unknown> | undefined,\n  advertisedProperties?: Record<string, unknown>,\n): void {\n  if (!args) return;\n  const propertyNames =\n    advertisedProperties !== undefined\n      ? Object.keys(advertisedProperties)\n      : advertisedToolPropertyNames(toolName);\n  if (!propertyNames) return;\n\n  const unpublished =\n    UNPUBLISHED_TOOL_ARGUMENT_ALIASES[toolName] ??\n    UNPUBLISHED_TOOL_ARGUMENT_ALIASES[schemaSourceToolName(toolName)] ??\n    [];\n  const allowed = new Set([...propertyNames, ...unpublished]);\n  const unknownKeys = Object.keys(args).filter((key) => !allowed.has(key));\n  if (unknownKeys.length === 0) return;\n  throw new Error(formatUnknownArgumentError(toolName, unknownKeys, propertyNames));\n}\n\n/**\n * Fold numeric aliases onto their canonical key (e.g. `depth` → `maxDepth`).\n * Conflicting values error; a single agreed value is written to the canonical\n * key and the alias keys are removed so every downstream reader sees one name.\n */\nexport function foldNumericToolArgumentAliases(\n  toolName: string,\n  params: Record<string, unknown>,\n): { params: Record<string, unknown> } | { error: string } {\n  const definitions = TOOL_NUMERIC_ARGUMENT_ALIASES[toolName];\n  if (!definitions) return { params };\n\n  const normalized = { ...params };\n  for (const { canonical, aliases } of definitions) {\n    const keys = [canonical, ...aliases];\n    const supplied: Array<{ key: string; value: number }> = [];","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/ac9a4e9abd8fd3058c070b72c23402a4f887929a/gitnexus/src/mcp/tool-arguments.ts#L107-L143","documentation":"The MCP server validates every tool call's top-level arguments against the tool's advertised inputSchema plus a small set of unpublished aliases. If the caller passes keys that are neither advertised nor aliased, `assertKnownMcpToolArguments` throws immediately with the unknown key names, a fuzzy 'Did you mean' suggestion when one matches, or a note that the advertised schema does not include the keys.","triggerScenarios":"An MCP client invokes a GitNexus tool (e.g. `impact`, `query`, `analyze`) with a misspelled argument (`dept` instead of `depth`), a snake_case variant, an argument that exists on a different GitNexus tool, or an argument removed/renamed in a newer GitNexus version while the client config still sends the old name.","commonSituations":"Hand-edited MCP client configs with stale or mistyped argument names; copy-pasting arguments between tools; using camelCase vs snake_case inconsistently; a GitNexus upgrade renaming arguments while an agent's cached tool descriptions are stale.","solutions":["Use the exact argument name from the tool's advertised inputSchema (check `tools/list` output).","Follow the 'Did you mean' suggestion in the error message — it maps the unknown key to the closest valid one.","Fold known numeric aliases onto canonical keys before calling (e.g. `depth` → `maxDepth`); conflicting alias values must be resolved to one.","If arguments were valid before an upgrade, refresh the MCP client's cached tool schemas to the current server version."],"exampleFix":"// before\n{ \"tool\": \"impact\", \"args\": { \"target\": \"foo\", \"dept\": 2 } }\n// after (error suggests \"maxDepth\"; depth is an accepted alias)\n{ \"tool\": \"impact\", \"args\": { \"target\": \"foo\", \"maxDepth\": 2 } }","handlingStrategy":"validation","validationCode":"// validate tool args against the advertised schema before calling\nfunction validateToolArgs(toolName, args, advertisedProperties) {\n  const allowed = new Set(Object.keys(advertisedProperties));\n  const unknown = Object.keys(args).filter(k => !allowed.has(k));\n  if (unknown.length) throw new Error(`Unknown argument(s) ${unknown.join(', ')} for tool \"${toolName}\"`);\n}","typeGuard":"const isKnownToolArg = (toolName, key, advertisedProperties) =>\n  key in advertisedProperties; // or consult tools/list schemas cached from the server","tryCatchPattern":"try {\n  const res = await mcpClient.callTool(toolName, args);\n} catch (e) {\n  const m = /Did you mean \"([^\"]+)\"\\?/.exec(e.message);\n  if (m) return callTool(toolName, { ...args, [m[1]]: args[unknownKey] });\n  throw e;\n}","preventionTips":["Generate client call sites from the server's `tools/list` schema instead of hand-writing argument names.","Use canonical argument names (e.g. `maxDepth`), not aliases, in persistent configs.","After upgrading GitNexus, refresh cached tool schemas in the MCP client.","Keep an integration test that calls each tool with valid args to catch renames early."],"tags":["mcp","arguments","validation","schema"],"backgroundTag":"unknown-argument","analyzedSha":"ac9a4e9abd8fd3058c070b72c23402a4f887929a","analyzedAt":"2026-09-15T23:29:44.066Z","contentChangedAt":"2026-09-15T23:29:44.066Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}