Offline, keyless lookup of the US civil aircraft registry — decode N-numbers, search records.
Decode N-numbers to aircraft, engine, status, and owner; search the US civil aircraft registry by owner, type, or state; resolve active/deregistered/reserved status — offline via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://faa-aircraft-registry.caseyjhand.com/mcp
The US civil aircraft registry, mirrored offline from the FAA's Releasable Aircraft Database — there is no live FAA registry API. Decode an N-number to its aircraft, engine, and registered owner; resolve active, deregistered, or reserved status; search by owner, make/model, state, or Mode S code; and decode manufacturer/model codes to full aircraft specs. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
faa_lookup_registration | Decode one N-number to its full pre-joined active record — aircraft make/model, engine, year, airworthiness, registration status, Mode S code, and registered owner (when redaction is off). |
faa_get_registration_status | Resolve an N-number across all three status files (active, deregistered, reserved) in priority order, returning a definitive recordType. |
faa_search_registrations | Search active registrations by owner name, make/model, state, aircraft type, or Mode S code. |
faa_search_aircraft_types | Search the aircraft reference table by manufacturer/model name, type, or category to discover manufacturer-model codes. |
faa_get_aircraft_type | Decode a 6–7-character manufacturer/model/series code to aircraft specs — category, type, engine, seats, weight class, cruise speed, type-certificate data. |
| Resource | Description |
|---|---|
faa://registration/{nNumber} | Full registration record for one N-number — the same decoded, pre-joined payload as faa_lookup_registration. |
Also reachable via faa_lookup_registration; search collections aren't exposed as resources.
faa_lookup_registration toolN12345 or 12345 — leading N optional, normalized before lookupMASTER → ACFTREF → ENGINE and decodes every coded field (aircraft type, engine type, status, airworthiness class, region) as both raw code and labelFAA_REDACT_OWNER_PII=false; otherwise ownerRedacted: true flags that they were withheldnot_found (well-formed but no active registration — use faa_get_registration_status), invalid_n_number (malformed input)faa_get_registration_status toolMASTER), deregistered (DEREG), and reserved (RESERVED) files in priority orderrecordType: active, deregistered, reserved, or unknown — a never-issued number resolves to unknown, not an errorinvalid_n_number for malformed inputfaa_search_registrations toolownerName, makeModel, state, aircraftType, modeSCode — at least one requiredFAA_REDACT_OWNER_PII is on; typed owner_search_disabled error names the alternative filterslimit 1–200 (default 25) and offset pagination; every response reports totalCount, and a truncated page carries nextOffsetfaa_lookup_registration for full detailno_filters when no filter is suppliedfaa_search_aircraft_types toolquery (manufacturer/model name, full-text), aircraftType code, category code — at least one requiredfaa_get_aircraft_typelimit 1–200 (default 25) and offset pagination; every response reports totalCount, and a truncated page carries nextOffsetno_filters when no filter is suppliedfaa_get_aircraft_type toolfaa_search_aircraft_typesnot_found (well-formed code, no reference record), invalid_code (malformed input)faa://registration/{nNumber} resourcefaa_lookup_registration — application/json, cached 1 hour, private scopenNumber accepts N12345 or 12345 (leading N optional)not_found, invalid_n_number — same conditions as the toolBuilt 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.
FAA registry-specific:
MirrorService: mirror:init builds the index out-of-band, and the HTTP server schedules a daily refresh aligned to the FAA's nightly re-releaseMASTER → ACFTREF → ENGINE records — one call returns decoded aircraft, engine, and status instead of raw join codesAgent-friendly output:
truncated with shown/cap when the result set is capped, so a partial page is never read as the wholerecordType and ownerRedacted flags let callers branch on data, not string parsingA public instance is available at https://faa-aircraft-registry.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"faa-aircraft-registry-mcp-server": {
"type": "streamable-http",
"url": "https://faa-aircraft-registry.caseyjhand.com/mcp"
}
}
}
First run requires building the local index — see First-run setup below. The package does not ship the FAA data; until
mirror:inithas run once, queries fail with a clear "run mirror:init" error.
Add the following to your MCP client configuration file:
{
"mcpServers": {
"faa-aircraft-registry-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/faa-aircraft-registry-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"FAA_MIRROR_PATH": "/path/to/faa-registry.db"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"faa-aircraft-registry-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/faa-aircraft-registry-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"FAA_MIRROR_PATH": "/path/to/faa-registry.db"
}
}
}
}
Or with Docker (mount a volume at /usr/src/app/.mirror so the built index persists across containers — build it once with docker exec … bun run mirror:init):
{
"mcpServers": {
"faa-aircraft-registry-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-v", "faa-registry:/usr/src/app/.mirror",
"ghcr.io/cyanheads/faa-aircraft-registry-mcp-server:latest"
]
}
}
}
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
FAA_MIRROR_PATH points the server at the index that mirror:init builds — set it to a persistent path so the index survives across runs. Owner PII is redacted by default; set FAA_REDACT_OWNER_PII=false only on a trusted local install to expose owner detail.
The FAA Releasable Aircraft Database is not bundled with the package — it is a public dataset (~70 MB compressed, refreshed daily by the FAA) that the operator downloads and indexes once before first use. Building the index produces a local SQLite file of a few hundred MB.
After installing, build the index out-of-band (never on server startup — a full parse of ~1M rows must not block start):
# Build the local index from the live FAA download (idempotent, resumable)
FAA_MIRROR_PATH=/path/to/faa-registry.db bun run mirror:init
# Verify it built (readiness + integrity check)
FAA_MIRROR_PATH=/path/to/faa-registry.db bun run mirror:verify
# Rebuild from the latest FAA release (run on a schedule, or out-of-band for stdio)
FAA_MIRROR_PATH=/path/to/faa-registry.db bun run mirror:refresh
FAA_MIRROR_PATH (and the same env var in your MCP client config) at a persistent location so the built index is reused across runs. Default: .mirror/faa-registry.db.mirror:refresh aligned to the FAA's nightly re-release (~11:30 PM Central). If an interrupted rebuild leaves the index empty, queries fail loudly with ServiceUnavailable until the rebuild succeeds. Under stdio, run mirror:refresh out-of-band (e.g. a cron job)..mirror data directory owned by the runtime user. Mount a volume there and run mirror:init once (e.g. docker exec <container> bun run mirror:init, or a one-shot init job against the shared volume) so the index persists across container restarts.ServiceUnavailable error whose recovery hint points to mirror:init — there is no live API to fall back to, so the cold state surfaces loudly rather than returning empty results.The source URL is overridable via FAA_DATABASE_URL (for a private or cached mirror); it is read only at ingest time, never per request.
bun:sqlite is built into Bun; a Node-only deployment adds better-sqlite3 (already declared as an optional peer dependency).FAA_MIRROR_PATH.mirror:init / mirror:refresh time to download the FAA ZIP. Not needed at query time.For local development or self-hosting from source:
git clone https://github.com/cyanheads/faa-aircraft-registry-mcp-server.git
cd faa-aircraft-registry-mcp-server
bun install
bun run mirror:init
| Variable | Description | Default |
|---|---|---|
FAA_REDACT_OWNER_PII | Redact owner name/address from output and disable owner-name search. Fail-safe: unset, empty, or malformed resolves to redacted. Set false only on a trusted local install. | true |
FAA_MIRROR_PATH | Filesystem path to the SQLite index file. Point at a persistent path; mount a volume here in production. | .mirror/faa-registry.db |
FAA_DATABASE_URL | Source URL for the FAA Releasable Aircraft Database ZIP, used by mirror:init / mirror:refresh only. Overridable for a private/cached mirror; never read at request time. | FAA registry ZIP |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_SESSION_MODE | Session mode: auto (resolves to stateful), stateful, or stateless. The server declares stateless in source because it has no multi-round-trip input; setting this overrides that declaration. | stateless |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path where the MCP server is mounted. | /mcp |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
See .env.example for the full list of optional overrides.
The FAA registry is releasable public record, but MASTER, DEREG, and RESERVED carry registrant names and physical addresses (deregistered records carry both mailing and physical address blocks plus co-owner names). This server ships a first-class redaction mode, redacted by default:
FAA_REDACT_OWNER_PII defaults to true. Unset, empty, or malformed all resolve to redacted, so a deployment can never leak PII by omission or misconfiguration.faa_search_registrations rejects ownerName). Disabling search matters — output-hiding alone would let an agent confirm a person↔aircraft link by probing the search input.ownerRedacted: true on every affected payload.FAA_REDACT_OWNER_PII=false — appropriate for a trusted local or self-hosted install, not a shared endpoint.Build and run:
# One-time build
bun run rebuild
# Build the local index (see First-run setup)
bun run mirror:init
# 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 faa-aircraft-registry-mcp-server .
docker run --rm -e MCP_TRANSPORT_TYPE=http -p 3010:3010 \
-v faa-registry:/usr/src/app/.mirror faa-aircraft-registry-mcp-server
# Build the index once against the mounted volume:
docker run --rm -v faa-registry:/usr/src/app/.mirror faa-aircraft-registry-mcp-server bun run mirror:init
The Dockerfile defaults to HTTP transport with stateless sessions, ships the mirror:init/mirror:refresh/mirror:verify CLI, pre-creates a writable .mirror data directory owned by the runtime user (mount a volume there and run mirror:init once to build and persist the index), and logs to /var/log/faa-aircraft-registry-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 and the resource, inits the registry service, and schedules the daily refresh under HTTP transport. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/services/registry | Registry service — the SQLite + FTS5 mirror, ingester, decode maps, N-number normalizer, and the owner-PII redaction gate. |
scripts/faa-mirror-*.ts | Mirror lifecycle CLI — mirror:init, mirror:refresh, mirror:verify. |
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.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
The FAA Releasable Aircraft Database is a public-domain US Government work, published by the FAA Civil Aviation Registry. This project redistributes none of that data; operators download it directly from the FAA at install time.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/faa-aircraft-registry-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-faa-aircraft-registry-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/faa-aircraft-registry-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/faa-aircraft-registry-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.