koala73/worldmonitor · warning · RpcValidationError

get-intel-timeline: domain Required unless country is set. O

Error message

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).

What it means

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).

Source

Thrown at api/mcp/registry/rpc-tools.ts:3136

      required: ['records', 'partial', 'upstreamUnavailable'],
      properties: {
        records: { type: 'array', description: 'Scoped history, newest first.', items: INTEL_HISTORY_RECORD_SCHEMA },
        partial: { type: 'boolean', description: 'True when the bounded post-filter window may omit older matching events.' },
        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".' },
      },
    },
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    _execute: async (params, base, context, execution) => {
      // Scope is mandatory server-side (a 400 from the handler). Checking it
      // here turns an opaque downstream failure into actionable -32602
      // violations (RpcValidationError) and saves the round-trip; the handler
      // stays the enforcing authority. A plain Error would land as -32603 +
      // Sentry error (WORLDMONITOR-10Y).
      const domain = typeof params.domain === 'string' ? params.domain.trim() : '';
      const country = normalizeCountry(params.country);
      assertIntelHistoryCountry('get-intel-timeline', country);
      if (!domain && !country) {
        throw new RpcValidationError('get-intel-timeline', [
          {
            field: 'domain',
            description: 'Required unless country is set. One of conflict, military, or energy.',
          },
          {
            field: 'country',
            description: 'Required unless domain is set. ISO 3166-1 alpha-2, uppercase (e.g. UA).',
          },
        ]);
      }

      const query = new URLSearchParams();
      addStringParam(query, 'domain', domain);
      addStringParam(query, 'country', country);
      addIntelHistoryNumber(query, 'from', params.from);
      addIntelHistoryNumber(query, 'to', params.to);
      addIntelHistoryNumber(query, 'limit', Math.min(Number(params.limit ?? MCP_HISTORY_TIMELINE_MAX_LIMIT), MCP_HISTORY_TIMELINE_MAX_LIMIT));

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Pass `domain` as one of "conflict", "military", or "energy"
  2. Or pass `country` as an uppercase ISO 3166-1 alpha-2 code (e.g. "UA"); supplying both narrows to their intersection
  3. If you intended a broad query, note it is intentionally unsupported — pick a domain or country scope per call

Example fix

// before
tools.get_intel_timeline({ from: 1700000000000 })
// after
tools.get_intel_timeline({ domain: 'conflict', from: 1700000000000 })
Defensive patterns

Strategy: validation

Validate before calling

const DOMAINS = new Set(['conflict', 'military', 'energy']);
const params: Record<string, unknown> = {};
if (input.domain && DOMAINS.has(input.domain)) params.domain = input.domain;
else if (isIsoAlpha2(input.country)) params.country = input.country;
else {
  // refuse to call without a scope instead of firing an unscoped request
  throw new Error('get_intel_timeline requires domain or country');
}
await tools.get_intel_timeline(params);

Type guard

function hasTimelineScope(p: { domain?: unknown; country?: unknown }): boolean {
  const d = typeof p.domain === 'string' ? p.domain.trim() : '';
  const c = typeof p.country === 'string' ? p.country.trim().toUpperCase() : '';
  return (d === 'conflict' || d === 'military' || d === 'energy') || /^[A-Z]{2}$/.test(c);
}

Try / catch

try {
  await tools.get_intel_timeline(params);
} catch (e) {
  if (e?.code === -32602) {
    // read e.data.violations, add domain or country, then retry once with a valid scope
  } else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-08-27). Data as JSON: /api/errors/666119f68f15d04f. Report an issue: GitHub.