From a4d2b546a3e3bffa28108dd3792e26d7557c2cda Mon Sep 17 00:00:00 2001 From: Marc Shade Date: Mon, 23 Feb 2026 11:12:09 -0500 Subject: [PATCH] refactor: replace Wingbits (paid) with hexdb.io (free) for aircraft lookups hexdb.io provides ICAO hex aircraft details with no API key required, eliminating the 30-day trial limitation of Wingbits. Co-Authored-By: Claude Opus 4.6 --- src/world_intel_mcp/server.py | 4 ++-- src/world_intel_mcp/sources/military.py | 22 ++++++++-------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/world_intel_mcp/server.py b/src/world_intel_mcp/server.py index 30fa72e..bb2bd4e 100644 --- a/src/world_intel_mcp/server.py +++ b/src/world_intel_mcp/server.py @@ -205,7 +205,7 @@ TOOLS: list[Tool] = [ ), Tool( name="intel_aircraft_details", - description="Get aircraft details from Wingbits by ICAO24 hex code. Requires WINGBITS_API_KEY.", + description="Get aircraft details from hexdb.io by ICAO24 hex code (free, no API key).", inputSchema={ "type": "object", "properties": { @@ -576,7 +576,7 @@ async def _dispatch(name: str, arguments: dict[str, Any]) -> Any: "economic": ["eia", "fred", "world-bank"], "natural": ["usgs", "nasa-firms"], "conflict": ["acled", "ucdp", "hdx"], - "military": ["opensky", "wingbits"], + "military": ["opensky", "hexdb"], "infrastructure": ["cloudflare-radar", "nga-msi"], "maritime": ["nga-msi"], "climate": ["open-meteo"], diff --git a/src/world_intel_mcp/sources/military.py b/src/world_intel_mcp/sources/military.py index 6938766..1c495d5 100644 --- a/src/world_intel_mcp/sources/military.py +++ b/src/world_intel_mcp/sources/military.py @@ -1,7 +1,7 @@ """Military aviation tracking source for world-intel-mcp. Provides real-time military aircraft tracking via OpenSky Network and -aircraft detail lookups via the Wingbits API. +aircraft detail lookups via hexdb.io (free, no API key). """ import asyncio @@ -19,7 +19,7 @@ logger = logging.getLogger("world-intel-mcp.sources.military") # --------------------------------------------------------------------------- _OPENSKY_STATES_URL = "https://opensky-network.org/api/states/all" -_WINGBITS_BASE_URL = "https://api.wingbits.com/v1/aircraft" +_HEXDB_BASE_URL = "https://hexdb.io/api/v1/aircraft" # ICAO hex prefix ranges known to be allocated to military operators. MILITARY_ICAO_PREFIXES = [ @@ -236,7 +236,7 @@ async def fetch_theater_posture(fetcher: Fetcher) -> dict: async def fetch_aircraft_details(fetcher: Fetcher, icao24: str) -> dict: - """Look up detailed aircraft information from the Wingbits API. + """Look up detailed aircraft information from hexdb.io (free, no API key). Args: fetcher: Shared HTTP fetcher with caching and circuit breaking. @@ -245,31 +245,25 @@ async def fetch_aircraft_details(fetcher: Fetcher, icao24: str) -> dict: Returns: Dict with aircraft detail payload, source, and timestamp. """ - api_key = os.environ.get("WINGBITS_API_KEY") - if not api_key: - return {"error": "WINGBITS_API_KEY not configured"} - - url = f"{_WINGBITS_BASE_URL}/{icao24}" - headers = {"X-API-Key": api_key} + url = f"{_HEXDB_BASE_URL}/{icao24}" data = await fetcher.get_json( url=url, - source="wingbits", + source="hexdb", cache_key=f"military:aircraft:{icao24}", cache_ttl=3600, - headers=headers, ) if data is None: - logger.warning("Wingbits returned no data for icao24=%s", icao24) + logger.warning("hexdb.io returned no data for icao24=%s", icao24) return { "aircraft": {}, - "source": "wingbits", + "source": "hexdb", "timestamp": _utc_now_iso(), } return { "aircraft": data, - "source": "wingbits", + "source": "hexdb", "timestamp": _utc_now_iso(), }