{"record":{"id":"68badc05d9cbad21","repo":"koala73/worldmonitor","slug":"get-intel-timeline-requires-at-least-one-of-domain","errorCode":null,"errorMessage":"get_intel_timeline requires at least one of domain (\"conflict\", \"military\", or \"energy\") or country (ISO 3166-1 alpha-2) — those are the two indexed scopes on the history store.","messagePattern":"get_intel_timeline requires at least one of domain \\(\"conflict\", \"military\", or \"energy\"\\) or country \\(ISO 3166-1 alpha-2\\) — those are the two indexed scopes on the history store\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/mcp/registry/rpc-tools.ts","lineNumber":2300,"sourceCode":"    // country_code returns `{error: \"...\"}` instead (result-level user-input error).\n    outputSchema: {\n      type: 'object',\n      properties: {\n        cached_at: { type: ['string', 'null'] },\n        stale: { type: 'boolean' },\n        country_code: { type: 'string' },\n        data: {\n          type: 'object',\n          properties: {\n            overview: { type: ['object', 'null'] },\n            categories: { type: ['object', 'array', 'null'] },\n            movers: { type: ['object', 'array', 'null'] },\n            retailerSpread: { type: ['object', 'array', 'null'] },\n            freshness: { type: ['object', 'null'] },\n          },\n        },\n        error: { type: 'string', description: 'Present only on user-input failure (missing/unknown country_code).' },\n      },\n    },\n    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n    // Hybrid _execute (not a pure cache tool) because the cache keys are\n    // parameterised by country. Mirrors api/health.js::BOOTSTRAP_KEYS:55-59\n    // exactly so the U7 Tier-3 parity test treats every key as covered.\n    _coverageKeys: [\n      'consumer-prices:overview:ae',\n      'consumer-prices:categories:ae:30d',\n      'consumer-prices:movers:ae:30d',\n      'consumer-prices:retailer-spread:ae:essentials-ae',\n      'consumer-prices:freshness:ae',\n    ],\n    _execute: async (params) => {\n      // Result-level errors (NOT throws) for user-input issues — the dispatcher\n      // maps thrown errors to JSON-RPC -32603 \"Internal error\", which is\n      // misleading for a clearly-user-side fault like a missing/unknown\n      // country_code. Returning {error: ...} surfaces a usable message via\n      // the normal tools/call result envelope.","sourceCodeStart":2282,"sourceCodeEnd":2318,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/api/mcp/registry/rpc-tools.ts#L2282-L2318","documentation":"Plain Error thrown in get_intel_timeline's _execute (api/mcp/registry/rpc-tools.ts:2256): the client-side scope pre-check found neither a usable domain ('conflict', 'military', or 'energy') nor a country (ISO 3166-1 alpha-2) in the params — after trimming, both were empty strings. The handler enforces this server-side with a 400 anyway (those are the only two indexed scopes on the history store); the MCP layer checks first to convert the opaque downstream failure into an actionable message and save the round-trip. The handler remains the enforcing authority.","triggerScenarios":"Calling tools/call get_intel_timeline with params that omit both domain and country, or pass whitespace-only strings (they trim to empty), or pass domain values outside the three indexed scopes... note: out-of-scope domain strings pass this check but will fail server-side with 400.","commonSituations":"Client built from a generic template that sends an empty params object. Whitespace-polluted values from user input. Assuming limit/from/to alone scope the query. Passing a region name ('middle-east') or full country name instead of the indexed domain enum or alpha-2 code.","solutions":["Pass at least one of domain ('conflict' | 'military' | 'energy') or country (ISO 3166-1 alpha-2, e.g. 'UA') in the call params","Trim and validate user-supplied domain/country before invoking the tool — mirror the exact enum and alpha-2 rules","If you intended a global timeline, note it is unsupported by design: the history store only has those two indexes"],"exampleFix":"// before\nconst res = await client.callTool('get_intel_timeline', { limit: 50 });\n// throws: requires at least one of domain ... or country ...\n\n// after\nconst res = await client.callTool('get_intel_timeline', { domain: 'conflict', limit: 50 });\n// or\nconst res = await client.callTool('get_intel_timeline', { country: 'UA', limit: 50 });","handlingStrategy":"validation","validationCode":"// Mirror the indexed scopes before calling\nconst DOMAINS = new Set(['conflict', 'military', 'energy']);\nconst domain = typeof p.domain === 'string' ? p.domain.trim() : '';\nconst country = typeof p.country === 'string' ? p.country.trim() : '';\nif (!domain && !country) throw new Error('get_intel_timeline: pass domain or country');\nif (country && !/^[A-Za-z]{2}$/.test(country)) throw new Error('country must be ISO 3166-1 alpha-2');\nif (domain && !DOMAINS.has(domain)) throw new Error(`domain must be one of ${[...DOMAINS].join('|')}`);","typeGuard":"function isMissingScopeError(e) {\n  return e instanceof Error && e.message.startsWith('get_intel_timeline requires at least one of domain');\n}","tryCatchPattern":"try {\n  return await client.callTool('get_intel_timeline', { domain, country, limit });\n} catch (e) {\n  if (isMissingScopeError(e)) {\n    // Client bug: rebuild params ensuring domain/country survive trimming\n    return await client.callTool('get_intel_timeline', { ...p, domain: domain || 'conflict' });\n  }\n  throw e;\n}","preventionTips":["Always set at least one of domain ('conflict'|'military'|'energy') or country (alpha-2) in the params template","Trim and validate user-supplied scope strings before they reach tools/call","Remember global/unscoped timelines are unsupported by design — the history store only indexes these two scopes"],"tags":["mcp","rpc","timeline","params","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}