feat(intelligence): GetCountryRisk RPC + MCP tool for per-country risk scores (#2502)

* feat(intelligence): GetCountryRisk RPC for per-country risk intelligence

Adds a new fast Redis-read RPC that consolidates CII score, travel advisory
level, and OFAC sanctions exposure into a single per-country response.

Replaces the need to call GetRiskScores (all-countries) and filter client-side.
Wired to MCP as get_country_risk tool (no LLM, ~200ms, good for agent screening).

- proto/intelligence/v1/get_country_risk.proto (new)
- server/intelligence/v1/get-country-risk.ts (reads 3 pre-seeded Redis keys)
- gateway.ts: slow cache tier
- api/mcp.ts: RpcToolDef with 8s timeout
- tests/mcp.test.mjs: update tool count 27→28

* fix(intelligence): upstream-unavailable signal, fetchedAt from CII, drop redundant catch

P1: return upstreamUnavailable:true when all Redis reads are null — prevents
CDN from caching false-negative sanctions/risk responses during Redis outages.

P2: fetchedAt now uses cii.computedAt (actual data age) instead of request time.

P2: removed redundant .catch(() => null) — getCachedJson already swallows errors.

* fix(intelligence): accurate OFAC counts and country names for GetCountryRisk

P1: sanctions:pressure:v1.countries is a top-12 slice — switch to a new
sanctions:country-counts:v1 key (ISO2→count across ALL 40K+ OFAC entries).
Written by seed-sanctions-pressure.mjs in afterPublish alongside entity index.

P1: trigger upstreamUnavailable:true when sanctions key alone is missing,
preventing false-negative sanctionsActive:false from being cached by CDN.

P2: advisory seeder now writes byCountryName (ISO2→display name) derived
from country-names.json reverse map. Handler uses it as fallback so countries
outside TIER1_COUNTRIES (TH, CO, BD, IT...) get proper names.
This commit is contained in:
Elie Habib
2026-03-29 17:07:03 +04:00
committed by GitHub
parent ebd778fe19
commit 8aee4d340e
13 changed files with 367 additions and 5 deletions

View File

@@ -348,6 +348,29 @@ const TOOL_REGISTRY: ToolDef[] = [
return res.json();
},
},
{
name: 'get_country_risk',
description: 'Structured risk intelligence for a specific country: Composite Instability Index (CII) score 0-100, component breakdown (unrest/conflict/security/news), travel advisory level, and OFAC sanctions exposure. Fast Redis read — no LLM. Use for quantitative risk screening or to answer "how risky is X right now?"',
inputSchema: {
type: 'object',
properties: {
country_code: { type: 'string', description: 'ISO 3166-1 alpha-2 country code, e.g. "RU", "IR", "CN", "UA"' },
},
required: ['country_code'],
},
_execute: async (params, base, apiKey) => {
const code = String(params.country_code ?? '').toUpperCase().slice(0, 2);
const res = await fetch(
`${base}/api/intelligence/v1/get-country-risk?country_code=${encodeURIComponent(code)}`,
{
headers: { 'X-WorldMonitor-Key': apiKey, 'User-Agent': 'worldmonitor-mcp-edge/1.0' },
signal: AbortSignal.timeout(8_000),
},
);
if (!res.ok) throw new Error(`get-country-risk HTTP ${res.status}`);
return res.json();
},
},
{
name: 'get_airspace',
description: 'Live ADS-B aircraft over a country. Returns civilian flights (OpenSky) and identified military aircraft with callsigns, positions, altitudes, and headings. Answers questions like "how many planes are over the UAE right now?" or "are there military aircraft over Taiwan?"',