koala73/worldmonitor · error

get_intel_timeline requires at least one of domain…

Error message

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.

What it means

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.

Solutions

  1. Pass at least one of domain ('conflict' | 'military' | 'energy') or country (ISO 3166-1 alpha-2, e.g. 'UA') in the call params
  2. Trim and validate user-supplied domain/country before invoking the tool — mirror the exact enum and alpha-2 rules
  3. If you intended a global timeline, note it is unsupported by design: the history store only has those two indexes

Example fix

// before
const res = await client.callTool('get_intel_timeline', { limit: 50 });
// throws: requires at least one of domain ... or country ...

// after
const res = await client.callTool('get_intel_timeline', { domain: 'conflict', limit: 50 });
// or
const res = await client.callTool('get_intel_timeline', { country: 'UA', limit: 50 });
Defensive patterns

Strategy: validation

Validate before calling

// Mirror the indexed scopes before calling
const DOMAINS = new Set(['conflict', 'military', 'energy']);
const domain = typeof p.domain === 'string' ? p.domain.trim() : '';
const country = typeof p.country === 'string' ? p.country.trim() : '';
if (!domain && !country) throw new Error('get_intel_timeline: pass domain or country');
if (country && !/^[A-Za-z]{2}$/.test(country)) throw new Error('country must be ISO 3166-1 alpha-2');
if (domain && !DOMAINS.has(domain)) throw new Error(`domain must be one of ${[...DOMAINS].join('|')}`);

Type guard

function isMissingScopeError(e) {
  return e instanceof Error && e.message.startsWith('get_intel_timeline requires at least one of domain');
}

Try / catch

try {
  return await client.callTool('get_intel_timeline', { domain, country, limit });
} catch (e) {
  if (isMissingScopeError(e)) {
    // Client bug: rebuild params ensuring domain/country survive trimming
    return await client.callTool('get_intel_timeline', { ...p, domain: domain || 'conflict' });
  }
  throw e;
}

Prevention

When it happens

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

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

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@e586b8b4b8 (2026-08-21). Data as JSON: /api/errors/68badc05d9cbad21. Report an issue: GitHub.

Appendix: source

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

    // country_code returns `{error: "..."}` instead (result-level user-input error).
    outputSchema: {
      type: 'object',
      properties: {
        cached_at: { type: ['string', 'null'] },
        stale: { type: 'boolean' },
        country_code: { type: 'string' },
        data: {
          type: 'object',
          properties: {
            overview: { type: ['object', 'null'] },
            categories: { type: ['object', 'array', 'null'] },
            movers: { type: ['object', 'array', 'null'] },
            retailerSpread: { type: ['object', 'array', 'null'] },
            freshness: { type: ['object', 'null'] },
          },
        },
        error: { type: 'string', description: 'Present only on user-input failure (missing/unknown country_code).' },
      },
    },
    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    // Hybrid _execute (not a pure cache tool) because the cache keys are
    // parameterised by country. Mirrors api/health.js::BOOTSTRAP_KEYS:55-59
    // exactly so the U7 Tier-3 parity test treats every key as covered.
    _coverageKeys: [
      'consumer-prices:overview:ae',
      'consumer-prices:categories:ae:30d',
      'consumer-prices:movers:ae:30d',
      'consumer-prices:retailer-spread:ae:essentials-ae',
      'consumer-prices:freshness:ae',
    ],
    _execute: async (params) => {
      // Result-level errors (NOT throws) for user-input issues — the dispatcher
      // maps thrown errors to JSON-RPC -32603 "Internal error", which is
      // misleading for a clearly-user-side fault like a missing/unknown
      // country_code. Returning {error: ...} surfaces a usable message via
      // the normal tools/call result envelope.

View on GitHub (pinned to e586b8b4b8)