faa-airmen-registry-mcp-server
Public Hosted Server: https://faa-airmen-registry.caseyjhand.com/mcp
Overview
FAA airmen certification records from the Releasable Airmen Database, served from a local snapshot. Resolve names to separate identity candidates, read pilot and non-pilot certificates with their ordered ratings, find flight and ground instructors and remote pilots, and read informational medical fields.
Runs as a stdio process, a loopback Streamable HTTP server (directly or in Docker), or the public hosted endpoint above. A self-hosted operator builds the snapshot with a separate CLI; the server never downloads data, and no API key is needed.
Tools
Tool | Description |
| Find identity candidates by name or FAA Unique ID, optionally narrowed by certificate type, level, and ratings, and read their complete certificate records |
| Read a person's pilot, flight instructor, remote pilot, and flight engineer certificates with their aircraft type, category/class, and instrument ratings |
| Read informational medical and BasicMed fields without assessing fitness or eligibility |
| Find flight and ground instructor records by name, state, and ratings |
| Find releasable remote pilot certificate records by name or Unique ID |
| Read codes, interpretation guidance, observed type designators, and mirror status |
Related MCP server: SkyDemon MCP Server
Capability reference
faa_airmen_verify_certificate tool
mode: "name"requireslastName;firstName,state,city, andzipCodenarrow it.nameMatchapplies to each supplied name field:words(default) matches whole leading words, soJOHNfinds the FIRST NAMEJOHN ROBERT(FAA first names usually include middle names) andSMITHfinds the LAST NAMESMITH JR; only a space separates words, soSMITHdoes not findSMITH-JONES.exactcompares the whole field, andprefixmatches from its beginning, mid-word includedmode: "id"takes oneuniqueId, an opaque A or C plus seven digits that is not a certificate number, and rejects name and location fieldscertificateType(a type letter, its reference topiccertificate_typesdescription such asremote pilotorflight_instructorin any case, orCFI) selects people but returns all their certificates; people sharing a name stay separate candidatescertificateLevel(one letter from reference topiccertificate_levels; requirescertificateType),ratingCodes(1–11 codes from topicratings), andtypeRatings(1–11 designators from topictype_ratings, such asB-737) narrow candidates further. Every requested credential must sit on one certificate, and matches still return all their certificates. Repeats collapse. A bare code or designator matches any level, andlevel/codeonly that slot's level:A/B-737is a type rating held at ATP level, whilecertificateLevel: "A"withB-737asks for an ATP certificate carrying it at any level,C/B-737includedratingCodescarries the same CFII and MEI mapping asfaa_airmen_search_instructors;CFIIorMEIsent as a code matches nothing. When a zero result routes to topicratingsand the codes sent includeCFII,MEI, or one of their codes, the notice names the alternatives and, unless the call sentcertificateType: "F", says to send each with it, since a bareINSTAalso matches a pilot certificate's instrument ratingA zero result with credential filters says how many people the name search matches without them, so a name miss reads apart from a credential miss
With
FAA_AIRMEN_REDACT_PIIon (the default), name mode needs bothlastNameandfirstNameand refusesnameMatch: "prefix",city, andzipCodewithpii_redacted; city and ZIP are omitted from results
faa_airmen_get_type_ratings tool
One
uniqueIdfrom afaa_airmen_verify_certificate,faa_airmen_search_instructors, orfaa_airmen_verify_remote_pilotresult; returns every certificate from the FAA pilot files (pilot, flight instructor, authorized aircraft instructor, remote pilot, flight engineer) with orderedratingsandtypeRatingsslots, each keeping itsrawvalue,ordinal, andcodeGround instructor, mechanic, and other non-pilot certificates are not included;
faa_airmen_verify_certificateinmode: "id"returns every certificatefound: falsefor an unknown ID; a known person with only non-pilot certificates gets an emptycertificatesarray
faa_airmen_check_medical_status tool
One
uniqueIdfrom afaa_airmen_verify_certificate,faa_airmen_search_instructors, orfaa_airmen_verify_remote_pilotresult; returns each distinct reported medical record:medicalClass,medicalDateRawandmedicalExpireDateRaw(normally MMYYYY), and the BasicMed course and checklist dates (normally YYYYMMDD)foundseparates an unknown ID from a known person, andhasReportedMedicalFields: falsemeans nothing was reported, never an adverse finding. Every result carries adisclaimer: the fields are informational, not evidence of fitness or eligibilityWith
FAA_AIRMEN_REDACT_PIIon (the default), every call is refused withpii_redacted
faa_airmen_search_instructors tool
Needs at least one of
lastName,firstName,state,city,zipCode, orratingCodes;instructorTypeisflight(F),ground(G), orboth(default)ratingCodestakes 1–11 codes from reference topicratings, all required on the same certificate; repeats collapse. A bare code matches any source level, andlevel/code(therawform results return, such asF/INSTA; the level is one letter) only that level. CFII isINSTA,INSTH,INSTI, orINSTPon F and MEI isAMEorASME;INST,ADV, andBASICoccur only on GA zero result that sent
ratingCodeswith a name or location filter counts the instructors of the requested type those filters match without the codes: a positive count points to the rating codes, zero to the name and location filters. A rating-codes route afterCFII,MEI, or one of their codes also names the alternatives; after aninstructorType: "ground"search it says to send them withflightorboth, since all of them sit on FWith
FAA_AIRMEN_REDACT_PIIon (the default), a name search needs bothlastNameandfirstName, andnameMatch: "prefix",city, andzipCodeare refused withpii_redacted; city and ZIP are omitted from resultsMatches carry their complete qualifying
instructorCertificates. Legacy expiry fields don't establish currency, and CFI certificates no longer expire under the December 2024 rule
faa_airmen_verify_remote_pilot tool
Same
mode,uniqueId, and name selectors asfaa_airmen_verify_certificate; returns only people holding U certificates, with all of them inremotePilotCertificatesWith
FAA_AIRMEN_REDACT_PIIon (the default), the same name restrictions apply: both names required, andnameMatch: "prefix",city, andzipCoderefused withpii_redactedCertificate presence doesn't establish Part 107 recurrent-training currency or operating authority
faa_airmen_list_reference tool
topiciscertificate_types,certificate_levels,ratings,type_ratings,identifiers,medical_guidance, ormirror_status; the four vocabulary topics take a case-insensitivefilterof up to 80 charactersStatic topics and
mirror_statusanswer before a snapshot exists;type_ratingslists the designators observed in the imported snapshotmirror_statusreturnsmirrorStatus.state(missing,ready,unavailable),refreshState(idle,running,failed, orunreadablewhenREFRESH.jsoncan't be read or isn't a valid refresh record; removing it is safe, and the next refresh rewrites it),lastRefreshFailure(the stage of a refresh that didn't complete, such asacquisition,validation,disk,cancelled,timeout, orinterruptedfor one whose process ended without recording a result), and the latest generation's provenance, without filesystem paths
Features
Built 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-specific:
Offline SQLite lookups over the four monthly source files (
PILOT_BASIC,NONPILOT_BASIC,PILOT_CERT,NONPILOT_CERT), joined by FAA Unique ID, keeping every distinct certificate and differing identity variantStreet addresses are discarded before anything is stored. City and ZIP import, output, and filtering need
FAA_AIRMEN_INCLUDE_LOCATION=true; state is always availableServing-time redaction is on by default (
FAA_AIRMEN_REDACT_PII, onlyfalseturns it off): name searches need first and last name,prefixmatching and city/ZIP filters are refused, city and ZIP are omitted from output, andfaa_airmen_check_medical_statusrefuses every call. Refusals carry the reasonpii_redacted, every successful response carriespiiRedacted, and the mirror import is unaffectedThe
faa-airmen-registry-mirrorCLI downloads and validates each snapshot into an immutable generation; a failed refresh leaves the last complete one servingThe FAA omits airmen who withhold address release and publishes no certificate numbers or recurrent-training dates. A miss is not evidence that someone is uncertificated, and a match is not a current-privilege determination; use the FAA's official services for authoritative answers
Agent-friendly output:
Provenance on every snapshot-backed response: source name, snapshot month, source age with a
staleflag, generation ID, and the generation's location import policy, plus the normalizedappliedFilters;faa_airmen_list_referencetopicmirror_statusreturns the full record, including hashes, row counts, and source-quality warning countsWhole-record paging: list tools return complete people within a ~24 KiB response budget, so
limit(default 10, at most 50; a larger limit returns 50 with a notice) is an upper bound.totalCount,returnedCount, andnextOffsetdescribe the actual page, and a page the budget cut short oflimitsays so in its notice with the offset to continue from; offsets stop at 10,000, whereatPagingCapasks for narrower filtersA published Agent Skill,
faa-airmen-registry-guide, served over MCP asskill://faa-airmen-registry-guide/SKILL.mdand shipped in the plugin, npm package,.mcpb, and image: mapping CFII, MEI, ATP, and type-rating shorthand to codes, one call per alternative code, name matches that miss silently, reading each rating at its own level, and the stop rule before reporting a credential absentTyped failures that name the next call:
mirror_missing,location_access_disabled,pii_redacted,invalid_filter, and the other declared reasons each carry a recovery step. A validation failure names the field and its expected form (state must be a two-letter USPS state, territory, or military code.), never the value, and empty pages still return totals and guidance
Getting started
Connect to the hosted instance, no install or snapshot:
claude mcp add --transport http faa-airmen-registry-mcp-server https://faa-airmen-registry.caseyjhand.com/mcpThe hosted instance runs with redaction on: name searches need both first and last names, nameMatch: "prefix" and city/ZIP searches are refused, and medical lookups are unavailable. Whole leading words remain supported by the default words matching. For the full surface, self-host with FAA_AIRMEN_REDACT_PII=false.
Or run it locally. A local install serves its own snapshot, so build it before connecting a client:
export FAA_AIRMEN_MIRROR_DIR="$HOME/.local/share/faa-airmen-registry-mcp-server"
npx -y --package @cyanheads/faa-airmen-registry-mcp-server faa-airmen-registry-mirror initinit downloads the FAA archive (about 57 MB) and builds a ~1.75 GB snapshot in about 12 minutes; allow about 3 GB of disk. Point the MCP client at the same FAA_AIRMEN_MIRROR_DIR, and set FAA_AIRMEN_REDACT_PII=false on a personal install that wants the full surface. Refreshing, pruning old generations, and keeping city and ZIP are covered in docs/snapshot.md.
Then add it to Claude Code with the same snapshot directory:
claude mcp add faa-airmen-registry-mcp-server -e FAA_AIRMEN_MIRROR_DIR="$FAA_AIRMEN_MIRROR_DIR" -- npx -y @cyanheads/faa-airmen-registry-mcp-serverFor other clients, add this to the MCP config file:
{
"mcpServers": {
"faa-airmen-registry-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/faa-airmen-registry-mcp-server"],
"env": { "FAA_AIRMEN_MIRROR_DIR": "/absolute/path/to/airmen-mirror" }
}
}
}For Docker, Streamable HTTP, or a source build, see Running the server.
Configuration
Variable | Description | Default |
| Persistent snapshot directory, shared by the CLI and the server. | Platform user-data directory plus |
|
|
|
| Serving-time redaction, on by default; only |
|
| Source-age warning threshold, integer 1–366; never a credential verdict. |
|
| Transport: |
|
| HTTP bind address. |
|
| HTTP server port. |
|
| HTTP endpoint path. Use a literal path such as |
|
| Public HTTP(S) base URL. Omit user information, query parameters, and fragments. | Unset |
| HTTP session mode: |
|
| Authentication: |
|
| Log level ( |
|
| Enable OpenTelemetry. |
|
See .env.example for the common framework overrides. Framework storage settings don't relocate the FAA snapshot.
When failed-call payload logging is enabled, the server redacts firstName, lastName, uniqueId, city, and zipCode by key regardless of the serving-time privacy setting.
Running the server
Local development
Requires Bun v1.4.4 or higher (or Node.js v24+) and about 3 GB of persistent local storage for the snapshot; network access is needed only for init and refresh. Under Node, better-sqlite3 loads prebuilt bindings for macOS, Linux, and Windows on x64 and arm64 with no compiler step. Execution is tested on macOS under Node and Bun.
Clone and install:
git clone https://github.com/cyanheads/faa-airmen-registry-mcp-server.git cd faa-airmen-registry-mcp-server && bun install cp .env.example .env # set FAA_AIRMEN_MIRROR_DIR, and FAA_AIRMEN_INCLUDE_LOCATION if you want city and ZIP # FAA_AIRMEN_REDACT_PII=false lifts serving-time redaction on a personal local installBuild, initialize the snapshot, and run:
# One-time build and snapshot bun run rebuild node --env-file=.env dist/cli/mirror.js init # Run the built server bun run start:stdio # or, on loopback at http://127.0.0.1:3010/mcp bun run start:httpThe CLI doesn't load
.envitself; pass--env-file=.envor export the variables.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
The image keeps its snapshot in /data/airmen, so initialize a named volume first; run the same command with refresh, verify, or status for maintenance, and with prune once every server container using the volume is stopped:
docker volume create faa-airmen-data
docker run --rm -v faa-airmen-data:/data/airmen ghcr.io/cyanheads/faa-airmen-registry-mcp-server:latest bun dist/cli/mirror.js init
docker run --rm -p 127.0.0.1:3010:3010 -v faa-airmen-data:/data/airmen ghcr.io/cyanheads/faa-airmen-registry-mcp-server:latestThe image defaults to HTTP transport, stateless session mode, and logs to /var/log/faa-airmen-registry-mcp-server; the last command publishes it on loopback only, at http://127.0.0.1:3010/mcp. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-v", "faa-airmen-data:/data/airmen", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/faa-airmen-registry-mcp-server:latest"]. To build locally, docker build -t faa-airmen-registry-mcp-server .; OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Directory | Purpose |
| Tool definitions ( |
| FAA acquisition, streaming import, immutable snapshot lifecycle, queries, and reference tables. |
| The |
| Server-specific environment variable parsing and validation with Zod. |
| The published |
| Synthetic-source lifecycle, query, and MCP contract tests. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicThe snapshot is operator-owned and read-only; keep it in the service layer and use
ctx.stateonly for tenant stateRegister new tools in the
createApp()arrays insrc/index.tsValidate source rows → normalize to domain types → return the output schema; never fabricate missing fields, never persist street addresses, and use synthetic identities in tests and examples
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Offline, keyless lookup of the US civil aircraft registry — decode N-numbers, search records.
FAA civil aircraft registry — resolve a US tail number (N-number) to its aircraft, engine…
Offline global aviation reference: airports, runways, navaids, frequencies from OurAirports.
Query FDA data on drugs, food, devices, and recalls via openFDA. STDIO or Streamable HTTP.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables offline lookup and search of the US civil aircraft registry by N-number, owner, type, or state, returning detailed aircraft and ownership information.372 npm4Apache 2.0
- AlicenseAqualityCmaintenanceEnables authenticated access to a pilot's flight-planning account, letting an assistant search installed airfield charts offline, pull full airfield records with pilot notes and live feedback, and list, read, or download flightplans from local disk or cloud storage.102MIT
- AlicenseAqualityCmaintenanceEnables agents to search live public airfares between airports across a date window or flexibility range, returning itineraries with prices, times, layovers, and cheapest/fastest/compromise picks, plus offline airport resolution. It runs locally over stdio with no API key, and reports missing fields honestly rather than inventing bags, fares, or prices.2MIT
- AlicenseNot gradedqualityBmaintenanceEnables checking US airports for ground stops, Ground Delay Programs, delays, and closures, while listing all active national airspace events and reading the Command Center's operations plan and full ATCSCC advisory text. It runs over stdio or Streamable HTTP and also decodes FAA event types, traffic-management terms, ARTCC codes, and identifier formats.152 npm1Apache 2.0