* chore: redeploy to pick up WORLDMONITOR_VALID_KEYS fix
* feat(mcp): add 5 AI inference tools — WorldBrief, CountryBrief, DeductionPanel, Forecasts, Social
Add RpcToolDef discriminated union alongside CacheToolDef so the MCP can
support both Redis cache reads and live LLM inference calls.
New tools (22 total, up from 17):
- get_social_velocity: Reddit geopolitical signals (cache read, intelligence:social:reddit:v1)
- get_world_brief: fetches live headlines via list-feed-digest then calls
summarize-article LLM; optional geo_context to focus the brief
- get_country_brief: calls get-country-intel-brief LLM endpoint; supports
analytical framework injection (PR #2380 compatible)
- analyze_situation: calls deduct-situation for open-ended AI geopolitical
reasoning with query + optional context
- generate_forecasts: calls get-forecasts for fresh AI forecast generation
(distinct from get_forecast_predictions which reads pre-computed cache)
The _execute path makes internal fetch calls using new URL(req.url).origin
and forwards the PRO API key header.
* fix(mcp): address Greptile review — timeouts, User-Agent, test coverage
- generate_forecasts: reduce AbortSignal timeout 60s → 25s (Vercel Edge hard ceiling ~30s)
- get_world_brief: reduce timeouts 10s+30s → 8s+20s (stays under 30s)
- Add User-Agent header to all _execute internal RPC fetches
- Move get_social_velocity out of AI inference comment block (it's a cache tool)
- Assert _execute not leaked in tools/list alongside existing _cacheKeys check
* feat(mcp): PRO MCP server — WorldMonitor data via Model Context Protocol
Adds api/mcp.ts: a Vercel edge function implementing MCP Streamable HTTP
transport (protocol 2025-03-26) for PRO API key holders.
PRO users point Claude Desktop (or any MCP client) at
https://api.worldmonitor.app/mcp with their X-WorldMonitor-Key header
and get 17 tools covering all major WorldMonitor data domains.
- Auth: validateApiKey(forceKey:true) — returns JSON-RPC -32001 on failure
- Rate limit: 60 calls/min per API key via Upstash sliding window (rl:mcp prefix)
- Tools read from existing Redis bootstrap cache (no upstream API calls)
- Each response includes cached_at (ISO timestamp) and stale (boolean)
- tools/list served from in-memory registry (<500ms, no Redis)
- Tool calls with warm cache respond in <800ms
- 11 tests covering auth, rate limit, protocol, tools/list, tools/call
* fix(mcp): handle Redis fetch throws + fix confusing label derivation
P1: wrap executeTool call in tools/call handler with try-catch so
TimeoutError/TypeError from AbortSignal.timeout in readJsonFromUpstash
returns JSON-RPC -32603 instead of an unhandled rejection → 500.
Mirrors the same guard already on the rate-limiter call above it.
P2: walk backwards through cache key segments, skipping version tags
(v\d+), bare numbers, 'stale', and 'sebuf' suffixes to find the first
meaningful label. Fixes 'economic:fred:v1:FEDFUNDS:0' → 'FEDFUNDS'
and 'risk:scores:sebuf:stale:v1' / 'theater_posture:sebuf:stale:v1'
both resolving to 'stale' (which collides with the top-level stale flag).
Adds test for P1 scenario (12 tests total, all pass).