{"record":{"id":"666119f68f15d04f","repo":"koala73/worldmonitor","slug":"get-intel-timeline-domain-required-unless-country","errorCode":null,"errorMessage":"get-intel-timeline: domain Required unless country is set. One of conflict, military, or energy. / country Required unless domain is set. ISO 3166-1 alpha-2, uppercase (e.g. UA).","messagePattern":"get-intel-timeline: domain Required unless country is set\\. One of conflict, military, or energy\\. / country Required unless domain is set\\. ISO 3166-1 alpha-2, uppercase \\(e\\.g\\. UA\\)\\.","errorType":"validation","errorClass":"RpcValidationError","httpStatus":400,"severity":"warning","filePath":"api/mcp/registry/rpc-tools.ts","lineNumber":3136,"sourceCode":"      required: ['records', 'partial', 'upstreamUnavailable'],\n      properties: {\n        records: { type: 'array', description: 'Scoped history, newest first.', items: INTEL_HISTORY_RECORD_SCHEMA },\n        partial: { type: 'boolean', description: 'True when the bounded post-filter window may omit older matching events.' },\n        upstreamUnavailable: { type: 'boolean', description: 'True when the history store could not be reached. `records` is then empty because the read failed — never read that as \"nothing happened in this window\".' },\n      },\n    },\n    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n    _execute: async (params, base, context, execution) => {\n      // Scope is mandatory server-side (a 400 from the handler). Checking it\n      // here turns an opaque downstream failure into actionable -32602\n      // violations (RpcValidationError) and saves the round-trip; the handler\n      // stays the enforcing authority. A plain Error would land as -32603 +\n      // Sentry error (WORLDMONITOR-10Y).\n      const domain = typeof params.domain === 'string' ? params.domain.trim() : '';\n      const country = normalizeCountry(params.country);\n      assertIntelHistoryCountry('get-intel-timeline', country);\n      if (!domain && !country) {\n        throw new RpcValidationError('get-intel-timeline', [\n          {\n            field: 'domain',\n            description: 'Required unless country is set. One of conflict, military, or energy.',\n          },\n          {\n            field: 'country',\n            description: 'Required unless domain is set. ISO 3166-1 alpha-2, uppercase (e.g. UA).',\n          },\n        ]);\n      }\n\n      const query = new URLSearchParams();\n      addStringParam(query, 'domain', domain);\n      addStringParam(query, 'country', country);\n      addIntelHistoryNumber(query, 'from', params.from);\n      addIntelHistoryNumber(query, 'to', params.to);\n      addIntelHistoryNumber(query, 'limit', Math.min(Number(params.limit ?? MCP_HISTORY_TIMELINE_MAX_LIMIT), MCP_HISTORY_TIMELINE_MAX_LIMIT));\n","sourceCodeStart":3118,"sourceCodeEnd":3154,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/api/mcp/registry/rpc-tools.ts#L3118-L3154","documentation":"Thrown by the `get_intel_timeline` MCP tool's `_execute` in api/mcp/registry/rpc-tools.ts:2441 as an RpcValidationError mapped to JSON-RPC -32602. The underlying history route requires at least one scope — a `domain` or a `country` — because an unscoped read of all intel history is not allowed server-side (it would 400). The registry checks this locally to return actionable field-level violations and avoid the round-trip and the generic -32603 path (WORLDMONITOR-10Y).","triggerScenarios":"Calling `get_intel_timeline` with neither `domain` nor `country`, or with values that normalize to empty strings (e.g. `domain: \"  \"`, or a `country` that `normalizeCountry` reduces to empty such as an unknown name).","commonSituations":"Agents exploring the tool without arguments; callers passing whitespace-only strings; callers passing a country name that normalization strips, leaving both scopes empty; passing an invalid domain value that trims to something empty.","solutions":["Pass `domain` as one of \"conflict\", \"military\", or \"energy\"","Or pass `country` as an uppercase ISO 3166-1 alpha-2 code (e.g. \"UA\"); supplying both narrows to their intersection","If you intended a broad query, note it is intentionally unsupported — pick a domain or country scope per call"],"exampleFix":"// before\ntools.get_intel_timeline({ from: 1700000000000 })\n// after\ntools.get_intel_timeline({ domain: 'conflict', from: 1700000000000 })","handlingStrategy":"validation","validationCode":"const DOMAINS = new Set(['conflict', 'military', 'energy']);\nconst params: Record<string, unknown> = {};\nif (input.domain && DOMAINS.has(input.domain)) params.domain = input.domain;\nelse if (isIsoAlpha2(input.country)) params.country = input.country;\nelse {\n  // refuse to call without a scope instead of firing an unscoped request\n  throw new Error('get_intel_timeline requires domain or country');\n}\nawait tools.get_intel_timeline(params);","typeGuard":"function hasTimelineScope(p: { domain?: unknown; country?: unknown }): boolean {\n  const d = typeof p.domain === 'string' ? p.domain.trim() : '';\n  const c = typeof p.country === 'string' ? p.country.trim().toUpperCase() : '';\n  return (d === 'conflict' || d === 'military' || d === 'energy') || /^[A-Z]{2}$/.test(c);\n}","tryCatchPattern":"try {\n  await tools.get_intel_timeline(params);\n} catch (e) {\n  if (e?.code === -32602) {\n    // read e.data.violations, add domain or country, then retry once with a valid scope\n  } else throw e;\n}","preventionTips":["Always pick a scope (domain or country) when composing get_intel_timeline calls","Trim and validate user-supplied domain/country strings before passing them through"],"tags":["mcp","validation","required-field","rpc","intel-timeline"],"backgroundTag":"missing-required-argument","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-08-27T19:53:08.521Z","contentChangedAt":"2026-08-27T19:53:08.521Z","schemaVersion":2},"datasetVersion":"2026-09-08T20:17:18.057Z"}