{"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":"Error","httpStatus":null,"severity":"error","filePath":"api/mcp/registry/rpc-tools.ts","lineNumber":1861,"sourceCode":"    },\n    outputSchema: {\n      type: 'object',\n      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 an actionable message and\n      // saves the round-trip; the handler stays the enforcing authority.\n      const domain = typeof params.domain === 'string' ? params.domain.trim() : '';\n      const country = typeof params.country === 'string' ? params.country.trim() : '';\n      if (!domain && !country) {\n        throw new Error('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.');\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\n      const url = `${base}/api/intelligence/v1/get-intel-timeline?${query}`;\n      const auth = await buildAuthHeaders(context, 'GET', url, null);\n      // No embedding on this path — one store read, so the tighter budget.\n      const response = await fetch(url, {\n        headers: { ...auth, 'User-Agent': 'worldmonitor-mcp-edge/1.0' },\n        signal: AbortSignal.timeout(8_000),\n      });\n      await assertMcpToolFetchOk(response, {\n        operation: 'get-intel-timeline',","sourceCodeStart":1843,"sourceCodeEnd":1879,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp/registry/rpc-tools.ts#L1843-L1879","documentation":"Thrown by the get_intel_timeline MCP tool as a client-side pre-flight validation before the server-side handler is even called. The tool requires at least one indexed scope — either a domain ('conflict', 'military', or 'energy') or a country (ISO 3166-1 alpha-2) — because those are the only two indexes on the history store. Without one, the handler would return a 400; this check turns that opaque failure into an actionable message and saves the round-trip. The handler remains the enforcing authority.","triggerScenarios":"Calling get_intel_timeline with neither domain nor country provided (both omitted, empty strings, or whitespace-only). For example: params: {} or params: { limit: 100 } or params: { domain: '  ', country: '' }. The validation trims the values first, so whitespace-only strings are treated as absent.","commonSituations":"An agent or caller that calls get_intel_timeline with only optional params (from, to, limit) forgetting the required scope; passing a domain value outside the allowed set (e.g. domain='geopolitics') — note this does NOT trigger THIS error (the string is non-empty) but would fail server-side; copy-paste from a template that omitted the scope fields.","solutions":["Provide at least one of domain or country: domain must be one of 'conflict', 'military', 'energy'; country must be an ISO 3166-1 alpha-2 code.","If you want all domains for a country, pass only country (e.g. country='US') — the handler applies the country index across all domains.","If you want a cross-country domain view, pass only domain (e.g. domain='military').","You can pass both domain AND country to narrow to one domain within one country."],"exampleFix":"// before — no scope, throws immediately\ntool: 'get_intel_timeline', params: { limit: 50 }\n// after — scoped to military domain\ntool: 'get_intel_timeline', params: { domain: 'military', limit: 50 }","handlingStrategy":"validation","validationCode":"// Validate scope params BEFORE calling get_intel_timeline\nconst VALID_DOMAINS = ['conflict', 'military', 'energy'];\nconst domain = typeof params.domain === 'string' ? params.domain.trim() : '';\nconst country = typeof params.country === 'string' ? params.country.trim() : '';\nif (!domain && !country) {\n  throw new Error('Provide at least one of domain or country');\n}\nif (country && !/^[A-Z]{2}$/.test(country)) {\n  throw new Error('country must be ISO 3166-1 alpha-2');\n}\nif (domain && !VALID_DOMAINS.includes(domain)) {\n  throw new Error(`domain must be one of: ${VALID_DOMAINS.join(', ')}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass at least one of domain or country — both are optional in the schema but mandatory in practice.","Domain must be exactly 'conflict', 'military', or 'energy' (validated server-side too).","Country must be a 2-letter uppercase ISO code.","This is a client-side check that saves a round-trip — the server enforces the same rule with a 400."],"tags":["mcp","validation","intel-timeline","input-validation"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}