The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Ourairports MCP Server listing page.
Resolve airport codes (IATA/ICAO/GPS/local), search airports, find the nearest by coordinate, and look up runways, navaids, and radio frequencies from the bundled public-domain OurAirports dataset via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://ourairports.caseyjhand.com/mcp
ourairports-mcp-server is the static aviation reference layer for resolving airport identifiers and grounding coordinates. It answers what exists — the catalog of airports, their codes, runways, navaids, and radio frequencies — to complement live aviation services that answer what is happening (weather, positions).
The entire OurAirports dataset is dedicated to the public domain and published as flat CSVs. Those six CSV files — airports, runways, navaids, airport frequencies, countries, and regions (~178k rows, ~20 MB) — are bundled into the package and baked into the Docker image at build time. At startup the server parses them into in-memory indices; every tool is then a local query. The result has no API key, no rate limit, and no upstream dependency to inherit an outage from.
How the working model fits together:
ident. A single code parameter resolves against a unified index (priority: ident → ICAO → IATA → GPS → local), and the response echoes the full code set so an ambiguous national code is self-correcting. A missing code (no IATA for a small field) is reported as null, never a 404.Float64Array of every airport (or navaid) position and return the nearest results ranked by distance, each with its bearing — sub-millisecond at this scale, no spatial index needed.OurAirports is community-edited. The data is surfaced as-is and is not authoritative for real flight operations — treat it the way you would any crowd-sourced reference.
Six read-only tools, all local queries against the bundled index — code resolution and detail, airport and runway search, coordinate grounding, navaids, and the country/region lookup table:
| Tool | Description |
|---|---|
ourairports_search_airports | Full-text and faceted search over the airport corpus by name, municipality, country, region, or type. Ranked summaries, closed airports excluded by default. |
ourairports_search_runways | Search runways across all airports by surface, length, width, and lighting, joined back to their airports and filtered by country, region, or airport type. One flat { airport, runway } row per matching runway. |
ourairports_get_airport | Full record for one airport resolved by any code (IATA/ICAO/GPS/local/ident), with its runways and radio frequencies inline. |
ourairports_find_airports | Airports within a radius of a coordinate, ranked nearest-first by great-circle distance, with distance and bearing. |
ourairports_find_navaids | Navigation aids (VOR, VOR-DME, DME, NDB, NDB-DME, TACAN, VORTAC) near a coordinate or serving a specific airport. |
ourairports_list_countries | Countries present in the dataset with ISO codes and airport counts; optional continent filter and nested regions. The lookup table for valid country/region filter values. |
ourairports_search_airportsThe common entry point — search by free text, facets, or both.
country (ISO 3166-1 alpha-2), region (ISO 3166-2), and type — country/region are exact match, case-insensitive, with surrounding whitespace ignoredinclude_closedourairports_get_airportourairports_search_runwaysCross-airport runway search — the counterpart to ourairports_get_airport, which lists runways for one already-known airport.
country, region, type) narrow the airports first; runway facets (surface, min_length_ft, min_width_ft, lighted) then filter their runwayssurface is a case-insensitive substring match against the raw upstream surface string (no controlled vocabulary — a shorter fragment like asp matches ASP, ASPH, and Asphalt), not an exact code{ airport, runway } row per matching runway — an airport with three matching runways contributes three rowsmin_*_ft filter is set — never assumed to meet a threshold the data can't confirminclude_closed_airports / include_closed_runways is setourairports_get_airportThe detail tool — one call returns everything the common case needs.
code case-insensitively across all five identifier spaces (priority: ident → ICAO → IATA → GPS → local); surrounding whitespace is ignoredinclude trims the response to a subset, and the output's included field distinguishes a relation omitted by include from one that genuinely has no recordsresolvedVia / resolutionNote, with an ambiguity warning for shared national codes so a wrong resolution is self-correctingnull; closed airports always resolveunknown_code error with a recovery hint when no identifier space matchesourairports_find_airportsThe grounding tool — turn a latitude/longitude into the nearest airport(s).
distanceKm and bearingDeg (degrees true) from the query pointradius_km (1–500, default 100), optional type filter, include_closed opt-inradius_kmourairports_find_navaidsNavigation aids two ways — spatially or by airport.
latitude + longitude (+ optional radius_km) ranks navaids nearest-first with distance and bearingairport_code returns the navaids serving that airportfrequencyKhz 114500) and MHzunknown_code error) from "airport found but has no associated navaids" (empty list with a note)| Type | Name | Description |
|---|---|---|
| Resource | airport://{code} | Single airport record by any code (IATA/ICAO/GPS/local/ident), with runways and frequencies inline. |
The airport://{code} resource is a stable-URI twin of ourairports_get_airport for clients that inject resource context. All data is reachable from the tools alone — tool-only clients lose nothing. The corpus is not exposed as a resource list (enumerating 85k airports is a dump, not a discovery aid); discovery is ourairports_search_airports.
Built on @cyanheads/mcp-ts-core:
none, jwt, oauthin-memory, filesystem, Supabase, Cloudflare KV/R2/D1OurAirports-specific:
Float64Array of coordinates, country/region maps, and a tokenized text-search indexAgent-friendly output:
null, never fabricatedresolvedVia / resolutionNote, with an ambiguity warning for shared national codesA public instance is available at https://ourairports.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP, with this client config:
Add the following to your MCP client configuration file.
Or with npx (no Bun required):
Or with Docker:
No API key is required — the dataset ships with the package and the image.
For Streamable HTTP, set the transport and start the server:
data/):The bundled snapshot is as fresh as the last build:data run (or, for the Docker image, the last build). To pull the latest daily drop from the OurAirports mirror, re-run bun run build:data and rebuild. To point at an existing local data drop without rebuilding, set OURAIRPORTS_DATA_DIR.
| Variable | Description | Default |
|---|---|---|
OURAIRPORTS_DATA_DIR | Directory holding the six OurAirports CSV files. Overridable to point at a fresher local data drop. | Bundled data/ |
OURAIRPORTS_DEFAULT_SEARCH_LIMIT | Default result cap for the search/find tools when the caller omits limit (1–100). | 20 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path where the server is mounted. | /mcp |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_SESSION_MODE | HTTP session mode: stateful, stateless, or auto. This server uses stateless mode because no tool requests follow-up input. | stateless |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend (unused on the data path — the index is in-memory). | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
Build and run:
Run checks and tests:
The build stage runs bun run build:data so the dataset is fetched and baked into the image — the resulting container is fully self-contained and makes no network calls at runtime. The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/ourairports-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources and loads the bundled index at setup(). |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Six read-only airport/runway/navaid tools. |
src/mcp-server/resources | Resource definitions. The airport://{code} record. |
src/services/airport-data | The bundled-data service — CSV parsing, in-memory indices, code resolution, search, and the haversine geo scan. |
scripts/build-data.ts | Build-time fetcher that bundles the six OurAirports CSVs into data/. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.tsnull, never fabricate missing valuesAirport, runway, navaid, and frequency data from OurAirports, dedicated to the public domain. Attribution is a courtesy, not a requirement. Source CSVs are published daily at davidmegginson.github.io/ourairports-data.
Issues and pull requests are welcome. Run checks and tests before submitting:
Apache-2.0 — see LICENSE for details.