Offline global aviation reference — airports, runways, navaids, frequencies from OurAirports.
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
Offline aviation reference data from the public-domain OurAirports dataset — airports, runways, navaids, and radio frequencies, bundled with the package rather than fetched at runtime. Resolve an airport by any code, search by name or facets, ground a coordinate in the nearest airports or navaids, and look up the countries and regions in the dataset. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| 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. |
| Resource | Description |
|---|---|
airport://{code} | Single airport record by any code (IATA/ICAO/GPS/local/ident), with runways and frequencies inline. |
All data is also reachable via ourairports_get_airport — tool-only clients lose nothing. Not exposed as a resource list (enumerating 85k airports is a dump, not a discovery aid); discovery is ourairports_search_airports.
ourairports_search_airports toolcountry (ISO 3166-1 alpha-2), region (ISO 3166-2), and type — country/region are exact match, case-insensitive, with surrounding whitespace ignoredinclude_closedourairports_get_airportlimit accepts 1–100 rows and defaults to OURAIRPORTS_DEFAULT_SEARCH_LIMIT (20); truncation reports the total matched count, cap, and recovery guidanceourairports_search_runways toolcountry, 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 setlimit accepts 1–100 rows and defaults to OURAIRPORTS_DEFAULT_SEARCH_LIMIT (20); truncation reports the total matched count, cap, and recovery guidanceourairports_get_airport toolcode 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_airports tooldistanceKm and bearingDeg (degrees true) from the query pointradius_km (1–500, default 100); limit (1–50) defaults to OURAIRPORTS_DEFAULT_SEARCH_LIMIT, clamped to this tool's 50-row max; optional type filter; include_closed opt-inradius_kmourairports_find_navaids toollatitude + longitude (+ optional radius_km, 1–500, default 100) ranks navaids nearest-first with distance and bearing; limit (1–50) defaults to OURAIRPORTS_DEFAULT_SEARCH_LIMIT, clamped to 50airport_code returns the navaids serving that airportmode_conflict validation errorfrequencyKhz 114500) and MHzunknown_code error) from "airport found but has no associated navaids" (empty list with a note)ourairports_list_countries toolcontinent filter (AF, AN, AS, EU, NA, OC, SA); optional include_regions nests each country's ISO 3166-2 regions with their airport countscountry / region filter values used by ourairports_search_airportsairport://{code} resourceourairports_get_airport — full record with runways and radio frequencies inlinecode resolves case-insensitively across all five identifier spaces (priority: ident → ICAO → IATA → GPS → local)unknown_code error with a recovery hint when no identifier space matchesBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
OurAirports-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:
{
"mcpServers": {
"ourairports-mcp-server": {
"type": "streamable-http",
"url": "https://ourairports.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"ourairports-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/ourairports-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"ourairports-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/ourairports-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"ourairports-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/ourairports-mcp-server:latest"
]
}
}
}
No API key is required — the dataset ships with the package and the image.
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/ourairports-mcp-server.git
cd ourairports-mcp-server
bun install
data/):bun run build: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:
# One-time data fetch + build
bun run build:data
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t ourairports-mcp-server .
docker run --rm -i -e MCP_TRANSPORT_TYPE=stdio ourairports-mcp-server
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/. |
framework-skills/ | Project development skills synced from @cyanheads/mcp-ts-core. |
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.
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.
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/ourairports-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-ourairports-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/ourairports-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/ourairports-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.