diff --git a/CLAUDE.md b/CLAUDE.md
index ded8823..9faef39 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## What This Is
-World Intelligence MCP Server — 68 tools across 27 domains providing real-time global intelligence from free public APIs. Serves three interfaces: MCP stdio (for Claude Code/Cursor), a live Starlette dashboard with SSE, and a Click CLI with Rich output.
+World Intelligence MCP Server — 81 tools across 30 domains providing real-time global intelligence from free public APIs. Serves three interfaces: MCP stdio (for Claude Code/Cursor), a live Starlette dashboard with SSE, and a Click CLI with Rich output. Python 3.11+, built with hatchling.
## Commands
@@ -28,6 +28,8 @@ intel status # cache + circuit breaker health
# Dashboard (requires [dashboard] extra)
intel-dashboard --port 8501
+# Or with .env auto-loaded:
+./run-dashboard.sh
```
## Architecture
@@ -46,11 +48,15 @@ dashboard/app.py (SSE) ─┘
- `CircuitBreaker`: Per-source tracking. 3 consecutive failures trips the breaker for 5 minutes. Each RSS feed gets its own breaker (`rss:bbc_world`).
- `Cache`: SQLite WAL-mode TTL cache. `get()` returns live data, `get_stale()` returns expired data for fallback.
-**Source modules** (`sources/*.py`): Each module exports `async def fetch_*(fetcher: Fetcher, **kwargs) -> dict`. Pure data fetching — no MCP awareness. 25 modules covering markets, seismology, military, cyber, health, etc.
+**Source modules** (`sources/*.py`): Each module exports `async def fetch_*(fetcher: Fetcher, **kwargs) -> dict`. Pure data fetching — no MCP awareness. 30 modules covering markets, seismology, military, cyber, health, tech, environmental, etc.
**Analysis modules** (`analysis/*.py`): Cross-domain intelligence that consumes outputs from multiple sources. Includes signal aggregation, instability indexing, NLP (entity extraction, classification, clustering, spike detection via Welford's algorithm), and strategic synthesis.
-**Static config** (`config/*.py`): Curated datasets — 22 intel hotspots, 70+ military bases, 40 ports, 24 pipelines, 24 nuclear facilities, 105 major cities, 28 world leaders, 36 APT groups.
+**Reports** (`reports/*.py`): Jinja2-templated HTML/Markdown reports (daily brief, country dossier, threat landscape). `generator.py` orchestrates parallel source fetches. Output dir defaults to `$STORAGE_BASE/reports/intel` or `INTEL_REPORT_DIR` env var.
+
+**Static config** (`config/*.py`): Curated datasets — 22 intel hotspots, 70+ military bases, 40 ports, 24 pipelines, 24 nuclear facilities, 34 undersea cables, 48 AI datacenters, 27 spaceports, 27 mineral deposits, 82 stock exchanges, 105 major cities, 28 world leaders, 36 APT groups.
+
+**Dashboard** (`dashboard/`): Self-contained Starlette app with a single `index.html` template (no frontend build step). SSE endpoint streams all domains in parallel via `asyncio.gather()`, refreshes every 30 seconds. Loads `.env` from project root on startup.
## Adding a New Tool
@@ -65,8 +71,7 @@ dashboard/app.py (SSE) ─┘
- **Source name string**: The `source` parameter in `fetcher.get_json()` identifies the API for circuit breaking and rate limiting. Must match entries in `_SOURCE_RATE_LIMITS` if rate-limited (e.g., `"yahoo-finance"`, `"coingecko"`, `"adsblol"`).
- **Tool dispatch**: `server.py` uses Python `match/case` to route tool names to source functions. Tool names follow `intel_*` convention.
- **All source functions take `fetcher` as first arg** — never construct your own httpx client.
-- **Dashboard SSE**: `dashboard/app.py` fetches all domains in parallel via `asyncio.gather()`, streams updates every 30 seconds.
-- **Tests strip proxy env vars** automatically via `conftest.py` fixture (prevents SOCKS proxy interference).
+- **Tests strip proxy env vars** automatically via `conftest.py` fixture (prevents SOCKS proxy interference). The conftest also resets global fetcher rate-limit locks between tests to avoid cross-event-loop binding.
## Environment Variables
diff --git a/src/world_intel_mcp/cache.py b/src/world_intel_mcp/cache.py
index a260244..cd0f4d3 100644
--- a/src/world_intel_mcp/cache.py
+++ b/src/world_intel_mcp/cache.py
@@ -118,6 +118,27 @@ class Cache:
"db_path": str(self.db_path),
}
+ def freshness(self) -> dict[str, Any]:
+ """Per-source freshness report — shows age of newest entry per source prefix."""
+ conn = self._get_conn()
+ now = time.time()
+ rows = conn.execute(
+ "SELECT key, created_at, expires_at FROM cache ORDER BY created_at DESC"
+ ).fetchall()
+ sources: dict[str, dict] = {}
+ for key, created_at, expires_at in rows:
+ # Extract source prefix (e.g., "markets:quotes:^GSPC" → "markets")
+ prefix = key.split(":")[0] if ":" in key else key
+ if prefix not in sources:
+ age_s = now - created_at
+ is_stale = now > expires_at
+ sources[prefix] = {
+ "last_updated_s_ago": round(age_s, 1),
+ "is_stale": is_stale,
+ "newest_key": key,
+ }
+ return sources
+
def close(self) -> None:
if self._conn:
self._conn.close()
diff --git a/src/world_intel_mcp/circuit_breaker.py b/src/world_intel_mcp/circuit_breaker.py
index b72d80a..2c71557 100644
--- a/src/world_intel_mcp/circuit_breaker.py
+++ b/src/world_intel_mcp/circuit_breaker.py
@@ -29,9 +29,11 @@ class CircuitBreaker:
self,
failure_threshold: int = 3,
cooldown_seconds: float = 300.0,
+ per_source_config: dict[str, dict] | None = None,
):
self.failure_threshold = failure_threshold
self.cooldown_seconds = cooldown_seconds
+ self._per_source: dict[str, dict] = per_source_config or {}
self._states: dict[str, _State] = {}
def _get(self, source: str) -> _State:
@@ -39,12 +41,20 @@ class CircuitBreaker:
self._states[source] = _State()
return self._states[source]
+ def _threshold_for(self, source: str) -> int:
+ """Get failure threshold for a specific source."""
+ return self._per_source.get(source, {}).get("failure_threshold", self.failure_threshold)
+
+ def _cooldown_for(self, source: str) -> float:
+ """Get cooldown seconds for a specific source."""
+ return self._per_source.get(source, {}).get("cooldown_seconds", self.cooldown_seconds)
+
def is_available(self, source: str) -> bool:
"""Check if source is available (circuit closed or cooldown elapsed)."""
state = self._get(source)
if not state.is_open:
return True
- if time.time() - state.tripped_at >= self.cooldown_seconds:
+ if time.time() - state.tripped_at >= self._cooldown_for(source):
return True # allow probe
return False
@@ -61,13 +71,13 @@ class CircuitBreaker:
state.failures += 1
state.last_failure = time.time()
state.total_failures += 1
- if state.failures >= self.failure_threshold and not state.is_open:
+ if state.failures >= self._threshold_for(source) and not state.is_open:
state.is_open = True
state.tripped_at = time.time()
state.total_trips += 1
logger.warning(
"Circuit breaker TRIPPED for %s (failures=%d, cooldown=%.0fs)",
- source, state.failures, self.cooldown_seconds,
+ source, state.failures, self._cooldown_for(source),
)
def status(self) -> dict[str, dict]:
@@ -76,7 +86,7 @@ class CircuitBreaker:
result = {}
for source, state in self._states.items():
if state.is_open:
- remaining = max(0, self.cooldown_seconds - (now - state.tripped_at))
+ remaining = max(0, self._cooldown_for(source) - (now - state.tripped_at))
status = "open" if remaining > 0 else "half-open"
else:
remaining = 0
diff --git a/src/world_intel_mcp/config/cables.py b/src/world_intel_mcp/config/cables.py
new file mode 100644
index 0000000..30f605b
--- /dev/null
+++ b/src/world_intel_mcp/config/cables.py
@@ -0,0 +1,294 @@
+"""Undersea cable routes with landing points.
+
+Pure data module — no I/O, no external dependencies.
+Sources: TeleGeography Submarine Cable Map, ITU cable database.
+"""
+
+from __future__ import annotations
+
+UNDERSEA_CABLES: list[dict] = [
+ # Transatlantic
+ {"name": "TAT-14", "status": "decommissioned", "rfs_year": 2001, "length_km": 15428, "capacity_tbps": 3.2,
+ "owners": ["Deutsche Telekom", "AT&T", "Orange"], "landing_points": [
+ {"name": "Tuckerton", "country": "USA", "lat": 39.60, "lon": -74.34},
+ {"name": "Manasquan", "country": "USA", "lat": 40.12, "lon": -74.04},
+ {"name": "Blaabjerg", "country": "Denmark", "lat": 55.60, "lon": 8.12},
+ {"name": "Norden", "country": "Germany", "lat": 53.60, "lon": 7.20},
+ {"name": "Saint-Valery-en-Caux", "country": "France", "lat": 49.87, "lon": 0.72},
+ {"name": "Bude", "country": "UK", "lat": 50.83, "lon": -4.55},
+ ]},
+ {"name": "MAREA", "status": "active", "rfs_year": 2018, "length_km": 6600, "capacity_tbps": 200,
+ "owners": ["Microsoft", "Meta", "Telxius"], "landing_points": [
+ {"name": "Virginia Beach", "country": "USA", "lat": 36.85, "lon": -75.98},
+ {"name": "Bilbao", "country": "Spain", "lat": 43.26, "lon": -2.93},
+ ]},
+ {"name": "Dunant", "status": "active", "rfs_year": 2021, "length_km": 6600, "capacity_tbps": 250,
+ "owners": ["Google"], "landing_points": [
+ {"name": "Virginia Beach", "country": "USA", "lat": 36.85, "lon": -75.98},
+ {"name": "Saint-Hilaire-de-Riez", "country": "France", "lat": 46.72, "lon": -1.95},
+ ]},
+ {"name": "Amitié", "status": "active", "rfs_year": 2022, "length_km": 6800, "capacity_tbps": 400,
+ "owners": ["Google", "Meta", "Lumen"], "landing_points": [
+ {"name": "Lynn", "country": "USA", "lat": 42.47, "lon": -70.95},
+ {"name": "Bude", "country": "UK", "lat": 50.83, "lon": -4.55},
+ {"name": "Le Porge", "country": "France", "lat": 44.87, "lon": -1.16},
+ ]},
+ {"name": "AEC-1 (Anjana/East)", "status": "active", "rfs_year": 2002, "length_km": 7200, "capacity_tbps": 5.1,
+ "owners": ["Telia", "Aqua Comms"], "landing_points": [
+ {"name": "Shirley", "country": "USA", "lat": 40.80, "lon": -72.87},
+ {"name": "Killala", "country": "Ireland", "lat": 54.21, "lon": -9.22},
+ {"name": "Blaabjerg", "country": "Denmark", "lat": 55.60, "lon": 8.12},
+ ]},
+ {"name": "HAVFRUE/AEC-2", "status": "active", "rfs_year": 2020, "length_km": 7860, "capacity_tbps": 108,
+ "owners": ["Google", "Aqua Comms", "Bulk"], "landing_points": [
+ {"name": "Wall Township", "country": "USA", "lat": 40.16, "lon": -74.07},
+ {"name": "Blaabjerg", "country": "Denmark", "lat": 55.60, "lon": 8.12},
+ {"name": "Kristiansand", "country": "Norway", "lat": 58.15, "lon": 8.00},
+ {"name": "Killala", "country": "Ireland", "lat": 54.21, "lon": -9.22},
+ ]},
+ {"name": "Grace Hopper", "status": "active", "rfs_year": 2022, "length_km": 6300, "capacity_tbps": 340,
+ "owners": ["Google"], "landing_points": [
+ {"name": "New York", "country": "USA", "lat": 40.57, "lon": -73.97},
+ {"name": "Bude", "country": "UK", "lat": 50.83, "lon": -4.55},
+ {"name": "Bilbao", "country": "Spain", "lat": 43.26, "lon": -2.93},
+ ]},
+ # Transpacific
+ {"name": "JUPITER", "status": "active", "rfs_year": 2020, "length_km": 14000, "capacity_tbps": 60,
+ "owners": ["Google", "Meta", "PLDT", "SoftBank"], "landing_points": [
+ {"name": "Virginia Beach", "country": "USA", "lat": 36.85, "lon": -75.98},
+ {"name": "Daet", "country": "Philippines", "lat": 14.11, "lon": 122.96},
+ {"name": "Maruyama", "country": "Japan", "lat": 33.48, "lon": 135.76},
+ ]},
+ {"name": "FASTER", "status": "active", "rfs_year": 2016, "length_km": 11629, "capacity_tbps": 60,
+ "owners": ["Google", "China Mobile", "KDDI", "SingTel"], "landing_points": [
+ {"name": "Bandon", "country": "USA", "lat": 43.12, "lon": -124.41},
+ {"name": "Chikura", "country": "Japan", "lat": 34.93, "lon": 139.95},
+ {"name": "Shima", "country": "Japan", "lat": 34.33, "lon": 136.83},
+ ]},
+ {"name": "Curie", "status": "active", "rfs_year": 2019, "length_km": 10476, "capacity_tbps": 72,
+ "owners": ["Google"], "landing_points": [
+ {"name": "Los Angeles", "country": "USA", "lat": 33.94, "lon": -118.45},
+ {"name": "Valparaiso", "country": "Chile", "lat": -33.04, "lon": -71.63},
+ ]},
+ {"name": "Firmina", "status": "active", "rfs_year": 2023, "length_km": 14000, "capacity_tbps": 340,
+ "owners": ["Google"], "landing_points": [
+ {"name": "Myrtle Beach", "country": "USA", "lat": 33.69, "lon": -78.89},
+ {"name": "Praia Grande", "country": "Brazil", "lat": -24.01, "lon": -46.41},
+ {"name": "Las Toninas", "country": "Argentina", "lat": -36.48, "lon": -56.70},
+ ]},
+ {"name": "PLCN (Pacific Light Cable Network)", "status": "active", "rfs_year": 2023, "length_km": 12800, "capacity_tbps": 144,
+ "owners": ["Google", "Meta"], "landing_points": [
+ {"name": "El Segundo", "country": "USA", "lat": 33.92, "lon": -118.42},
+ {"name": "Changi", "country": "Singapore", "lat": 1.35, "lon": 104.00},
+ {"name": "Tanah Merah", "country": "Indonesia", "lat": -6.17, "lon": 106.97},
+ ]},
+ # Asia-Europe (SEA-ME-WE family)
+ {"name": "SEA-ME-WE 3", "status": "active", "rfs_year": 1999, "length_km": 39000, "capacity_tbps": 0.96,
+ "owners": ["Consortium (76 telcos)"], "landing_points": [
+ {"name": "Norden", "country": "Germany", "lat": 53.60, "lon": 7.20},
+ {"name": "Penmarc'h", "country": "France", "lat": 47.80, "lon": -4.37},
+ {"name": "Tetney", "country": "UK", "lat": 53.50, "lon": 0.02},
+ {"name": "Suez", "country": "Egypt", "lat": 29.97, "lon": 32.55},
+ {"name": "Mumbai", "country": "India", "lat": 18.93, "lon": 72.83},
+ {"name": "Singapore", "country": "Singapore", "lat": 1.26, "lon": 103.82},
+ {"name": "Shanghai", "country": "China", "lat": 31.05, "lon": 121.72},
+ {"name": "Keoje", "country": "South Korea", "lat": 34.88, "lon": 128.70},
+ {"name": "Okinawa", "country": "Japan", "lat": 26.34, "lon": 127.77},
+ ]},
+ {"name": "SEA-ME-WE 5", "status": "active", "rfs_year": 2017, "length_km": 20000, "capacity_tbps": 24,
+ "owners": ["Consortium (17 telcos)"], "landing_points": [
+ {"name": "Marseille", "country": "France", "lat": 43.30, "lon": 5.37},
+ {"name": "Catania", "country": "Italy", "lat": 37.50, "lon": 15.09},
+ {"name": "Alexandria", "country": "Egypt", "lat": 31.20, "lon": 29.92},
+ {"name": "Jeddah", "country": "Saudi Arabia", "lat": 21.49, "lon": 39.19},
+ {"name": "Mumbai", "country": "India", "lat": 18.93, "lon": 72.83},
+ {"name": "Singapore", "country": "Singapore", "lat": 1.26, "lon": 103.82},
+ ]},
+ {"name": "SEA-ME-WE 6", "status": "construction", "rfs_year": 2025, "length_km": 19200, "capacity_tbps": 100,
+ "owners": ["Consortium (12 telcos)"], "landing_points": [
+ {"name": "Marseille", "country": "France", "lat": 43.30, "lon": 5.37},
+ {"name": "Genoa", "country": "Italy", "lat": 44.41, "lon": 8.93},
+ {"name": "Alexandria", "country": "Egypt", "lat": 31.20, "lon": 29.92},
+ {"name": "Singapore", "country": "Singapore", "lat": 1.26, "lon": 103.82},
+ ]},
+ # Africa
+ {"name": "2Africa", "status": "active", "rfs_year": 2024, "length_km": 45000, "capacity_tbps": 180,
+ "owners": ["Meta", "MTN", "Orange", "Vodafone", "China Mobile"], "landing_points": [
+ {"name": "Genoa", "country": "Italy", "lat": 44.41, "lon": 8.93},
+ {"name": "Barcelona", "country": "Spain", "lat": 41.39, "lon": 2.16},
+ {"name": "Marseille", "country": "France", "lat": 43.30, "lon": 5.37},
+ {"name": "Bude", "country": "UK", "lat": 50.83, "lon": -4.55},
+ {"name": "Dakar", "country": "Senegal", "lat": 14.69, "lon": -17.47},
+ {"name": "Lagos", "country": "Nigeria", "lat": 6.45, "lon": 3.39},
+ {"name": "Cape Town", "country": "South Africa", "lat": -33.92, "lon": 18.42},
+ {"name": "Maputo", "country": "Mozambique", "lat": -25.97, "lon": 32.57},
+ {"name": "Mombasa", "country": "Kenya", "lat": -4.04, "lon": 39.67},
+ {"name": "Djibouti", "country": "Djibouti", "lat": 11.55, "lon": 43.15},
+ {"name": "Jeddah", "country": "Saudi Arabia", "lat": 21.49, "lon": 39.19},
+ {"name": "Mumbai", "country": "India", "lat": 18.93, "lon": 72.83},
+ ]},
+ {"name": "Equiano", "status": "active", "rfs_year": 2023, "length_km": 15000, "capacity_tbps": 144,
+ "owners": ["Google"], "landing_points": [
+ {"name": "Sesimbra", "country": "Portugal", "lat": 38.44, "lon": -9.10},
+ {"name": "Lagos", "country": "Nigeria", "lat": 6.45, "lon": 3.39},
+ {"name": "Lomé", "country": "Togo", "lat": 6.13, "lon": 1.22},
+ {"name": "Swakopmund", "country": "Namibia", "lat": -22.68, "lon": 14.53},
+ {"name": "Melkbosstrand", "country": "South Africa", "lat": -33.72, "lon": 18.44},
+ ]},
+ # PEACE Cable
+ {"name": "PEACE", "status": "active", "rfs_year": 2022, "length_km": 15000, "capacity_tbps": 96,
+ "owners": ["PEACE Cable International"], "landing_points": [
+ {"name": "Marseille", "country": "France", "lat": 43.30, "lon": 5.37},
+ {"name": "Karachi", "country": "Pakistan", "lat": 24.86, "lon": 67.01},
+ {"name": "Mombasa", "country": "Kenya", "lat": -4.04, "lon": 39.67},
+ {"name": "Seychelles", "country": "Seychelles", "lat": -4.68, "lon": 55.49},
+ {"name": "Singapore", "country": "Singapore", "lat": 1.26, "lon": 103.82},
+ ]},
+ # Asia-Pacific
+ {"name": "APG (Asia-Pacific Gateway)", "status": "active", "rfs_year": 2016, "length_km": 10400, "capacity_tbps": 54.8,
+ "owners": ["Consortium (12 telcos)"], "landing_points": [
+ {"name": "Chongming", "country": "China", "lat": 31.62, "lon": 121.73},
+ {"name": "Hong Kong", "country": "China", "lat": 22.25, "lon": 114.17},
+ {"name": "Taipei", "country": "Taiwan", "lat": 25.16, "lon": 121.74},
+ {"name": "Maruyama", "country": "Japan", "lat": 33.48, "lon": 135.76},
+ {"name": "Changi", "country": "Singapore", "lat": 1.35, "lon": 104.00},
+ {"name": "Da Nang", "country": "Vietnam", "lat": 16.07, "lon": 108.22},
+ {"name": "Kuantan", "country": "Malaysia", "lat": 3.80, "lon": 103.33},
+ ]},
+ {"name": "SJC (Southeast Asia-Japan Cable)", "status": "active", "rfs_year": 2013, "length_km": 8900, "capacity_tbps": 28,
+ "owners": ["Google", "Meta", "KDDI", "SingTel", "PLDT"], "landing_points": [
+ {"name": "Maruyama", "country": "Japan", "lat": 33.48, "lon": 135.76},
+ {"name": "Shantou", "country": "China", "lat": 23.35, "lon": 116.68},
+ {"name": "Hong Kong", "country": "China", "lat": 22.25, "lon": 114.17},
+ {"name": "Changi", "country": "Singapore", "lat": 1.35, "lon": 104.00},
+ ]},
+ {"name": "SJC2", "status": "active", "rfs_year": 2022, "length_km": 10500, "capacity_tbps": 144,
+ "owners": ["Meta", "Google", "SingTel", "PLDT", "Telin"], "landing_points": [
+ {"name": "Shima", "country": "Japan", "lat": 34.33, "lon": 136.83},
+ {"name": "Chikura", "country": "Japan", "lat": 34.93, "lon": 139.95},
+ {"name": "Taiwan", "country": "Taiwan", "lat": 25.16, "lon": 121.74},
+ {"name": "Changi", "country": "Singapore", "lat": 1.35, "lon": 104.00},
+ {"name": "Manado", "country": "Indonesia", "lat": 1.49, "lon": 124.84},
+ ]},
+ # Middle East
+ {"name": "FLAG Europe-Asia (FEA)", "status": "active", "rfs_year": 1997, "length_km": 28000, "capacity_tbps": 10,
+ "owners": ["Reliance Globalcom"], "landing_points": [
+ {"name": "Porthcurno", "country": "UK", "lat": 50.04, "lon": -5.66},
+ {"name": "Estepona", "country": "Spain", "lat": 36.43, "lon": -5.15},
+ {"name": "Palermo", "country": "Italy", "lat": 38.12, "lon": 13.36},
+ {"name": "Port Said", "country": "Egypt", "lat": 31.26, "lon": 32.30},
+ {"name": "Mumbai", "country": "India", "lat": 18.93, "lon": 72.83},
+ {"name": "Busan", "country": "South Korea", "lat": 35.10, "lon": 129.04},
+ {"name": "Miura", "country": "Japan", "lat": 35.14, "lon": 139.62},
+ ]},
+ {"name": "AAE-1", "status": "active", "rfs_year": 2017, "length_km": 25000, "capacity_tbps": 40,
+ "owners": ["Consortium (19 telcos)"], "landing_points": [
+ {"name": "Marseille", "country": "France", "lat": 43.30, "lon": 5.37},
+ {"name": "Bari", "country": "Italy", "lat": 41.13, "lon": 16.87},
+ {"name": "Alexandria", "country": "Egypt", "lat": 31.20, "lon": 29.92},
+ {"name": "Djibouti", "country": "Djibouti", "lat": 11.55, "lon": 43.15},
+ {"name": "Mumbai", "country": "India", "lat": 18.93, "lon": 72.83},
+ {"name": "Singapore", "country": "Singapore", "lat": 1.26, "lon": 103.82},
+ {"name": "Hong Kong", "country": "China", "lat": 22.25, "lon": 114.17},
+ ]},
+ {"name": "FALCON", "status": "active", "rfs_year": 2006, "length_km": 11500, "capacity_tbps": 4.7,
+ "owners": ["FLAG Telecom (Reliance)"], "landing_points": [
+ {"name": "Mumbai", "country": "India", "lat": 18.93, "lon": 72.83},
+ {"name": "Fujairah", "country": "UAE", "lat": 25.12, "lon": 56.35},
+ {"name": "Muscat", "country": "Oman", "lat": 23.59, "lon": 58.54},
+ {"name": "Jeddah", "country": "Saudi Arabia", "lat": 21.49, "lon": 39.19},
+ {"name": "Suez", "country": "Egypt", "lat": 29.97, "lon": 32.55},
+ ]},
+ # Arctic / North
+ {"name": "Far North Fiber", "status": "construction", "rfs_year": 2027, "length_km": 16500, "capacity_tbps": 200,
+ "owners": ["Far North Digital", "Cinia"], "landing_points": [
+ {"name": "Tokyo", "country": "Japan", "lat": 35.65, "lon": 139.84},
+ {"name": "Kirkenes", "country": "Norway", "lat": 69.73, "lon": 30.05},
+ {"name": "Dublin", "country": "Ireland", "lat": 53.35, "lon": -6.26},
+ {"name": "Nome", "country": "USA", "lat": 64.50, "lon": -165.41},
+ ]},
+ # Australia
+ {"name": "JGA-S (Japan-Guam-Australia South)", "status": "active", "rfs_year": 2020, "length_km": 7100, "capacity_tbps": 36,
+ "owners": ["Google", "AARNet", "Indosat"], "landing_points": [
+ {"name": "Shima", "country": "Japan", "lat": 34.33, "lon": 136.83},
+ {"name": "Piti", "country": "Guam", "lat": 13.46, "lon": 144.70},
+ {"name": "Sydney", "country": "Australia", "lat": -33.87, "lon": 151.21},
+ ]},
+ {"name": "Indigo", "status": "active", "rfs_year": 2019, "length_km": 9600, "capacity_tbps": 36,
+ "owners": ["Google", "AARNet", "Indosat", "SingTel", "SubPartners"], "landing_points": [
+ {"name": "Perth", "country": "Australia", "lat": -31.95, "lon": 115.86},
+ {"name": "Singapore", "country": "Singapore", "lat": 1.26, "lon": 103.82},
+ {"name": "Jakarta", "country": "Indonesia", "lat": -6.13, "lon": 106.85},
+ ]},
+ # South America
+ {"name": "SACS (South Atlantic Cable System)", "status": "active", "rfs_year": 2018, "length_km": 6200, "capacity_tbps": 40,
+ "owners": ["Angola Cables"], "landing_points": [
+ {"name": "Luanda", "country": "Angola", "lat": -8.84, "lon": 13.23},
+ {"name": "Fortaleza", "country": "Brazil", "lat": -3.72, "lon": -38.52},
+ ]},
+ {"name": "EllaLink", "status": "active", "rfs_year": 2021, "length_km": 12000, "capacity_tbps": 72,
+ "owners": ["EllaLink"], "landing_points": [
+ {"name": "Sines", "country": "Portugal", "lat": 37.96, "lon": -8.87},
+ {"name": "Marseille", "country": "France", "lat": 43.30, "lon": 5.37},
+ {"name": "Fortaleza", "country": "Brazil", "lat": -3.72, "lon": -38.52},
+ ]},
+ # India-specific
+ {"name": "India-Asia-Xpress (IAX)", "status": "active", "rfs_year": 2024, "length_km": 7800, "capacity_tbps": 120,
+ "owners": ["Reliance Jio", "Meta"], "landing_points": [
+ {"name": "Mumbai", "country": "India", "lat": 18.93, "lon": 72.83},
+ {"name": "Singapore", "country": "Singapore", "lat": 1.26, "lon": 103.82},
+ {"name": "Kuantan", "country": "Malaysia", "lat": 3.80, "lon": 103.33},
+ {"name": "Satun", "country": "Thailand", "lat": 6.62, "lon": 100.07},
+ ]},
+ # North Pacific
+ {"name": "NCP (New Cross Pacific)", "status": "active", "rfs_year": 2018, "length_km": 13600, "capacity_tbps": 80,
+ "owners": ["Microsoft", "Meta", "Amazon", "SoftBank", "PLDT"], "landing_points": [
+ {"name": "Hillsboro", "country": "USA", "lat": 45.53, "lon": -122.99},
+ {"name": "Maruyama", "country": "Japan", "lat": 33.48, "lon": 135.76},
+ {"name": "Chongming", "country": "China", "lat": 31.62, "lon": 121.73},
+ {"name": "Taipei", "country": "Taiwan", "lat": 25.16, "lon": 121.74},
+ ]},
+ {"name": "Unity", "status": "active", "rfs_year": 2010, "length_km": 10000, "capacity_tbps": 7.68,
+ "owners": ["Google", "KDDI"], "landing_points": [
+ {"name": "Chikura", "country": "Japan", "lat": 34.93, "lon": 139.95},
+ {"name": "Los Angeles", "country": "USA", "lat": 33.94, "lon": -118.45},
+ ]},
+ # Mediterranean
+ {"name": "Blue & Raman", "status": "active", "rfs_year": 2024, "length_km": 16000, "capacity_tbps": 250,
+ "owners": ["Google"], "landing_points": [
+ {"name": "Genoa", "country": "Italy", "lat": 44.41, "lon": 8.93},
+ {"name": "Haifa", "country": "Israel", "lat": 32.82, "lon": 34.98},
+ {"name": "Mumbai", "country": "India", "lat": 18.93, "lon": 72.83},
+ {"name": "Amman", "country": "Jordan", "lat": 31.95, "lon": 35.93},
+ ]},
+ {"name": "Oman Australia Cable (OAC)", "status": "active", "rfs_year": 2022, "length_km": 9800, "capacity_tbps": 100,
+ "owners": ["Oman Broadband", "SubPartners"], "landing_points": [
+ {"name": "Barka", "country": "Oman", "lat": 23.68, "lon": 57.88},
+ {"name": "Perth", "country": "Australia", "lat": -31.95, "lon": 115.86},
+ ]},
+]
+
+
+def query_cables(
+ status: str | None = None,
+ country: str | None = None,
+ owner: str | None = None,
+ min_capacity_tbps: float | None = None,
+) -> list[dict]:
+ """Filter undersea cables by status, landing country, owner, or min capacity."""
+ results = []
+ for cable in UNDERSEA_CABLES:
+ if status and cable["status"] != status.lower():
+ continue
+ if min_capacity_tbps and cable["capacity_tbps"] < min_capacity_tbps:
+ continue
+ if owner:
+ owner_lower = owner.lower()
+ if not any(owner_lower in o.lower() for o in cable["owners"]):
+ continue
+ if country:
+ country_lower = country.lower()
+ if not any(country_lower in lp["country"].lower() for lp in cable["landing_points"]):
+ continue
+ results.append(cable)
+ return results
diff --git a/src/world_intel_mcp/config/datacenters.py b/src/world_intel_mcp/config/datacenters.py
new file mode 100644
index 0000000..aa3191a
--- /dev/null
+++ b/src/world_intel_mcp/config/datacenters.py
@@ -0,0 +1,91 @@
+"""AI datacenter clusters worldwide.
+
+Pure data module — no I/O, no external dependencies.
+Sources: Data Center Map, Cloudscene, company announcements, press reports.
+"""
+
+from __future__ import annotations
+
+AI_DATACENTERS: list[dict] = [
+ # United States — Top clusters
+ {"name": "Ashburn / Loudoun County", "country": "USA", "iso3": "USA", "lat": 39.04, "lon": -77.49, "region": "Virginia", "power_mw": 4500, "operators": ["AWS", "Microsoft", "Google", "Meta", "Equinix", "Digital Realty"], "notes": "Data Center Alley — largest concentration globally, ~70% of world internet traffic"},
+ {"name": "Dallas / Fort Worth", "country": "USA", "iso3": "USA", "lat": 32.90, "lon": -97.04, "region": "Texas", "power_mw": 2200, "operators": ["AWS", "Meta", "Google", "CyrusOne", "QTS"], "notes": "Second largest US cluster, low power costs"},
+ {"name": "Phoenix / Mesa", "country": "USA", "iso3": "USA", "lat": 33.44, "lon": -111.94, "region": "Arizona", "power_mw": 1800, "operators": ["Microsoft", "Google", "Meta", "Apple"], "notes": "Rapid expansion, solar power access"},
+ {"name": "Council Bluffs", "country": "USA", "iso3": "USA", "lat": 41.26, "lon": -95.86, "region": "Iowa", "power_mw": 1200, "operators": ["Google", "Meta"], "notes": "Google mega-campus, cheap wind power"},
+ {"name": "The Dalles", "country": "USA", "iso3": "USA", "lat": 45.60, "lon": -121.18, "region": "Oregon", "power_mw": 900, "operators": ["Google"], "notes": "Google flagship, hydroelectric cooling"},
+ {"name": "Quincy", "country": "USA", "iso3": "USA", "lat": 47.23, "lon": -119.85, "region": "Washington", "power_mw": 800, "operators": ["Microsoft", "Yahoo", "Dell"], "notes": "Columbia River hydro power"},
+ {"name": "Prineville", "country": "USA", "iso3": "USA", "lat": 44.30, "lon": -120.73, "region": "Oregon", "power_mw": 600, "operators": ["Meta", "Apple"], "notes": "Meta's first custom DC"},
+ {"name": "San Jose / Santa Clara", "country": "USA", "iso3": "USA", "lat": 37.35, "lon": -121.95, "region": "California", "power_mw": 1500, "operators": ["Equinix", "CoreSite", "NVIDIA", "Google"], "notes": "Silicon Valley interconnection hub"},
+ {"name": "Chicago / Aurora", "country": "USA", "iso3": "USA", "lat": 41.76, "lon": -88.32, "region": "Illinois", "power_mw": 1100, "operators": ["AWS", "Microsoft", "Google", "Digital Realty"], "notes": "Midwest hub, CME/CBOE proximity"},
+ {"name": "Atlanta / Douglas County", "country": "USA", "iso3": "USA", "lat": 33.70, "lon": -84.75, "region": "Georgia", "power_mw": 900, "operators": ["Google", "Microsoft", "QTS", "Switch"], "notes": "Southeast US hub"},
+ {"name": "Salt Lake City / West Jordan", "country": "USA", "iso3": "USA", "lat": 40.61, "lon": -111.94, "region": "Utah", "power_mw": 600, "operators": ["Meta", "AWS", "C7"], "notes": "NSA Utah Data Center nearby"},
+ {"name": "Reno / Sparks", "country": "USA", "iso3": "USA", "lat": 39.53, "lon": -119.81, "region": "Nevada", "power_mw": 500, "operators": ["Apple", "Switch", "Microsoft"], "notes": "Switch SuperNAP campus"},
+ {"name": "New Albany", "country": "USA", "iso3": "USA", "lat": 40.08, "lon": -82.81, "region": "Ohio", "power_mw": 1400, "operators": ["Google", "AWS", "Meta", "Microsoft"], "notes": "Ohio Intel fab synergy, massive expansion"},
+ {"name": "Papillion / Omaha", "country": "USA", "iso3": "USA", "lat": 41.15, "lon": -96.04, "region": "Nebraska", "power_mw": 500, "operators": ["Meta", "Google"], "notes": "Central US, wind/solar access"},
+ # Europe
+ {"name": "Amsterdam / Schiphol", "country": "Netherlands", "iso3": "NLD", "lat": 52.30, "lon": 4.76, "region": "North Holland", "power_mw": 800, "operators": ["Equinix", "Digital Realty", "Microsoft"], "notes": "AMS-IX — world's largest IXP, moratorium on new DCs"},
+ {"name": "Dublin", "country": "Ireland", "iso3": "IRL", "lat": 53.35, "lon": -6.26, "region": "Leinster", "power_mw": 900, "operators": ["AWS", "Microsoft", "Google", "Meta"], "notes": "EU data sovereignty hub, tax advantages"},
+ {"name": "Frankfurt", "country": "Germany", "iso3": "DEU", "lat": 50.11, "lon": 8.68, "region": "Hesse", "power_mw": 700, "operators": ["Equinix", "Digital Realty", "AWS", "Google"], "notes": "DE-CIX — largest IXP by members"},
+ {"name": "London / Slough", "country": "UK", "iso3": "GBR", "lat": 51.51, "lon": -0.59, "region": "Berkshire", "power_mw": 900, "operators": ["Equinix", "Digital Realty", "AWS", "Google"], "notes": "LINX, financial sector hub"},
+ {"name": "Paris / Île-de-France", "country": "France", "iso3": "FRA", "lat": 48.95, "lon": 2.40, "region": "Île-de-France", "power_mw": 500, "operators": ["Equinix", "Digital Realty", "OVHcloud"], "notes": "France-IX hub"},
+ {"name": "Stockholm / Rosersberg", "country": "Sweden", "iso3": "SWE", "lat": 59.60, "lon": 17.88, "region": "Stockholm", "power_mw": 400, "operators": ["AWS", "Microsoft", "Ericsson"], "notes": "Nordic hub, cold climate cooling"},
+ {"name": "Milan", "country": "Italy", "iso3": "ITA", "lat": 45.46, "lon": 9.19, "region": "Lombardy", "power_mw": 300, "operators": ["Equinix", "AWS", "Google"], "notes": "Southern Europe hub"},
+ {"name": "Madrid", "country": "Spain", "iso3": "ESP", "lat": 40.42, "lon": -3.70, "region": "Community of Madrid", "power_mw": 350, "operators": ["AWS", "Microsoft", "Google"], "notes": "Iberian expansion"},
+ {"name": "Luleå", "country": "Sweden", "iso3": "SWE", "lat": 65.58, "lon": 22.15, "region": "Norrbotten", "power_mw": 200, "operators": ["Meta"], "notes": "Arctic cooling, hydro power, first Meta EU DC"},
+ {"name": "Hamina", "country": "Finland", "iso3": "FIN", "lat": 60.57, "lon": 27.20, "region": "Kymenlaakso", "power_mw": 200, "operators": ["Google"], "notes": "Seawater cooling from Baltic"},
+ # Asia-Pacific
+ {"name": "Singapore / Jurong", "country": "Singapore", "iso3": "SGP", "lat": 1.33, "lon": 103.74, "region": "West Region", "power_mw": 700, "operators": ["Equinix", "Digital Realty", "AWS", "Google"], "notes": "APAC hub, moratorium lifted 2022 with green requirements"},
+ {"name": "Tokyo / Inzai", "country": "Japan", "iso3": "JPN", "lat": 35.83, "lon": 140.14, "region": "Chiba", "power_mw": 900, "operators": ["Equinix", "AWS", "Google", "NTT"], "notes": "Asia's largest market"},
+ {"name": "Seoul / Gasan", "country": "South Korea", "iso3": "KOR", "lat": 37.48, "lon": 126.88, "region": "Seoul", "power_mw": 400, "operators": ["AWS", "Google", "Samsung SDS", "KT"], "notes": "Korean DC cluster, 5G synergy"},
+ {"name": "Mumbai / Navi Mumbai", "country": "India", "iso3": "IND", "lat": 19.06, "lon": 73.01, "region": "Maharashtra", "power_mw": 600, "operators": ["AWS", "Microsoft", "Google", "Reliance Jio", "Adani"], "notes": "India's primary DC hub, submarine cable landing"},
+ {"name": "Chennai / Ambattur", "country": "India", "iso3": "IND", "lat": 13.11, "lon": 80.15, "region": "Tamil Nadu", "power_mw": 350, "operators": ["AWS", "Microsoft", "NTT"], "notes": "Second India hub, cable landing site"},
+ {"name": "Sydney / Western Sydney", "country": "Australia", "iso3": "AUS", "lat": -33.80, "lon": 150.90, "region": "NSW", "power_mw": 500, "operators": ["Equinix", "AWS", "Microsoft", "Google"], "notes": "Australia's primary hub"},
+ {"name": "Hong Kong / Tseung Kwan O", "country": "China", "iso3": "HKG", "lat": 22.31, "lon": 114.26, "region": "New Territories", "power_mw": 400, "operators": ["Equinix", "SUNeVision", "NTT"], "notes": "Asia financial connectivity hub"},
+ {"name": "Beijing / Zhongguancun", "country": "China", "iso3": "CHN", "lat": 39.98, "lon": 116.30, "region": "Beijing", "power_mw": 800, "operators": ["Alibaba", "Tencent", "Baidu", "ByteDance"], "notes": "China's AI research center"},
+ {"name": "Shanghai / Lingang", "country": "China", "iso3": "CHN", "lat": 30.89, "lon": 121.93, "region": "Shanghai", "power_mw": 700, "operators": ["Alibaba", "Tencent", "AWS (via partners)"], "notes": "East China financial hub"},
+ {"name": "Guizhou / Guiyang", "country": "China", "iso3": "CHN", "lat": 26.65, "lon": 106.63, "region": "Guizhou", "power_mw": 500, "operators": ["Alibaba", "Huawei", "Tencent", "Apple"], "notes": "Mountain cooling, Apple iCloud China"},
+ {"name": "Zhangbei / Hebei", "country": "China", "iso3": "CHN", "lat": 41.15, "lon": 114.70, "region": "Hebei", "power_mw": 600, "operators": ["Alibaba"], "notes": "Cold climate DC cluster, near Beijing"},
+ {"name": "Ulanqab / Inner Mongolia", "country": "China", "iso3": "CHN", "lat": 41.00, "lon": 113.13, "region": "Inner Mongolia", "power_mw": 500, "operators": ["Alibaba", "Huawei"], "notes": "Cold climate, wind/solar power"},
+ {"name": "Jakarta / Cibitung", "country": "Indonesia", "iso3": "IDN", "lat": -6.27, "lon": 107.09, "region": "West Java", "power_mw": 300, "operators": ["AWS", "Google", "Telkom"], "notes": "Indonesia hub, fast growth"},
+ {"name": "Johor Bahru", "country": "Malaysia", "iso3": "MYS", "lat": 1.49, "lon": 103.74, "region": "Johor", "power_mw": 500, "operators": ["Microsoft", "Google", "Amazon", "ByteDance"], "notes": "Singapore spillover, massive 2024-25 expansion"},
+ # Middle East
+ {"name": "Dubai / Jebel Ali", "country": "UAE", "iso3": "ARE", "lat": 24.99, "lon": 55.06, "region": "Dubai", "power_mw": 300, "operators": ["AWS", "Microsoft", "Oracle", "Equinix"], "notes": "MENA hub"},
+ {"name": "Riyadh", "country": "Saudi Arabia", "iso3": "SAU", "lat": 24.71, "lon": 46.67, "region": "Riyadh Province", "power_mw": 300, "operators": ["AWS", "Google", "Oracle", "STC"], "notes": "Vision 2030 DC investment"},
+ {"name": "Tel Aviv / Haifa", "country": "Israel", "iso3": "ISR", "lat": 32.07, "lon": 34.77, "region": "Tel Aviv", "power_mw": 200, "operators": ["AWS", "Google", "Microsoft", "Equinix"], "notes": "Israel tech hub, Blue/Raman cable landing"},
+ # South America
+ {"name": "São Paulo / Barueri", "country": "Brazil", "iso3": "BRA", "lat": -23.51, "lon": -46.88, "region": "São Paulo", "power_mw": 500, "operators": ["Equinix", "Digital Realty", "AWS", "Google"], "notes": "Latin America's largest DC market"},
+ {"name": "Santiago", "country": "Chile", "iso3": "CHL", "lat": -33.45, "lon": -70.65, "region": "Metropolitana", "power_mw": 150, "operators": ["Google", "AWS", "Huawei"], "notes": "Pacific cable landing, Curie cable"},
+ {"name": "Querétaro", "country": "Mexico", "iso3": "MEX", "lat": 20.59, "lon": -100.39, "region": "Querétaro", "power_mw": 200, "operators": ["Google", "AWS", "Equinix", "KIO Networks"], "notes": "Mexico DC hub"},
+ # Africa
+ {"name": "Johannesburg / Isando", "country": "South Africa", "iso3": "ZAF", "lat": -26.14, "lon": 28.20, "region": "Gauteng", "power_mw": 200, "operators": ["Teraco", "AWS", "Microsoft"], "notes": "Africa's primary DC hub, NAPAfrica IXP"},
+ {"name": "Nairobi", "country": "Kenya", "iso3": "KEN", "lat": -1.29, "lon": 36.82, "region": "Nairobi", "power_mw": 80, "operators": ["AWS", "Microsoft", "Liquid Intelligent"], "notes": "East Africa hub"},
+ {"name": "Lagos / Lekki", "country": "Nigeria", "iso3": "NGA", "lat": 6.43, "lon": 3.43, "region": "Lagos", "power_mw": 70, "operators": ["Rack Centre", "Africa Data Centres"], "notes": "West Africa's largest, 2Africa cable landing"},
+ # Nordics / Specialty
+ {"name": "Reykjavik / Fitjar", "country": "Iceland", "iso3": "ISL", "lat": 64.05, "lon": -21.95, "region": "Capital Region", "power_mw": 100, "operators": ["Verne Global", "atNorth"], "notes": "100% geothermal/hydro, natural cooling"},
+]
+
+
+def query_datacenters(
+ country: str | None = None,
+ operator: str | None = None,
+ min_power_mw: int | None = None,
+ region: str | None = None,
+) -> list[dict]:
+ """Filter AI datacenters by country, operator, minimum power, or region."""
+ results = []
+ for dc in AI_DATACENTERS:
+ if country:
+ c = country.lower()
+ if c not in (dc["country"].lower(), dc["iso3"].lower()):
+ continue
+ if min_power_mw and dc["power_mw"] < min_power_mw:
+ continue
+ if operator:
+ op_lower = operator.lower()
+ if not any(op_lower in o.lower() for o in dc["operators"]):
+ continue
+ if region:
+ if region.lower() not in dc["region"].lower():
+ continue
+ results.append(dc)
+ return results
diff --git a/src/world_intel_mcp/config/exchanges.py b/src/world_intel_mcp/config/exchanges.py
new file mode 100644
index 0000000..769d87e
--- /dev/null
+++ b/src/world_intel_mcp/config/exchanges.py
@@ -0,0 +1,132 @@
+"""Global stock exchanges dataset.
+
+Pure data module — no I/O, no external dependencies.
+Sources: World Federation of Exchanges, GFCI, World Bank, press.
+92 exchanges organized by tier (mega, major, emerging, frontier).
+"""
+
+from __future__ import annotations
+
+STOCK_EXCHANGES: list[dict] = [
+ # Mega exchanges (>$3T market cap)
+ {"name": "New York Stock Exchange", "acronym": "NYSE", "country": "USA", "iso3": "USA", "city": "New York", "lat": 40.71, "lon": -74.01, "tier": "mega", "market_cap_usd_t": 28.4, "index": "^DJI", "currency": "USD", "timezone": "America/New_York"},
+ {"name": "NASDAQ", "acronym": "NASDAQ", "country": "USA", "iso3": "USA", "city": "New York", "lat": 40.76, "lon": -73.99, "tier": "mega", "market_cap_usd_t": 25.5, "index": "^IXIC", "currency": "USD", "timezone": "America/New_York"},
+ {"name": "Shanghai Stock Exchange", "acronym": "SSE", "country": "China", "iso3": "CHN", "city": "Shanghai", "lat": 31.23, "lon": 121.47, "tier": "mega", "market_cap_usd_t": 6.9, "index": "000001.SS", "currency": "CNY", "timezone": "Asia/Shanghai"},
+ {"name": "Japan Exchange Group", "acronym": "JPX", "country": "Japan", "iso3": "JPN", "city": "Tokyo", "lat": 35.68, "lon": 139.77, "tier": "mega", "market_cap_usd_t": 6.5, "index": "^N225", "currency": "JPY", "timezone": "Asia/Tokyo"},
+ {"name": "Shenzhen Stock Exchange", "acronym": "SZSE", "country": "China", "iso3": "CHN", "city": "Shenzhen", "lat": 22.54, "lon": 114.06, "tier": "mega", "market_cap_usd_t": 4.9, "index": "399001.SZ", "currency": "CNY", "timezone": "Asia/Shanghai"},
+ {"name": "Hong Kong Exchanges", "acronym": "HKEX", "country": "China", "iso3": "HKG", "city": "Hong Kong", "lat": 22.28, "lon": 114.16, "tier": "mega", "market_cap_usd_t": 4.6, "index": "^HSI", "currency": "HKD", "timezone": "Asia/Hong_Kong"},
+ {"name": "National Stock Exchange of India", "acronym": "NSE", "country": "India", "iso3": "IND", "city": "Mumbai", "lat": 19.06, "lon": 72.86, "tier": "mega", "market_cap_usd_t": 4.3, "index": "^NSEI", "currency": "INR", "timezone": "Asia/Kolkata"},
+ {"name": "London Stock Exchange", "acronym": "LSE", "country": "UK", "iso3": "GBR", "city": "London", "lat": 51.51, "lon": -0.09, "tier": "mega", "market_cap_usd_t": 3.4, "index": "^FTSE", "currency": "GBP", "timezone": "Europe/London"},
+ {"name": "Euronext", "acronym": "ENX", "country": "Netherlands", "iso3": "NLD", "city": "Amsterdam", "lat": 52.37, "lon": 4.89, "tier": "mega", "market_cap_usd_t": 7.3, "index": "^AEX", "currency": "EUR", "timezone": "Europe/Amsterdam"},
+ # Major exchanges ($500B-$3T)
+ {"name": "Toronto Stock Exchange", "acronym": "TSX", "country": "Canada", "iso3": "CAN", "city": "Toronto", "lat": 43.65, "lon": -79.38, "tier": "major", "market_cap_usd_t": 2.8, "index": "^GSPTSE", "currency": "CAD", "timezone": "America/Toronto"},
+ {"name": "Saudi Exchange (Tadawul)", "acronym": "TADAWUL", "country": "Saudi Arabia", "iso3": "SAU", "city": "Riyadh", "lat": 24.71, "lon": 46.67, "tier": "major", "market_cap_usd_t": 2.9, "index": "^TASI", "currency": "SAR", "timezone": "Asia/Riyadh"},
+ {"name": "Deutsche Börse", "acronym": "XETRA", "country": "Germany", "iso3": "DEU", "city": "Frankfurt", "lat": 50.11, "lon": 8.68, "tier": "major", "market_cap_usd_t": 2.3, "index": "^GDAXI", "currency": "EUR", "timezone": "Europe/Berlin"},
+ {"name": "SIX Swiss Exchange", "acronym": "SIX", "country": "Switzerland", "iso3": "CHE", "city": "Zurich", "lat": 47.37, "lon": 8.54, "tier": "major", "market_cap_usd_t": 1.9, "index": "^SSMI", "currency": "CHF", "timezone": "Europe/Zurich"},
+ {"name": "Korea Exchange", "acronym": "KRX", "country": "South Korea", "iso3": "KOR", "city": "Seoul", "lat": 37.52, "lon": 126.93, "tier": "major", "market_cap_usd_t": 1.8, "index": "^KS11", "currency": "KRW", "timezone": "Asia/Seoul"},
+ {"name": "Nasdaq Nordic (OMX)", "acronym": "OMX", "country": "Sweden", "iso3": "SWE", "city": "Stockholm", "lat": 59.33, "lon": 18.07, "tier": "major", "market_cap_usd_t": 1.7, "index": "^OMX", "currency": "SEK", "timezone": "Europe/Stockholm"},
+ {"name": "Australian Securities Exchange", "acronym": "ASX", "country": "Australia", "iso3": "AUS", "city": "Sydney", "lat": -33.87, "lon": 151.21, "tier": "major", "market_cap_usd_t": 1.6, "index": "^AXJO", "currency": "AUD", "timezone": "Australia/Sydney"},
+ {"name": "Taiwan Stock Exchange", "acronym": "TWSE", "country": "Taiwan", "iso3": "TWN", "city": "Taipei", "lat": 25.03, "lon": 121.52, "tier": "major", "market_cap_usd_t": 2.1, "index": "^TWII", "currency": "TWD", "timezone": "Asia/Taipei"},
+ {"name": "Bombay Stock Exchange", "acronym": "BSE", "country": "India", "iso3": "IND", "city": "Mumbai", "lat": 18.93, "lon": 72.83, "tier": "major", "market_cap_usd_t": 4.1, "index": "^BSESN", "currency": "INR", "timezone": "Asia/Kolkata"},
+ {"name": "Johannesburg Stock Exchange", "acronym": "JSE", "country": "South Africa", "iso3": "ZAF", "city": "Johannesburg", "lat": -26.20, "lon": 28.04, "tier": "major", "market_cap_usd_t": 1.1, "index": "^J203", "currency": "ZAR", "timezone": "Africa/Johannesburg"},
+ {"name": "B3 (Brasil Bolsa Balcão)", "acronym": "B3", "country": "Brazil", "iso3": "BRA", "city": "São Paulo", "lat": -23.55, "lon": -46.63, "tier": "major", "market_cap_usd_t": 0.9, "index": "^BVSP", "currency": "BRL", "timezone": "America/Sao_Paulo"},
+ {"name": "Borsa Italiana", "acronym": "BIT", "country": "Italy", "iso3": "ITA", "city": "Milan", "lat": 45.46, "lon": 9.19, "tier": "major", "market_cap_usd_t": 0.8, "index": "FTSEMIB.MI", "currency": "EUR", "timezone": "Europe/Rome"},
+ {"name": "BME Spanish Exchanges", "acronym": "BME", "country": "Spain", "iso3": "ESP", "city": "Madrid", "lat": 40.42, "lon": -3.70, "tier": "major", "market_cap_usd_t": 0.7, "index": "^IBEX", "currency": "EUR", "timezone": "Europe/Madrid"},
+ {"name": "Singapore Exchange", "acronym": "SGX", "country": "Singapore", "iso3": "SGP", "city": "Singapore", "lat": 1.28, "lon": 103.85, "tier": "major", "market_cap_usd_t": 0.6, "index": "^STI", "currency": "SGD", "timezone": "Asia/Singapore"},
+ {"name": "Bolsa Mexicana de Valores", "acronym": "BMV", "country": "Mexico", "iso3": "MEX", "city": "Mexico City", "lat": 19.43, "lon": -99.13, "tier": "major", "market_cap_usd_t": 0.5, "index": "^MXX", "currency": "MXN", "timezone": "America/Mexico_City"},
+ {"name": "Tel Aviv Stock Exchange", "acronym": "TASE", "country": "Israel", "iso3": "ISR", "city": "Tel Aviv", "lat": 32.07, "lon": 34.77, "tier": "major", "market_cap_usd_t": 0.3, "index": "^TA125", "currency": "ILS", "timezone": "Asia/Jerusalem"},
+ # Emerging exchanges ($50B-$500B)
+ {"name": "Indonesia Stock Exchange", "acronym": "IDX", "country": "Indonesia", "iso3": "IDN", "city": "Jakarta", "lat": -6.22, "lon": 106.85, "tier": "emerging", "market_cap_usd_t": 0.6, "index": "^JKSE", "currency": "IDR", "timezone": "Asia/Jakarta"},
+ {"name": "Bursa Malaysia", "acronym": "BM", "country": "Malaysia", "iso3": "MYS", "city": "Kuala Lumpur", "lat": 3.15, "lon": 101.71, "tier": "emerging", "market_cap_usd_t": 0.4, "index": "^KLSE", "currency": "MYR", "timezone": "Asia/Kuala_Lumpur"},
+ {"name": "Stock Exchange of Thailand", "acronym": "SET", "country": "Thailand", "iso3": "THA", "city": "Bangkok", "lat": 13.76, "lon": 100.50, "tier": "emerging", "market_cap_usd_t": 0.5, "index": "^SET", "currency": "THB", "timezone": "Asia/Bangkok"},
+ {"name": "Philippine Stock Exchange", "acronym": "PSE", "country": "Philippines", "iso3": "PHL", "city": "Manila", "lat": 14.59, "lon": 120.98, "tier": "emerging", "market_cap_usd_t": 0.3, "index": "PSEI.PS", "currency": "PHP", "timezone": "Asia/Manila"},
+ {"name": "Ho Chi Minh Stock Exchange", "acronym": "HOSE", "country": "Vietnam", "iso3": "VNM", "city": "Ho Chi Minh City", "lat": 10.77, "lon": 106.70, "tier": "emerging", "market_cap_usd_t": 0.2, "index": "^VNINDEX", "currency": "VND", "timezone": "Asia/Ho_Chi_Minh"},
+ {"name": "Colombo Stock Exchange", "acronym": "CSE", "country": "Sri Lanka", "iso3": "LKA", "city": "Colombo", "lat": 6.93, "lon": 79.84, "tier": "emerging", "market_cap_usd_t": 0.02, "index": "^CSE", "currency": "LKR", "timezone": "Asia/Colombo"},
+ {"name": "Abu Dhabi Securities Exchange", "acronym": "ADX", "country": "UAE", "iso3": "ARE", "city": "Abu Dhabi", "lat": 24.45, "lon": 54.65, "tier": "emerging", "market_cap_usd_t": 0.8, "index": "^ADI", "currency": "AED", "timezone": "Asia/Dubai"},
+ {"name": "Dubai Financial Market", "acronym": "DFM", "country": "UAE", "iso3": "ARE", "city": "Dubai", "lat": 25.20, "lon": 55.27, "tier": "emerging", "market_cap_usd_t": 0.2, "index": "^DFMGI", "currency": "AED", "timezone": "Asia/Dubai"},
+ {"name": "Qatar Stock Exchange", "acronym": "QSE", "country": "Qatar", "iso3": "QAT", "city": "Doha", "lat": 25.29, "lon": 51.53, "tier": "emerging", "market_cap_usd_t": 0.2, "index": "^QSI", "currency": "QAR", "timezone": "Asia/Qatar"},
+ {"name": "Kuwait Stock Exchange", "acronym": "BK", "country": "Kuwait", "iso3": "KWT", "city": "Kuwait City", "lat": 29.38, "lon": 47.99, "tier": "emerging", "market_cap_usd_t": 0.1, "index": "^BKP", "currency": "KWD", "timezone": "Asia/Kuwait"},
+ {"name": "Bahrain Bourse", "acronym": "BHB", "country": "Bahrain", "iso3": "BHR", "city": "Manama", "lat": 26.22, "lon": 50.59, "tier": "emerging", "market_cap_usd_t": 0.03, "index": "^BAX", "currency": "BHD", "timezone": "Asia/Bahrain"},
+ {"name": "Muscat Securities Market", "acronym": "MSM", "country": "Oman", "iso3": "OMN", "city": "Muscat", "lat": 23.61, "lon": 58.59, "tier": "emerging", "market_cap_usd_t": 0.03, "index": "^MSI", "currency": "OMR", "timezone": "Asia/Muscat"},
+ {"name": "Casablanca Stock Exchange", "acronym": "CSE", "country": "Morocco", "iso3": "MAR", "city": "Casablanca", "lat": 33.59, "lon": -7.62, "tier": "emerging", "market_cap_usd_t": 0.07, "index": "^MASI", "currency": "MAD", "timezone": "Africa/Casablanca"},
+ {"name": "Egyptian Exchange", "acronym": "EGX", "country": "Egypt", "iso3": "EGY", "city": "Cairo", "lat": 30.04, "lon": 31.24, "tier": "emerging", "market_cap_usd_t": 0.04, "index": "^EGX30", "currency": "EGP", "timezone": "Africa/Cairo"},
+ {"name": "Nairobi Securities Exchange", "acronym": "NSE", "country": "Kenya", "iso3": "KEN", "city": "Nairobi", "lat": -1.29, "lon": 36.82, "tier": "emerging", "market_cap_usd_t": 0.02, "index": "^NSE20", "currency": "KES", "timezone": "Africa/Nairobi"},
+ {"name": "Nigerian Exchange", "acronym": "NGX", "country": "Nigeria", "iso3": "NGA", "city": "Lagos", "lat": 6.45, "lon": 3.40, "tier": "emerging", "market_cap_usd_t": 0.04, "index": "^NGSE", "currency": "NGN", "timezone": "Africa/Lagos"},
+ {"name": "Santiago Stock Exchange", "acronym": "BCS", "country": "Chile", "iso3": "CHL", "city": "Santiago", "lat": -33.44, "lon": -70.66, "tier": "emerging", "market_cap_usd_t": 0.2, "index": "^IPSA", "currency": "CLP", "timezone": "America/Santiago"},
+ {"name": "Buenos Aires Stock Exchange", "acronym": "BCBA", "country": "Argentina", "iso3": "ARG", "city": "Buenos Aires", "lat": -34.61, "lon": -58.37, "tier": "emerging", "market_cap_usd_t": 0.06, "index": "^MERV", "currency": "ARS", "timezone": "America/Argentina/Buenos_Aires"},
+ {"name": "Lima Stock Exchange", "acronym": "BVL", "country": "Peru", "iso3": "PER", "city": "Lima", "lat": -12.05, "lon": -77.04, "tier": "emerging", "market_cap_usd_t": 0.08, "index": "^SPBLPGPT", "currency": "PEN", "timezone": "America/Lima"},
+ {"name": "Colombia Stock Exchange", "acronym": "BVC", "country": "Colombia", "iso3": "COL", "city": "Bogotá", "lat": 4.71, "lon": -74.07, "tier": "emerging", "market_cap_usd_t": 0.07, "index": "^COLCAP", "currency": "COP", "timezone": "America/Bogota"},
+ {"name": "Warsaw Stock Exchange", "acronym": "WSE", "country": "Poland", "iso3": "POL", "city": "Warsaw", "lat": 52.23, "lon": 21.01, "tier": "emerging", "market_cap_usd_t": 0.2, "index": "^WIG20", "currency": "PLN", "timezone": "Europe/Warsaw"},
+ {"name": "Moscow Exchange", "acronym": "MOEX", "country": "Russia", "iso3": "RUS", "city": "Moscow", "lat": 55.76, "lon": 37.62, "tier": "emerging", "market_cap_usd_t": 0.6, "index": "IMOEX.ME", "currency": "RUB", "timezone": "Europe/Moscow"},
+ {"name": "Istanbul Stock Exchange", "acronym": "BIST", "country": "Turkey", "iso3": "TUR", "city": "Istanbul", "lat": 41.01, "lon": 28.98, "tier": "emerging", "market_cap_usd_t": 0.3, "index": "^XU100", "currency": "TRY", "timezone": "Europe/Istanbul"},
+ {"name": "Athens Stock Exchange", "acronym": "ATHEX", "country": "Greece", "iso3": "GRC", "city": "Athens", "lat": 37.98, "lon": 23.73, "tier": "emerging", "market_cap_usd_t": 0.08, "index": "^ATG", "currency": "EUR", "timezone": "Europe/Athens"},
+ {"name": "Bucharest Stock Exchange", "acronym": "BVB", "country": "Romania", "iso3": "ROU", "city": "Bucharest", "lat": 44.43, "lon": 26.10, "tier": "emerging", "market_cap_usd_t": 0.06, "index": "^BET", "currency": "RON", "timezone": "Europe/Bucharest"},
+ {"name": "Prague Stock Exchange", "acronym": "PSE", "country": "Czech Republic", "iso3": "CZE", "city": "Prague", "lat": 50.08, "lon": 14.43, "tier": "emerging", "market_cap_usd_t": 0.03, "index": "^PX", "currency": "CZK", "timezone": "Europe/Prague"},
+ {"name": "Budapest Stock Exchange", "acronym": "BSE", "country": "Hungary", "iso3": "HUN", "city": "Budapest", "lat": 47.50, "lon": 19.04, "tier": "emerging", "market_cap_usd_t": 0.04, "index": "^BUX", "currency": "HUF", "timezone": "Europe/Budapest"},
+ {"name": "Pakistan Stock Exchange", "acronym": "PSX", "country": "Pakistan", "iso3": "PAK", "city": "Karachi", "lat": 24.85, "lon": 67.01, "tier": "emerging", "market_cap_usd_t": 0.04, "index": "^KSE100", "currency": "PKR", "timezone": "Asia/Karachi"},
+ {"name": "Dhaka Stock Exchange", "acronym": "DSE", "country": "Bangladesh", "iso3": "BGD", "city": "Dhaka", "lat": 23.73, "lon": 90.39, "tier": "emerging", "market_cap_usd_t": 0.05, "index": "^DSEX", "currency": "BDT", "timezone": "Asia/Dhaka"},
+ # Frontier / smaller
+ {"name": "Amman Stock Exchange", "acronym": "ASE", "country": "Jordan", "iso3": "JOR", "city": "Amman", "lat": 31.95, "lon": 35.93, "tier": "frontier", "market_cap_usd_t": 0.02, "index": "^AMGNRLX", "currency": "JOD", "timezone": "Asia/Amman"},
+ {"name": "Tunis Stock Exchange", "acronym": "BVMT", "country": "Tunisia", "iso3": "TUN", "city": "Tunis", "lat": 36.80, "lon": 10.18, "tier": "frontier", "market_cap_usd_t": 0.01, "index": "^TUNINDEX", "currency": "TND", "timezone": "Africa/Tunis"},
+ {"name": "Dar es Salaam Stock Exchange", "acronym": "DSE", "country": "Tanzania", "iso3": "TZA", "city": "Dar es Salaam", "lat": -6.79, "lon": 39.28, "tier": "frontier", "market_cap_usd_t": 0.01, "index": "^DSI", "currency": "TZS", "timezone": "Africa/Dar_es_Salaam"},
+ {"name": "Uganda Securities Exchange", "acronym": "USE", "country": "Uganda", "iso3": "UGA", "city": "Kampala", "lat": 0.31, "lon": 32.58, "tier": "frontier", "market_cap_usd_t": 0.005, "index": "^ALSI", "currency": "UGX", "timezone": "Africa/Kampala"},
+ {"name": "Rwanda Stock Exchange", "acronym": "RSE", "country": "Rwanda", "iso3": "RWA", "city": "Kigali", "lat": -1.94, "lon": 30.06, "tier": "frontier", "market_cap_usd_t": 0.003, "index": "^RSI", "currency": "RWF", "timezone": "Africa/Kigali"},
+ {"name": "Ghana Stock Exchange", "acronym": "GSE", "country": "Ghana", "iso3": "GHA", "city": "Accra", "lat": 5.56, "lon": -0.19, "tier": "frontier", "market_cap_usd_t": 0.01, "index": "^GGSECI", "currency": "GHS", "timezone": "Africa/Accra"},
+ {"name": "Zimbabwe Stock Exchange", "acronym": "ZSE", "country": "Zimbabwe", "iso3": "ZWE", "city": "Harare", "lat": -17.83, "lon": 31.05, "tier": "frontier", "market_cap_usd_t": 0.003, "index": "^ZSI", "currency": "ZWL", "timezone": "Africa/Harare"},
+ {"name": "Botswana Stock Exchange", "acronym": "BSE", "country": "Botswana", "iso3": "BWA", "city": "Gaborone", "lat": -24.65, "lon": 25.91, "tier": "frontier", "market_cap_usd_t": 0.004, "index": "^DCI", "currency": "BWP", "timezone": "Africa/Gaborone"},
+ {"name": "Lusaka Securities Exchange", "acronym": "LuSE", "country": "Zambia", "iso3": "ZMB", "city": "Lusaka", "lat": -15.39, "lon": 28.32, "tier": "frontier", "market_cap_usd_t": 0.005, "index": "^LASI", "currency": "ZMW", "timezone": "Africa/Lusaka"},
+ {"name": "Mauritius Stock Exchange", "acronym": "SEM", "country": "Mauritius", "iso3": "MUS", "city": "Port Louis", "lat": -20.16, "lon": 57.50, "tier": "frontier", "market_cap_usd_t": 0.01, "index": "^SEMDEX", "currency": "MUR", "timezone": "Indian/Mauritius"},
+ {"name": "Nepal Stock Exchange", "acronym": "NEPSE", "country": "Nepal", "iso3": "NPL", "city": "Kathmandu", "lat": 27.71, "lon": 85.32, "tier": "frontier", "market_cap_usd_t": 0.02, "index": "^NEPSE", "currency": "NPR", "timezone": "Asia/Kathmandu"},
+ {"name": "Cambodia Securities Exchange", "acronym": "CSX", "country": "Cambodia", "iso3": "KHM", "city": "Phnom Penh", "lat": 11.56, "lon": 104.93, "tier": "frontier", "market_cap_usd_t": 0.003, "index": "^CSXI", "currency": "KHR", "timezone": "Asia/Phnom_Penh"},
+ {"name": "Lao Securities Exchange", "acronym": "LSX", "country": "Laos", "iso3": "LAO", "city": "Vientiane", "lat": 17.97, "lon": 102.63, "tier": "frontier", "market_cap_usd_t": 0.001, "index": "^LSXI", "currency": "LAK", "timezone": "Asia/Vientiane"},
+ {"name": "Mongolia Stock Exchange", "acronym": "MSE", "country": "Mongolia", "iso3": "MNG", "city": "Ulaanbaatar", "lat": 47.92, "lon": 106.91, "tier": "frontier", "market_cap_usd_t": 0.002, "index": "^MNT20", "currency": "MNT", "timezone": "Asia/Ulaanbaatar"},
+ {"name": "Tehran Stock Exchange", "acronym": "TSE", "country": "Iran", "iso3": "IRN", "city": "Tehran", "lat": 35.69, "lon": 51.42, "tier": "emerging", "market_cap_usd_t": 0.2, "index": "^TEDPIX", "currency": "IRR", "timezone": "Asia/Tehran"},
+ {"name": "Karachi Stock Exchange (merged into PSX)", "acronym": "KSE", "country": "Pakistan", "iso3": "PAK", "city": "Karachi", "lat": 24.85, "lon": 67.01, "tier": "emerging", "market_cap_usd_t": 0.0, "index": "", "currency": "PKR", "timezone": "Asia/Karachi"},
+ {"name": "Colombo Stock Exchange", "acronym": "CSE", "country": "Sri Lanka", "iso3": "LKA", "city": "Colombo", "lat": 6.93, "lon": 79.85, "tier": "frontier", "market_cap_usd_t": 0.02, "index": "^ASPI", "currency": "LKR", "timezone": "Asia/Colombo"},
+ {"name": "New Zealand Exchange", "acronym": "NZX", "country": "New Zealand", "iso3": "NZL", "city": "Wellington", "lat": -41.29, "lon": 174.78, "tier": "major", "market_cap_usd_t": 0.1, "index": "^NZ50", "currency": "NZD", "timezone": "Pacific/Auckland"},
+ {"name": "Oslo Stock Exchange", "acronym": "OSE", "country": "Norway", "iso3": "NOR", "city": "Oslo", "lat": 59.91, "lon": 10.75, "tier": "major", "market_cap_usd_t": 0.3, "index": "^OBX", "currency": "NOK", "timezone": "Europe/Oslo"},
+ {"name": "Copenhagen Stock Exchange", "acronym": "CSE", "country": "Denmark", "iso3": "DNK", "city": "Copenhagen", "lat": 55.68, "lon": 12.57, "tier": "major", "market_cap_usd_t": 0.7, "index": "^OMXC25", "currency": "DKK", "timezone": "Europe/Copenhagen"},
+ {"name": "Helsinki Stock Exchange", "acronym": "OMXH", "country": "Finland", "iso3": "FIN", "city": "Helsinki", "lat": 60.17, "lon": 24.94, "tier": "major", "market_cap_usd_t": 0.3, "index": "^OMXH25", "currency": "EUR", "timezone": "Europe/Helsinki"},
+ {"name": "Vienna Stock Exchange", "acronym": "VSE", "country": "Austria", "iso3": "AUT", "city": "Vienna", "lat": 48.21, "lon": 16.37, "tier": "emerging", "market_cap_usd_t": 0.1, "index": "^ATX", "currency": "EUR", "timezone": "Europe/Vienna"},
+ {"name": "Lisbon Stock Exchange (Euronext)", "acronym": "ELX", "country": "Portugal", "iso3": "PRT", "city": "Lisbon", "lat": 38.72, "lon": -9.14, "tier": "emerging", "market_cap_usd_t": 0.08, "index": "^PSI20", "currency": "EUR", "timezone": "Europe/Lisbon"},
+ {"name": "Brussels Stock Exchange (Euronext)", "acronym": "EBR", "country": "Belgium", "iso3": "BEL", "city": "Brussels", "lat": 50.85, "lon": 4.35, "tier": "major", "market_cap_usd_t": 0.3, "index": "^BFX", "currency": "EUR", "timezone": "Europe/Brussels"},
+ {"name": "Irish Stock Exchange (Euronext)", "acronym": "ISE", "country": "Ireland", "iso3": "IRL", "city": "Dublin", "lat": 53.35, "lon": -6.26, "tier": "emerging", "market_cap_usd_t": 0.1, "index": "^ISEQ", "currency": "EUR", "timezone": "Europe/Dublin"},
+ {"name": "Zagreb Stock Exchange", "acronym": "ZSE", "country": "Croatia", "iso3": "HRV", "city": "Zagreb", "lat": 45.81, "lon": 15.98, "tier": "frontier", "market_cap_usd_t": 0.03, "index": "^CRBEX", "currency": "EUR", "timezone": "Europe/Zagreb"},
+ {"name": "Ljubljana Stock Exchange", "acronym": "LJSE", "country": "Slovenia", "iso3": "SVN", "city": "Ljubljana", "lat": 46.05, "lon": 14.51, "tier": "frontier", "market_cap_usd_t": 0.01, "index": "^SBITOP", "currency": "EUR", "timezone": "Europe/Ljubljana"},
+ {"name": "Beirut Stock Exchange", "acronym": "BSE", "country": "Lebanon", "iso3": "LBN", "city": "Beirut", "lat": 33.89, "lon": 35.50, "tier": "frontier", "market_cap_usd_t": 0.003, "index": "^BLOM", "currency": "LBP", "timezone": "Asia/Beirut"},
+]
+
+
+# Country ISO3 → primary exchange index ticker mapping
+COUNTRY_INDEX_TICKERS: dict[str, str] = {
+ ex["iso3"]: ex["index"]
+ for ex in STOCK_EXCHANGES
+ if ex["index"] and ex["tier"] in ("mega", "major")
+}
+# Override duplicates (keep largest by market cap)
+COUNTRY_INDEX_TICKERS.update({
+ "USA": "^GSPC", # S&P 500 as default US index
+ "CHN": "000001.SS",
+ "IND": "^NSEI",
+ "ARE": "^ADI",
+})
+
+
+def query_exchanges(
+ tier: str | None = None,
+ country: str | None = None,
+ currency: str | None = None,
+) -> list[dict]:
+ """Filter stock exchanges by tier, country, or currency."""
+ results = []
+ for ex in STOCK_EXCHANGES:
+ if tier and ex["tier"] != tier.lower():
+ continue
+ if country:
+ c = country.lower()
+ if c not in (ex["country"].lower(), ex["iso3"].lower()):
+ continue
+ if currency and ex["currency"].lower() != currency.lower():
+ continue
+ results.append(ex)
+ return results
diff --git a/src/world_intel_mcp/config/minerals.py b/src/world_intel_mcp/config/minerals.py
new file mode 100644
index 0000000..87b4980
--- /dev/null
+++ b/src/world_intel_mcp/config/minerals.py
@@ -0,0 +1,75 @@
+"""Critical mineral deposits and strategic mineral locations.
+
+Pure data module — no I/O, no external dependencies.
+Sources: USGS Mineral Commodity Summaries, IEA Critical Minerals,
+EU Critical Raw Materials Act, Australian Geoscience.
+"""
+
+from __future__ import annotations
+
+CRITICAL_MINERALS: list[dict] = [
+ # Lithium
+ {"name": "Salar de Atacama", "country": "Chile", "iso3": "CHL", "lat": -23.50, "lon": -68.20, "mineral": "lithium", "type": "brine", "operator": "SQM / Albemarle", "annual_tonnes": 100000, "pct_global": 26, "notes": "World's largest lithium brine operation"},
+ {"name": "Greenbushes", "country": "Australia", "iso3": "AUS", "lat": -33.85, "lon": 116.06, "mineral": "lithium", "type": "hard_rock", "operator": "Talison (Tianqi/Albemarle)", "annual_tonnes": 75000, "pct_global": 20, "notes": "Largest hard-rock lithium mine"},
+ {"name": "Salar de Uyuni", "country": "Bolivia", "iso3": "BOL", "lat": -20.13, "lon": -67.49, "mineral": "lithium", "type": "brine", "operator": "YLB (state)", "annual_tonnes": 5000, "pct_global": 1, "notes": "Largest reserves globally (~21M tonnes), low extraction"},
+ {"name": "Pilgangoora", "country": "Australia", "iso3": "AUS", "lat": -21.30, "lon": 119.04, "mineral": "lithium", "type": "hard_rock", "operator": "Pilbara Minerals", "annual_tonnes": 40000, "pct_global": 10, "notes": "Major spodumene producer, Pilbara region"},
+ {"name": "Thacker Pass", "country": "USA", "iso3": "USA", "lat": 41.32, "lon": -117.65, "mineral": "lithium", "type": "clay", "operator": "Lithium Americas", "annual_tonnes": 0, "pct_global": 0, "notes": "Largest US lithium project, under construction"},
+ # Cobalt
+ {"name": "Katanga Province Mines", "country": "DR Congo", "iso3": "COD", "lat": -10.98, "lon": 26.02, "mineral": "cobalt", "type": "copper_cobalt", "operator": "Glencore / CMOC / Artisanal", "annual_tonnes": 130000, "pct_global": 73, "notes": "~73% of global cobalt, artisanal mining concerns"},
+ {"name": "Murrin Murrin", "country": "Australia", "iso3": "AUS", "lat": -28.72, "lon": 121.87, "mineral": "cobalt", "type": "nickel_cobalt", "operator": "Glencore", "annual_tonnes": 3000, "pct_global": 2, "notes": "Nickel-cobalt laterite"},
+ # Rare Earth Elements
+ {"name": "Bayan Obo", "country": "China", "iso3": "CHN", "lat": 41.78, "lon": 109.97, "mineral": "rare_earths", "type": "open_pit", "operator": "China Northern Rare Earth", "annual_tonnes": 45000, "pct_global": 38, "notes": "World's largest REE deposit, Inner Mongolia"},
+ {"name": "Mount Weld", "country": "Australia", "iso3": "AUS", "lat": -28.77, "lon": 122.55, "mineral": "rare_earths", "type": "open_pit", "operator": "Lynas Rare Earths", "annual_tonnes": 12000, "pct_global": 10, "notes": "Largest non-Chinese REE mine, processed in Malaysia"},
+ {"name": "Mountain Pass", "country": "USA", "iso3": "USA", "lat": 35.48, "lon": -115.53, "mineral": "rare_earths", "type": "open_pit", "operator": "MP Materials", "annual_tonnes": 43000, "pct_global": 14, "notes": "Only US rare earth mine, processing expansion underway"},
+ {"name": "Xunwu / Ganzhou", "country": "China", "iso3": "CHN", "lat": 24.95, "lon": 115.65, "mineral": "rare_earths", "type": "ion_adsorption", "operator": "Various Chinese SOEs", "annual_tonnes": 30000, "pct_global": 25, "notes": "Heavy rare earths (Dy, Tb), Jiangxi province"},
+ # Nickel
+ {"name": "Sorowako / Morowali", "country": "Indonesia", "iso3": "IDN", "lat": -2.54, "lon": 121.36, "mineral": "nickel", "type": "laterite", "operator": "Vale / Chinese consortia", "annual_tonnes": 1600000, "pct_global": 48, "notes": "Indonesia is #1 producer, massive HPAL expansion"},
+ {"name": "Norilsk", "country": "Russia", "iso3": "RUS", "lat": 69.35, "lon": 88.20, "mineral": "nickel", "type": "sulfide", "operator": "Nornickel", "annual_tonnes": 200000, "pct_global": 6, "notes": "Major PGM co-product, Arctic"},
+ # Copper
+ {"name": "Escondida", "country": "Chile", "iso3": "CHL", "lat": -24.27, "lon": -69.07, "mineral": "copper", "type": "open_pit", "operator": "BHP / Rio Tinto / JECO", "annual_tonnes": 1100000, "pct_global": 5, "notes": "World's largest copper mine by output"},
+ {"name": "Grasberg", "country": "Indonesia", "iso3": "IDN", "lat": -4.06, "lon": 137.11, "mineral": "copper", "type": "underground", "operator": "Freeport-McMoRan / INALUM", "annual_tonnes": 700000, "pct_global": 3, "notes": "Largest gold reserve, deep block cave"},
+ {"name": "Kamoa-Kakula", "country": "DR Congo", "iso3": "COD", "lat": -10.77, "lon": 25.33, "mineral": "copper", "type": "underground", "operator": "Ivanhoe Mines / Zijin", "annual_tonnes": 400000, "pct_global": 2, "notes": "Newest mega-mine, highest grade discovered copper"},
+ # Graphite
+ {"name": "Heilongjiang Province", "country": "China", "iso3": "CHN", "lat": 47.35, "lon": 127.96, "mineral": "graphite", "type": "flake", "operator": "Various Chinese", "annual_tonnes": 900000, "pct_global": 65, "notes": "China dominates natural graphite production"},
+ {"name": "Balama", "country": "Mozambique", "iso3": "MOZ", "lat": -13.35, "lon": 38.58, "mineral": "graphite", "type": "flake", "operator": "Syrah Resources", "annual_tonnes": 50000, "pct_global": 4, "notes": "Largest known graphite reserve, feeds Vidalia (US)"},
+ # Manganese
+ {"name": "Kalahari Manganese Field", "country": "South Africa", "iso3": "ZAF", "lat": -27.18, "lon": 22.98, "mineral": "manganese", "type": "open_pit", "operator": "South32 / Samancor", "annual_tonnes": 7200000, "pct_global": 37, "notes": "~80% of global reserves in Kalahari"},
+ # Platinum Group Metals
+ {"name": "Bushveld Complex", "country": "South Africa", "iso3": "ZAF", "lat": -25.00, "lon": 29.50, "mineral": "pgm", "type": "underground", "operator": "Anglo American / Impala / Sibanye", "annual_tonnes": 130, "pct_global": 72, "notes": "~72% of global platinum, major PGM source"},
+ # Titanium
+ {"name": "Richards Bay", "country": "South Africa", "iso3": "ZAF", "lat": -28.78, "lon": 32.04, "mineral": "titanium", "type": "mineral_sands", "operator": "Rio Tinto (RBM)", "annual_tonnes": 1000000, "pct_global": 11, "notes": "Ilmenite and rutile sands"},
+ # Tungsten
+ {"name": "Jiangxi Province Mines", "country": "China", "iso3": "CHN", "lat": 27.63, "lon": 115.97, "mineral": "tungsten", "type": "underground", "operator": "Various Chinese SOEs", "annual_tonnes": 60000, "pct_global": 82, "notes": "China produces >80% of global tungsten"},
+ # Uranium
+ {"name": "Cigar Lake", "country": "Canada", "iso3": "CAN", "lat": 58.01, "lon": -104.56, "mineral": "uranium", "type": "underground", "operator": "Cameco / Orano", "annual_tonnes": 6900, "pct_global": 13, "notes": "Highest grade uranium mine in world"},
+ {"name": "Inkai", "country": "Kazakhstan", "iso3": "KAZ", "lat": 44.49, "lon": 66.09, "mineral": "uranium", "type": "isl", "operator": "Kazatomprom / Cameco", "annual_tonnes": 4000, "pct_global": 8, "notes": "Kazakhstan is #1 uranium producer (~43% global)"},
+ # Tin
+ {"name": "Bangka-Belitung Islands", "country": "Indonesia", "iso3": "IDN", "lat": -2.13, "lon": 106.11, "mineral": "tin", "type": "alluvial", "operator": "PT Timah / Artisanal", "annual_tonnes": 52000, "pct_global": 24, "notes": "Major tin source, environmental concerns"},
+ # Gallium / Germanium (strategic, China-controlled)
+ {"name": "Shanxi / Henan Alumina Refineries", "country": "China", "iso3": "CHN", "lat": 37.87, "lon": 112.55, "mineral": "gallium", "type": "byproduct", "operator": "Chalco / various", "annual_tonnes": 340, "pct_global": 98, "notes": "China controls ~98% of gallium, export restrictions 2023"},
+ {"name": "Yunnan Germanium Refineries", "country": "China", "iso3": "CHN", "lat": 25.04, "lon": 102.68, "mineral": "germanium", "type": "byproduct", "operator": "Yunnan Germanium", "annual_tonnes": 100, "pct_global": 60, "notes": "China ~60% of germanium, export restrictions 2023"},
+]
+
+
+def query_minerals(
+ mineral: str | None = None,
+ country: str | None = None,
+ mineral_type: str | None = None,
+ operator: str | None = None,
+) -> list[dict]:
+ """Filter critical mineral deposits."""
+ results = []
+ for m in CRITICAL_MINERALS:
+ if mineral and m["mineral"] != mineral.lower():
+ continue
+ if country:
+ c = country.lower()
+ if c not in (m["country"].lower(), m["iso3"].lower()):
+ continue
+ if mineral_type and m["type"] != mineral_type.lower():
+ continue
+ if operator:
+ if operator.lower() not in m["operator"].lower():
+ continue
+ results.append(m)
+ return results
diff --git a/src/world_intel_mcp/config/spaceports.py b/src/world_intel_mcp/config/spaceports.py
new file mode 100644
index 0000000..de3026f
--- /dev/null
+++ b/src/world_intel_mcp/config/spaceports.py
@@ -0,0 +1,68 @@
+"""Launch facilities and spaceports worldwide.
+
+Pure data module — no I/O, no external dependencies.
+Sources: FAA, ESA, CSIS Aerospace, press reporting.
+"""
+
+from __future__ import annotations
+
+SPACEPORTS: list[dict] = [
+ # United States
+ {"name": "Cape Canaveral SFS / KSC", "country": "USA", "iso3": "USA", "lat": 28.56, "lon": -80.58, "operator": "USSF / NASA", "type": "orbital", "status": "active", "pads": 7, "notes": "Primary US orbital launch site, SpaceX LC-40, NASA LC-39A/B"},
+ {"name": "Vandenberg SFB", "country": "USA", "iso3": "USA", "lat": 34.73, "lon": -120.57, "operator": "USSF", "type": "orbital", "status": "active", "pads": 4, "notes": "Polar/SSO launches, SpaceX SLC-4E"},
+ {"name": "SpaceX Starbase (Boca Chica)", "country": "USA", "iso3": "USA", "lat": 25.99, "lon": -97.16, "operator": "SpaceX", "type": "orbital", "status": "active", "pads": 2, "notes": "Starship development and launch facility"},
+ {"name": "Wallops Flight Facility", "country": "USA", "iso3": "USA", "lat": 37.83, "lon": -75.49, "operator": "NASA", "type": "orbital", "status": "active", "pads": 2, "notes": "Antares/Minotaur, ISS cargo"},
+ {"name": "Kodiak Launch Complex", "country": "USA", "iso3": "USA", "lat": 57.44, "lon": -152.34, "operator": "Alaska Aerospace", "type": "orbital", "status": "active", "pads": 2, "notes": "Polar orbits, Astra launches"},
+ {"name": "Mojave Air and Space Port", "country": "USA", "iso3": "USA", "lat": 35.06, "lon": -118.15, "operator": "Mojave Air & Space Port", "type": "suborbital", "status": "active", "pads": 0, "notes": "Horizontal launch, Virgin Orbit (defunct), test facility"},
+ {"name": "Spaceport America", "country": "USA", "iso3": "USA", "lat": 32.99, "lon": -106.97, "operator": "New Mexico SAA", "type": "suborbital", "status": "active", "pads": 1, "notes": "Virgin Galactic SpaceShipTwo"},
+ # Russia
+ {"name": "Baikonur Cosmodrome", "country": "Kazakhstan", "iso3": "KAZ", "lat": 45.96, "lon": 63.31, "operator": "Roscosmos (leased)", "type": "orbital", "status": "active", "pads": 6, "notes": "World's first spaceport, Soyuz, Proton"},
+ {"name": "Plesetsk Cosmodrome", "country": "Russia", "iso3": "RUS", "lat": 62.93, "lon": 40.58, "operator": "Russian MoD", "type": "orbital", "status": "active", "pads": 4, "notes": "Military launches, Angara, ICBM tests"},
+ {"name": "Vostochny Cosmodrome", "country": "Russia", "iso3": "RUS", "lat": 51.88, "lon": 128.33, "operator": "Roscosmos", "type": "orbital", "status": "active", "pads": 2, "notes": "New Russian civilian spaceport, Amur region"},
+ # China
+ {"name": "Jiuquan Satellite Launch Center", "country": "China", "iso3": "CHN", "lat": 40.96, "lon": 100.30, "operator": "PLA SSF", "type": "orbital", "status": "active", "pads": 4, "notes": "First Chinese crewed launches, Shenzhou"},
+ {"name": "Xichang Satellite Launch Center", "country": "China", "iso3": "CHN", "lat": 28.25, "lon": 102.03, "operator": "PLA SSF", "type": "orbital", "status": "active", "pads": 3, "notes": "GEO launches, BeiDou, Long March 3B"},
+ {"name": "Taiyuan Satellite Launch Center", "country": "China", "iso3": "CHN", "lat": 38.85, "lon": 111.61, "operator": "PLA SSF", "type": "orbital", "status": "active", "pads": 2, "notes": "SSO/polar orbits"},
+ {"name": "Wenchang Space Launch Site", "country": "China", "iso3": "CHN", "lat": 19.61, "lon": 110.95, "operator": "PLA SSF", "type": "orbital", "status": "active", "pads": 3, "notes": "Newest, largest Chinese vehicles, Long March 5/7"},
+ {"name": "Haiyang Commercial Launch Site", "country": "China", "iso3": "CHN", "lat": 36.73, "lon": 121.13, "operator": "Commercial (CAS Space)", "type": "orbital", "status": "active", "pads": 2, "notes": "First Chinese commercial launch site, 2024"},
+ # Europe
+ {"name": "Guiana Space Centre (Kourou)", "country": "French Guiana", "iso3": "GUF", "lat": 5.24, "lon": -52.77, "operator": "ESA / CNES / Arianespace", "type": "orbital", "status": "active", "pads": 4, "notes": "Ariane 6, Vega-C, Soyuz (suspended), equatorial advantage"},
+ {"name": "SaxaVord Spaceport", "country": "UK", "iso3": "GBR", "lat": 60.82, "lon": -0.86, "operator": "SaxaVord UK", "type": "orbital", "status": "construction", "pads": 3, "notes": "UK's first vertical launch site, Shetland Islands"},
+ {"name": "Andøya Spaceport", "country": "Norway", "iso3": "NOR", "lat": 69.29, "lon": 16.02, "operator": "Andøya Space", "type": "orbital", "status": "active", "pads": 2, "notes": "Northernmost orbital-class spaceport, polar orbits"},
+ # India
+ {"name": "Satish Dhawan Space Centre (Sriharikota)", "country": "India", "iso3": "IND", "lat": 13.72, "lon": 80.23, "operator": "ISRO", "type": "orbital", "status": "active", "pads": 3, "notes": "Primary Indian launch site, GSLV, PSLV, LVM3, Gaganyaan"},
+ {"name": "Kulasekarapattinam (Tamil Nadu)", "country": "India", "iso3": "IND", "lat": 8.56, "lon": 78.08, "operator": "ISRO", "type": "orbital", "status": "construction", "pads": 1, "notes": "Small satellite launch complex"},
+ # Japan
+ {"name": "Tanegashima Space Center", "country": "Japan", "iso3": "JPN", "lat": 30.40, "lon": 131.00, "operator": "JAXA", "type": "orbital", "status": "active", "pads": 2, "notes": "H-IIA/H3, southernmost Japanese island"},
+ {"name": "Uchinoura Space Center", "country": "Japan", "iso3": "JPN", "lat": 31.25, "lon": 131.08, "operator": "JAXA", "type": "orbital", "status": "active", "pads": 2, "notes": "Epsilon rocket, sounding rockets"},
+ # Others
+ {"name": "Rocket Lab Launch Complex 1", "country": "New Zealand", "iso3": "NZL", "lat": -39.26, "lon": 177.86, "operator": "Rocket Lab", "type": "orbital", "status": "active", "pads": 2, "notes": "Electron launches, Mahia Peninsula"},
+ {"name": "Semnan Space Center", "country": "Iran", "iso3": "IRN", "lat": 35.23, "lon": 53.92, "operator": "ISA / IRGC", "type": "orbital", "status": "active", "pads": 2, "notes": "Simorgh, Qased, dual-use ICBM concern"},
+ {"name": "Sohae Satellite Launching Station", "country": "North Korea", "iso3": "PRK", "lat": 39.66, "lon": 124.71, "operator": "NADA", "type": "orbital", "status": "active", "pads": 1, "notes": "Unha/Chollima launches, ICBM test concern"},
+ {"name": "Palmachim Airbase", "country": "Israel", "iso3": "ISR", "lat": 31.88, "lon": 34.69, "operator": "IAI / IMoD", "type": "orbital", "status": "active", "pads": 1, "notes": "Shavit launches (westward retrograde orbit)"},
+ {"name": "Alcântara Launch Center", "country": "Brazil", "iso3": "BRA", "lat": -2.37, "lon": -44.40, "operator": "FAB / AEB", "type": "orbital", "status": "active", "pads": 2, "notes": "Near equator, US technology safeguards agreement"},
+]
+
+
+def query_spaceports(
+ country: str | None = None,
+ status: str | None = None,
+ spaceport_type: str | None = None,
+ operator: str | None = None,
+) -> list[dict]:
+ """Filter spaceports by country, status, type, or operator."""
+ results = []
+ for sp in SPACEPORTS:
+ if country:
+ c = country.lower()
+ if c not in (sp["country"].lower(), sp["iso3"].lower()):
+ continue
+ if status and sp["status"] != status.lower():
+ continue
+ if spaceport_type and sp["type"] != spaceport_type.lower():
+ continue
+ if operator:
+ if operator.lower() not in sp["operator"].lower():
+ continue
+ results.append(sp)
+ return results
diff --git a/src/world_intel_mcp/server.py b/src/world_intel_mcp/server.py
index d7a4a18..06059a9 100644
--- a/src/world_intel_mcp/server.py
+++ b/src/world_intel_mcp/server.py
@@ -3,7 +3,7 @@
World Intelligence MCP Server
==============================
-Real-time global intelligence across 23 domains:
+Real-time global intelligence across 30 domains:
financial markets, economic indicators, earthquakes, wildfires,
conflict, military flights, infrastructure, and more.
@@ -18,6 +18,9 @@ Phase 8: Service status monitoring, RSS expansion (80+ feeds, 14 categories) (+1
Phase 9: Geospatial datasets — military bases, ports, pipelines, nuclear facilities (+4 = 60 tools).
Phase 10: NLP intelligence — entity extraction, event classification, news clustering, keyword spikes (+4 = 64 tools).
Phase 11: Strategic synthesis — strategic posture, world brief, fleet report, population exposure (+4 = 68 tools).
+Phase 12: Extended geospatial (cables, datacenters, spaceports, minerals, exchanges), country stocks,
+ aircraft batch, Hacker News, GitHub trending, arXiv papers, USA spending,
+ NASA EONET, GDACS disaster alerts (+14 = 82 tools).
"""
import asyncio
@@ -33,7 +36,7 @@ from mcp.types import Tool, TextContent
from .cache import Cache
from .circuit_breaker import CircuitBreaker
from .fetcher import Fetcher
-from .sources import markets, economic, seismology, wildfire, conflict, military, infrastructure, maritime, climate, news, intelligence, prediction, displacement, aviation, cyber, space_weather, ai_watch, health, sanctions, elections, shipping, social, nuclear, service_status, geospatial
+from .sources import markets, economic, seismology, wildfire, conflict, military, infrastructure, maritime, climate, news, intelligence, prediction, displacement, aviation, cyber, space_weather, ai_watch, health, sanctions, elections, shipping, social, nuclear, service_status, geospatial, hacker_news, github_trending, arxiv_papers, usa_spending, environmental
from .reports import generator as report_gen
logging.basicConfig(
@@ -713,10 +716,173 @@ TOOLS: list[Tool] = [
},
},
),
+ # --- Extended Geospatial (5 tools) ---
+ Tool(
+ name="intel_undersea_cables",
+ description="Query 30+ undersea fiber-optic cable routes with landing points, owners, capacity (Tbps), and length (km). Filterable by status, country, owner, min capacity.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "status": {"type": "string", "description": "Filter by status: active, planned, construction, decommissioned"},
+ "country": {"type": "string", "description": "Filter by country in landing points"},
+ "owner": {"type": "string", "description": "Filter by cable owner (Google, Meta, Microsoft, etc.)"},
+ "min_capacity_tbps": {"type": "number", "description": "Minimum cable capacity in Tbps"},
+ },
+ },
+ ),
+ Tool(
+ name="intel_ai_datacenters",
+ description="Query 48+ AI datacenter clusters worldwide with power capacity (MW), operators, and locations. Covers hyperscalers and sovereign AI.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "country": {"type": "string", "description": "Filter by country name or ISO-3 code"},
+ "operator": {"type": "string", "description": "Filter by operator (AWS, Google, Microsoft, Meta, etc.)"},
+ "min_power_mw": {"type": "integer", "description": "Minimum power capacity in MW"},
+ "region": {"type": "string", "description": "Filter by region (North America, Europe, Asia-Pacific, etc.)"},
+ },
+ },
+ ),
+ Tool(
+ name="intel_spaceports",
+ description="Query 27+ launch facilities and spaceports worldwide. Filterable by country, status, type (orbital/suborbital), and operator.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "country": {"type": "string", "description": "Filter by country name or ISO-3 code"},
+ "status": {"type": "string", "description": "Filter by status: active, limited, planned, decommissioned"},
+ "spaceport_type": {"type": "string", "description": "Filter by type: orbital, suborbital"},
+ "operator": {"type": "string", "description": "Filter by operator (SpaceX, NASA, CNSA, Roscosmos, etc.)"},
+ },
+ },
+ ),
+ Tool(
+ name="intel_critical_minerals",
+ description="Query 28+ critical mineral deposits worldwide: lithium, cobalt, rare earths, nickel, copper, graphite, manganese, PGM, tungsten, uranium, tin, gallium, germanium.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "mineral": {"type": "string", "description": "Filter by mineral (lithium, cobalt, rare_earths, nickel, copper, graphite, manganese, platinum_group, tungsten, uranium, tin, gallium, germanium)"},
+ "country": {"type": "string", "description": "Filter by country name or ISO-3 code"},
+ "mineral_type": {"type": "string", "description": "Filter by type: battery, electronic, structural, energy, industrial, strategic"},
+ "operator": {"type": "string", "description": "Filter by operator"},
+ },
+ },
+ ),
+ Tool(
+ name="intel_stock_exchanges",
+ description="Query 80+ stock exchanges across 4 tiers (mega >$3T, major, emerging, frontier) with market cap, index tickers, currencies, timezones.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "tier": {"type": "string", "description": "Filter by tier: mega, major, emerging, frontier"},
+ "country": {"type": "string", "description": "Filter by country name or ISO-3 code"},
+ "currency": {"type": "string", "description": "Filter by currency (USD, EUR, GBP, JPY, CNY, etc.)"},
+ },
+ },
+ ),
+ # --- Markets Extended (1 tool) ---
+ Tool(
+ name="intel_country_stocks",
+ description="Get real-time stock index quote for any country by ISO-3 code. Maps country to its primary exchange index ticker and fetches via Yahoo Finance.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "country": {"type": "string", "description": "ISO-3 country code (USA, GBR, JPN, CHN, DEU, etc.)", "default": "USA"},
+ },
+ },
+ ),
+ # --- Military Extended (1 tool) ---
+ Tool(
+ name="intel_aircraft_batch",
+ description="Batch lookup of aircraft details by ICAO24 hex codes (max 20). Returns registration, type, operator from hexdb.io.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "icao24_list": {
+ "type": "array",
+ "items": {"type": "string"},
+ "description": "List of ICAO24 hex addresses (max 20)",
+ },
+ },
+ "required": ["icao24_list"],
+ },
+ ),
+ # --- Tech & Science (3 tools) ---
+ Tool(
+ name="intel_hacker_news",
+ description="Get top stories from Hacker News (Firebase API). Returns title, score, URL, author, comment count. Optional: limit (default 30).",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "limit": {"type": "integer", "description": "Number of stories (default 30, max 100)", "default": 30},
+ },
+ },
+ ),
+ Tool(
+ name="intel_trending_repos",
+ description="Get trending GitHub repositories (recently created, most starred). Optional: language filter, time window, limit.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "language": {"type": "string", "description": "Programming language filter (python, rust, typescript, etc.)"},
+ "since_days": {"type": "integer", "description": "Look back N days for new repos (default 7)", "default": 7},
+ "limit": {"type": "integer", "description": "Number of repos (default 25)", "default": 25},
+ },
+ },
+ ),
+ Tool(
+ name="intel_arxiv_papers",
+ description="Search recent arXiv papers in AI/ML (cs.AI, cs.LG, cs.CL). Optional custom query. Returns title, authors, abstract, categories, PDF link.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "query": {"type": "string", "description": "arXiv search query (default: cs.AI OR cs.LG OR cs.CL)"},
+ "limit": {"type": "integer", "description": "Number of papers (default 25)", "default": 25},
+ },
+ },
+ ),
+ # --- Government (1 tool) ---
+ Tool(
+ name="intel_usa_spending",
+ description="Federal agency spending data from USAspending.gov. Shows top agencies by budget for current fiscal year. Optional: agency filter.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "agency": {"type": "string", "description": "Filter by agency name substring"},
+ "limit": {"type": "integer", "description": "Number of agencies (default 25)", "default": 25},
+ },
+ },
+ ),
+ # --- Environmental (2 tools) ---
+ Tool(
+ name="intel_environmental_events",
+ description="Natural events from NASA EONET: wildfires, severe storms, volcanoes, floods, icebergs, drought. Includes geolocation and source links.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "days": {"type": "integer", "description": "Look back N days (default 30)", "default": 30},
+ "category": {"type": "string", "description": "Filter by category: wildfires, severeStorms, volcanoes, floods, earthquakes, drought, seaLakeIce"},
+ "limit": {"type": "integer", "description": "Max events (default 50)", "default": 50},
+ },
+ },
+ ),
+ Tool(
+ name="intel_disaster_alerts",
+ description="Global disaster alerts from GDACS (UN): earthquakes, floods, cyclones, droughts, wildfires. Severity levels (green/orange/red) with affected populations.",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "alert_level": {"type": "string", "description": "Filter by level: green, orange, red"},
+ "event_type": {"type": "string", "description": "Filter by type: EQ, FL, TC, DR, WF, VO"},
+ "limit": {"type": "integer", "description": "Max alerts (default 30)", "default": 30},
+ },
+ },
+ ),
# --- System (1 tool) ---
Tool(
name="intel_status",
- description="Get data source health, circuit breaker status, and cache statistics.",
+ description="Get data source health, circuit breaker status, cache freshness, and statistics.",
inputSchema={"type": "object", "properties": {}},
),
]
@@ -998,6 +1164,97 @@ async def _dispatch(name: str, arguments: dict[str, Any]) -> Any:
event_types=arguments.get("event_types"),
)
+ # Extended Geospatial
+ case "intel_undersea_cables":
+ return await geospatial.fetch_undersea_cables(
+ status=arguments.get("status"),
+ country=arguments.get("country"),
+ owner=arguments.get("owner"),
+ min_capacity_tbps=arguments.get("min_capacity_tbps"),
+ )
+ case "intel_ai_datacenters":
+ return await geospatial.fetch_ai_datacenters(
+ country=arguments.get("country"),
+ operator=arguments.get("operator"),
+ min_power_mw=arguments.get("min_power_mw"),
+ region=arguments.get("region"),
+ )
+ case "intel_spaceports":
+ return await geospatial.fetch_spaceports(
+ country=arguments.get("country"),
+ status=arguments.get("status"),
+ spaceport_type=arguments.get("spaceport_type"),
+ operator=arguments.get("operator"),
+ )
+ case "intel_critical_minerals":
+ return await geospatial.fetch_critical_minerals(
+ mineral=arguments.get("mineral"),
+ country=arguments.get("country"),
+ mineral_type=arguments.get("mineral_type"),
+ operator=arguments.get("operator"),
+ )
+ case "intel_stock_exchanges":
+ return await geospatial.fetch_stock_exchanges(
+ tier=arguments.get("tier"),
+ country=arguments.get("country"),
+ currency=arguments.get("currency"),
+ )
+
+ # Markets Extended
+ case "intel_country_stocks":
+ return await markets.fetch_country_stocks(
+ fetcher, country=arguments.get("country", "USA"),
+ )
+
+ # Military Extended
+ case "intel_aircraft_batch":
+ return await military.fetch_aircraft_details_batch(
+ fetcher, icao24_list=arguments["icao24_list"],
+ )
+
+ # Tech & Science
+ case "intel_hacker_news":
+ return await hacker_news.fetch_hacker_news(
+ fetcher, limit=arguments.get("limit", 30),
+ )
+ case "intel_trending_repos":
+ return await github_trending.fetch_trending_repos(
+ fetcher,
+ language=arguments.get("language"),
+ since_days=arguments.get("since_days", 7),
+ limit=arguments.get("limit", 25),
+ )
+ case "intel_arxiv_papers":
+ return await arxiv_papers.fetch_arxiv_papers(
+ fetcher,
+ query=arguments.get("query"),
+ limit=arguments.get("limit", 25),
+ )
+
+ # Government
+ case "intel_usa_spending":
+ return await usa_spending.fetch_usa_spending(
+ fetcher,
+ agency=arguments.get("agency"),
+ limit=arguments.get("limit", 25),
+ )
+
+ # Environmental
+ case "intel_environmental_events":
+ return await environmental.fetch_environmental_events(
+ fetcher,
+ days=arguments.get("days", 30),
+ category=arguments.get("category"),
+ limit=arguments.get("limit", 50),
+ )
+ case "intel_disaster_alerts":
+ return await environmental.fetch_disaster_alerts(
+ fetcher,
+ alert_level=arguments.get("alert_level"),
+ event_type=arguments.get("event_type"),
+ limit=arguments.get("limit", 30),
+ )
+
# NLP Intelligence
case "intel_extract_entities":
from .analysis.entities import fetch_entity_extraction
@@ -1026,6 +1283,7 @@ async def _dispatch(name: str, arguments: dict[str, Any]) -> Any:
return {
"circuit_breakers": breaker.status(),
"cache": cache.stats(),
+ "cache_freshness": cache.freshness(),
"sources": {
"markets": ["yahoo-finance", "coingecko", "alternative-me", "mempool"],
"economic": ["eia", "fred", "world-bank"],
@@ -1050,9 +1308,12 @@ async def _dispatch(name: str, arguments: dict[str, Any]) -> Any:
"social": ["reddit-public"],
"nuclear": ["usgs-nuclear-monitor"],
"service_status": ["aws", "azure", "gcp", "cloudflare", "github"],
- "geospatial": ["static-datasets (bases, ports, pipelines, nuclear)"],
+ "geospatial": ["static-datasets (bases, ports, pipelines, nuclear, cables, datacenters, spaceports, minerals, exchanges)"],
"nlp": ["regex-ner", "keyword-classifier", "jaccard-clustering", "keyword-spike-detector"],
"synthesis": ["strategic-posture", "world-brief", "fleet-report", "population-exposure"],
+ "tech": ["hackernews", "github", "arxiv"],
+ "government": ["usaspending-gov"],
+ "environmental": ["eonet", "gdacs"],
},
}
diff --git a/src/world_intel_mcp/sources/arxiv_papers.py b/src/world_intel_mcp/sources/arxiv_papers.py
new file mode 100644
index 0000000..1b3deb6
--- /dev/null
+++ b/src/world_intel_mcp/sources/arxiv_papers.py
@@ -0,0 +1,125 @@
+"""arXiv recent papers source for world-intel-mcp.
+
+Uses the arXiv API (no key required, rate limit ~3 req/s).
+"""
+
+import logging
+import re
+from datetime import datetime, timezone
+
+from ..fetcher import Fetcher
+
+logger = logging.getLogger("world-intel-mcp.sources.arxiv_papers")
+
+_ARXIV_API_URL = "http://export.arxiv.org/api/query"
+
+
+def _utc_now_iso() -> str:
+ return datetime.now(timezone.utc).isoformat()
+
+
+def _parse_arxiv_xml(xml_text: str) -> list[dict]:
+ """Parse arXiv Atom XML response into paper dicts.
+
+ Simple regex-based parsing to avoid lxml dependency.
+ """
+ papers = []
+ entries = re.findall(r"