Files
worldmonitor/docs/mcp.mdx
Elie Habib 9c3c7e8657 fix(agent-readiness): align OAuth resource with public MCP origin (#3345)
* fix(agent-readiness): align OAuth resource with actual public MCP origin

isitagentready.com's OAuth Protected Resource check enforces an
origin match between the scanned host and the metadata's `resource`
field (per the spirit of RFC 9728 §3). Our metadata declared
`resource: "https://api.worldmonitor.app"` while the MCP endpoint is
publicly served at `https://worldmonitor.app/mcp` (per vercel.json's
/mcp → /api/mcp rewrite and the MCP card's transport.endpoint).

Flip `resource` to `https://worldmonitor.app` across the three
places that declare it:

- public/.well-known/oauth-protected-resource
- public/.well-known/mcp/server-card.json (authentication block)
- api/mcp.ts (two WWW-Authenticate resource_metadata pointers)

`authorization_servers` intentionally stays on api.worldmonitor.app
— that's where /oauth/{authorize,token,register} actually live.
RFC 9728 permits AS and resource to be at different origins.

No server-side validation breaks: api/oauth/*.js and api/mcp.ts
do not bind tokens to the old resource value.

* fix(agent-readiness): align docs/tests + add MCP origin guardrail

Addresses P1/P2 review on this PR. The resource-origin flip in the
previous commit only moved the mismatch from apex to api unless the
repo also documents apex as the canonical MCP origin.

- docs/mcp.mdx: swap api.worldmonitor.app/mcp -> worldmonitor.app/mcp
  (OAuth endpoints stay on api.*, only the resource URL changes)
- tests/mcp.test.mjs: same fixture update
- tests/deploy-config.test.mjs: new guardrail block asserting that
  MCP transport.endpoint origin, OAuth metadata resource, MCP card
  authentication.resource, and api/mcp.ts resource_metadata
  pointers all share the same origin. Includes a regression guard
  that authorization_servers stays on api.worldmonitor.app (the
  intentional resource/AS split).
2026-04-23 19:42:13 +04:00

245 lines
9.6 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "MCP Server"
description: "Connect Claude, Cursor, and other MCP-compatible clients to WorldMonitor's live global-intelligence data via the Model Context Protocol."
---
WorldMonitor exposes its intelligence stack as a [Model Context Protocol](https://modelcontextprotocol.io) server so any MCP-compatible client (Claude Desktop, Claude web, Cursor, MCP Inspector, custom agents) can pull live conflict, market, aviation, maritime, economic, and forecasting data directly into a model's context.
<Info>
The MCP server is gated behind **PRO**. Free-tier users see a 401 at the OAuth step.
</Info>
## Endpoints
| Endpoint | Purpose |
|----------|---------|
| `https://worldmonitor.app/mcp` | JSON-RPC server (Streamable HTTP transport, protocol `2025-03-26`) |
| `https://api.worldmonitor.app/api/oauth/register` | Dynamic Client Registration (RFC 7591) |
| `https://api.worldmonitor.app/api/oauth/authorize` | OAuth 2.1 authorization endpoint (PKCE required) |
| `https://api.worldmonitor.app/api/oauth/token` | Token endpoint (authorization_code + refresh_token) |
| `https://api.worldmonitor.app/.well-known/oauth-authorization-server` | AS metadata (RFC 8414) |
| `https://worldmonitor.app/.well-known/oauth-protected-resource` | Resource server metadata (RFC 9728) |
Protocol version: `2025-03-26`. Server identifier: `worldmonitor` v1.0.
## Authentication
The MCP handler accepts two auth modes, in priority order:
1. **OAuth 2.1 bearer** — `Authorization: Bearer <token>` where `<token>` was issued by `/api/oauth/token`. This is what Claude Desktop, claude.ai, Cursor, and MCP Inspector use automatically. Required for any client that hits MCP from a browser origin.
2. **Direct API key** — `X-WorldMonitor-Key: wm_live_...`. Intended for server-side scripts, `curl`, and custom integrations. Do **not** send a `wm_live_...` key as a `Bearer` token — it will fail OAuth resolution and return `401 invalid_token`.
Both modes check the same PRO entitlement before every tool call, so a subscription downgrade revokes access on the next request.
### Redirect URI allowlist
Dynamic Client Registration is **not** open to arbitrary HTTPS redirects. Only these prefixes are accepted:
- `https://claude.ai/api/mcp/auth_callback`
- `https://claude.com/api/mcp/auth_callback`
- `http://localhost:<port>` / `http://127.0.0.1:<port>` (any port) — for Claude Code, MCP Inspector, local development
Other clients must proxy via one of these redirects or run locally.
### Token lifetimes
| Artifact | TTL |
|----------|-----|
| Authorization code | 10 min |
| Access token | 1 hour |
| Refresh token | 7 days |
| Registered client record | 90 days (sliding) |
## Rate limits
- **MCP calls**: 60 requests / minute / API key (sliding window)
- **OAuth authorize**: 10 requests / minute / IP
- **OAuth token**: 10 requests / minute / IP
- **Dynamic registration**: 5 registrations / minute / IP
Exceeding returns `429` with a `Retry-After` header.
## Client setup
### Claude Desktop
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) — use the remote MCP entry:
```json
{
"mcpServers": {
"worldmonitor": {
"url": "https://worldmonitor.app/mcp"
}
}
}
```
Claude Desktop handles the OAuth flow automatically on first connection.
### Claude web (claude.ai)
Add via **Settings → Connectors → Add custom connector**:
- Name: `WorldMonitor`
- URL: `https://worldmonitor.app/mcp`
### Cursor
`~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"worldmonitor": {
"url": "https://worldmonitor.app/mcp"
}
}
}
```
### MCP Inspector (debugging)
```bash
npx @modelcontextprotocol/inspector https://worldmonitor.app/mcp
```
## Tool catalog
The server exposes 32 tools. Most are cache-reads over pre-seeded Redis keys (sub-second). Six (`get_world_brief`, `get_country_brief`, `analyze_situation`, `generate_forecasts`, `search_flights`, `search_flight_prices_by_date`) call live LLMs or external APIs and are slower.
### Markets & economy
| Tool | Description |
|------|-------------|
| `get_market_data` | Equity quotes, commodity prices (incl. gold GC=F), crypto, FX, sector performance, ETF flows, Gulf markets. |
| `get_economic_data` | Fed Funds, economic calendar, fuel prices, ECB FX, EU yield curve, earnings, COT, energy storage, BIS DSR + property prices. |
| `get_country_macro` | IMF WEO bundle per country: inflation, current account, GDP/capita, unemployment, savings-investment gap (~210 countries). |
| `get_eu_housing_cycle` | Eurostat annual house price index (prc_hpi_a), 10-yr sparkline per EU member + EA20/EU27. |
| `get_eu_quarterly_gov_debt` | Eurostat quarterly gross government debt (%GDP), 8-quarter sparkline. |
| `get_eu_industrial_production` | Eurostat monthly industrial production index, 12-month sparkline. |
| `get_prediction_markets` | Active Polymarket event contracts with live probabilities. |
| `get_supply_chain_data` | Dry bulk shipping stress index, customs flows, COMTRADE bilateral trade. |
| `get_commodity_geo` | 71 major mining sites worldwide (gold, silver, copper, lithium, uranium, coal). |
### Geopolitical & security
| Tool | Description |
|------|-------------|
| `get_conflict_events` | Active UCDP/Iran conflicts, unrest w/ geo-coords, country risk scores. |
| `get_country_risk` | CII score 0-100, component breakdown, travel advisory, OFAC exposure per country. Fast, no LLM. |
| `get_military_posture` | Theater posture + military risk scores. |
| `get_cyber_threats` | URLhaus/Feodotracker malware IOCs, CISA KEV catalog, active C2 infra. |
| `get_sanctions_data` | OFAC SDN entities + sanctions pressure scores by country. |
| `get_news_intelligence` | AI-classified threat news, GDELT signals, cross-source intel. |
| `get_positive_events` | Diplomatic agreements, humanitarian aid, peace initiatives. |
| `get_social_velocity` | Reddit r/worldnews + r/geopolitics top posts, engagement scores. |
### Movement & infrastructure
| Tool | Description |
|------|-------------|
| `get_airspace` | Live ADS-B over a country. Params: `iso2` (2-letter code), `filter` (`all`/`civilian`/`military`). |
| `get_maritime_activity` | AIS density zones, dark-ship events, chokepoint congestion per country. Params: `iso2`. |
| `get_aviation_status` | FAA airport delays, NOTAM closures, tracked military aircraft. |
| `get_infrastructure_status` | Cloudflare Radar outages, major cloud/internet service status. |
| `search_flights` | Google Flights real-time search between IATA airport codes on a specific date. |
| `search_flight_prices_by_date` | Date-grid cheapest-day pricing across a range. |
### Environment & science
| Tool | Description |
|------|-------------|
| `get_natural_disasters` | USGS earthquakes, NASA FIRMS wildfires, hazard events. |
| `get_climate_data` | Temp/precip anomalies vs WMO normals, GDACS/FIRMS alerts, Mauna Loa CO2, OpenAQ PM2.5, sea ice, ocean heat. |
| `get_radiation_data` | Global radiation monitoring station readings + anomaly flags. |
| `get_research_signals` | Emerging technology events from curated research feeds. |
### AI intelligence (live LLM)
| Tool | Description | Cost |
|------|-------------|------|
| `get_world_brief` | AI-summarized world intel brief. Optional `geo_context` param. | LLM |
| `get_country_brief` | Per-country geopolitical + economic assessment. Supports analytical frameworks. | LLM |
| `analyze_situation` | Ad-hoc geopolitical deduction from a query + context. Returns confidence + supporting signals. | LLM |
| `generate_forecasts` | Fresh probability estimates (bypasses cache). | LLM |
| `get_forecast_predictions` | Pre-computed cached forecasts. Fast. | Cache |
## JSON-RPC example
Server-side with a direct API key — send it as `X-WorldMonitor-Key`, **not** as a bearer token.
```bash
WM_KEY="wm_live_..."
# 1. List tools
curl -s https://worldmonitor.app/mcp \
-H "X-WorldMonitor-Key: $WM_KEY" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# 2. Call a cache tool
curl -s https://worldmonitor.app/mcp \
-H "X-WorldMonitor-Key: $WM_KEY" \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc":"2.0","id":2,
"method":"tools/call",
"params":{"name":"get_country_risk","arguments":{"iso2":"IR"}}
}'
```
If instead you've completed the OAuth flow and hold an access token from `/api/oauth/token`, pass it as `Authorization: Bearer $TOKEN`.
## Response shape
Tool responses use the standard MCP content block format:
```json
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{ "type": "text", "text": "{...json payload...}" }
],
"isError": false
}
}
```
For cache tools, the JSON payload includes `_meta` with `fetchedAt` and `staleness` so the model can reason about freshness.
## Data freshness
All cache tools read from Redis keys written by Railway cron seeders. Typical freshness:
| Domain | Typical freshness |
|--------|-------------------|
| Markets (intraday) | 15 min |
| Flights (ADS-B) | 13 min |
| Maritime (AIS) | 515 min |
| Conflicts / unrest | 1560 min |
| Macro / BIS / Eurostat | dailyweekly |
| IMF WEO | monthly |
Seed-level health per key: [status.worldmonitor.app](https://status.worldmonitor.app/).
## Errors
| Code | Meaning |
|------|---------|
| 401 | Invalid / missing token |
| 403 | Token valid but account not PRO |
| 404 | Unknown tool name |
| 429 | Rate limited (60 req/min/key) |
| 503 | Upstream cache unavailable |
Tool-level errors return `isError: true` with a text explanation in `content`.
## Related
- [Authentication overview](/authentication) — browser vs bearer vs OAuth
- [API Reference](/api/ConflictService.openapi.yaml) — the same data via REST
- [Pro features](https://www.worldmonitor.app/pro)