preflight-mcp
preflight-mcp
An MCP server that assembles the preflight information required by 14 CFR §91.103 ("Preflight action"), so a Claude-based assistant can put together a real go/no-go briefing for a route — not a generic weather wrapper, but a tool set scoped to what the regulation actually requires a pilot in command to know before flying.
Regulatory mapping
§91.103 requirement | Tool | Status |
(a) Weather reports and forecasts |
| implemented |
(b) Runway lengths at airports of intended use |
| implemented |
(b) Takeoff/landing distance data |
| implemented (seeded with approximate Cessna 172R/S data) |
(a) Fuel requirements |
| implemented |
(a) Alternatives if the flight can't be completed |
| implemented (requires a candidate airport list — see limitations) |
(a) Known ATC traffic delays | — | not built; no clean free data source |
NOTAMs (part of "all available information") |
| implemented, but returns a clear "not configured" error until you supply FAA API credentials — see Configuring NOTAM credentials |
Setup
npm install
npm run buildFor local development without a build step:
npm run devRun the test suite with npm test, or npm run typecheck for a type-only check.
Tools
get_metar_taf
Fetches decoded current METAR and TAF data for one or more ICAO airport identifiers from the free, no-auth aviationweather.gov API.
// input
{ "airportIds": ["KXNA", "KTUL"], "reportTypes": ["metar", "taf"] }// output (abridged)
{
"results": [
{
"airportId": "KXNA",
"metar": { "raw": "...", "flightCategory": "VFR", "ceilingFt": null, "windSpeedKt": 6, ... },
"taf": { "raw": "...", "forecasts": [ { "ceilingFt": 4000, ... } ] }
}
],
"notFound": []
}An airport id found in only one of METAR/TAF has the other field set to
null (not an error). An id absent from both is listed in notFound.
ceilingFt is decoded from the lowest broken/overcast cloud layer — the
single most decision-relevant field for a VFR go/no-go call.
get_airport_info
Fetches runway lengths/widths/surfaces and field elevation for a single ICAO airport identifier.
// input
{ "airportId": "KXNA" }// output
{
"airportId": "KXNA",
"name": "FAYETTEVILLE/SPRINGDALE/ROGERS/NORTHWEST ARKANSAS NTL",
"fieldElevationFt": 1286,
"lat": 36.2816,
"lon": -94.3078,
"runways": [
{ "id": "16L/34R", "lengthFt": 8801, "widthFt": 150, "surface": "Concrete", "alignmentDegMagnetic": 160 }
],
"warnings": []
}An unknown airport id is a hard error (this tool takes a single required input, unlike the batch weather tool).
aircraft_performance
Computes takeoff or landing ground roll and total distance over a 50ft obstacle, given density-altitude-relevant conditions.
// input
{
"operation": "takeoff",
"pressureAltitudeFt": 2000,
"temperatureC": 25,
"weightLbs": 2500,
"headwindKt": 5,
"runwaySurface": "paved",
"obstacleHeightFt": 50
}// output
{
"groundRollFt": 1009,
"totalDistanceOverObstacleFt": 1781,
"adjustments": {
"baseGroundRollFt": 1096,
"baseOverObstacleFt": 1934,
"weightAdjustmentPct": -0.025,
"windAdjustmentFt": -105,
"surfaceAdjustmentFt": 0
},
"warnings": [
"Weight adjustment below max gross is an approximation (linear rule of thumb), not a charted lighter-weight table."
]
}Seeded with approximate Cessna 172R/S (2450–2550 lb, Lycoming IO-360) POH
performance data — see Known limitations. Inputs
outside the charted pressure-altitude/temperature range are extrapolated
and flagged in warnings rather than silently returned as if charted.
estimate_fuel
Basic burn-rate × time fuel planning with Part 91 reserve minimums and an explicit go/no-go verdict.
// input
{
"route": [{ "airportId": "KXNA" }, { "airportId": "KTUL", "distanceNm": 85 }],
"cruiseSpeedKt": 110,
"fuelBurnGph": 8.5,
"usableFuelGal": 50,
"flightRules": "day-vfr"
}// output (abridged)
{
"legs": [{ "from": "KXNA", "to": "KTUL", "distanceNm": 85, "estimatedTimeMin": 46 }],
"reserveMinutes": 30,
"tripFuelGal": 6.57,
"totalRequiredGal": 10.82,
"fuelMarginGal": 39.18,
"goNoGo": "go",
"reason": "Usable fuel exceeds required fuel by 39.2 gal."
}This tool does not call get_airport_info itself — if you omit
distanceNm for a leg, fetch both airports' coordinates first and compute
the distance before calling this tool.
find_alternates
Finds alternate airports meeting minimum ceiling/visibility, checked against current METAR.
// input
{
"airportId": "KXNA",
"radiusNm": 50,
"minCeilingFt": 1000,
"minVisibilitySm": 3,
"candidateAirportIds": ["KFYV", "KROG", "KBVX"]
}Requires candidateAirportIds — see Known limitations.
get_notams
Fetches current NOTAMs for a single ICAO airport identifier from the FAA NOTAM Search API.
// input
{ "airportId": "KJLN" }// output
{
"airportId": "KJLN",
"totalCount": 1,
"notams": [
{
"number": "A0001/26",
"type": "N",
"issued": "2026-01-01T00:00:00.000Z",
"effectiveStart": "2026-01-01T00:00:00.000Z",
"effectiveEnd": "PERM",
"text": "RWY 13/31 CLSD",
"classification": "DOM"
}
]
}Without credentials configured, it returns an MCP tool error instead of silently omitting NOTAMs from a briefing:
// error text when FAA_NOTAM_CLIENT_ID / FAA_NOTAM_CLIENT_SECRET are unset (isError: true)
[notams_not_configured] FAA NOTAM API credentials are not configured. Register for the
NOTAM Search API at https://api.faa.gov, then set FAA_NOTAM_CLIENT_ID and
FAA_NOTAM_CLIENT_SECRET in this server's environment — see README.md.See Configuring NOTAM credentials and Known limitations (the decoded NOTAM shape hasn't been verified against a live response, since this account has no registered credentials yet).
Known limitations
No ATC-delay data. §91.103(a) also asks pilots to consider known ATC traffic delays; there's no clean free API for this, so it isn't built.
find_alternateshas no geo-radius search. aviationweather.gov's airport endpoint only accepts specific ids, not a "within N nm" query, so this tool requires the caller to supplycandidateAirportIds. A future version could use the FAA NASR airport dataset (there's prior art for this in a sibling project,skyfleet-aviation-data) to support true radius-based discovery.get_notamsneeds FAA API credentials, which aren't configured by default. See Configuring NOTAM credentials. Separately, the decoded NOTAM shape insrc/lib/faaNotamClient.tsis based on FAA's publicly documented schema but has not been verified against a live response — if fields don't line up once you have real credentials,decodeNotam()is the only place that should need adjusting.aircraft_performanceis seeded with approximate data, not your exact aircraft's charted POH numbers. Once you've confirmed your aircraft's actual performance charts, replace the tables insrc/data/c172-performance.ts—DEFAULT_AIRCRAFTinsrc/data/config.tsis the intended swap point.
Configuring NOTAM credentials
get_notams reads FAA_NOTAM_CLIENT_ID and FAA_NOTAM_CLIENT_SECRET from
the server process's environment — there's no config file or hardcoded
default. Until both are set, the tool returns a notams_not_configured
error rather than silently skipping NOTAMs.
Register for the NOTAM Search API at https://api.faa.gov to get a
client_id/client_secretpair.Provide them to the server one of these ways:
Local dev (
npm run dev): copy.env.exampleto.env, fill in the two values, and run withnode --env-file=.env(Node 20.6+) — e.g.node --env-file=.env node_modules/.bin/tsx src/index.ts— or export them in your shell before runningnpm run dev..envis gitignored; never commit real credentials.Claude Code, via
claude mcp add:claude mcp add --scope user preflight \ --env FAA_NOTAM_CLIENT_ID=your-client-id \ --env FAA_NOTAM_CLIENT_SECRET=your-client-secret \ -- npx tsx /absolute/path/to/preflight-mcp/src/index.tsClaude Desktop / manual
.mcp.json, via theenvblock:{ "mcpServers": { "preflight": { "command": "node", "args": ["/absolute/path/to/preflight-mcp/dist/index.js"], "env": { "FAA_NOTAM_CLIENT_ID": "your-client-id", "FAA_NOTAM_CLIENT_SECRET": "your-client-secret" } } } }
Adding to Claude Desktop or Claude Code
Claude Desktop (claude_desktop_config.json), using the built output.
Replace /absolute/path/to/preflight-mcp with wherever you cloned this repo:
{
"mcpServers": {
"preflight": {
"command": "node",
"args": ["/absolute/path/to/preflight-mcp/dist/index.js"]
}
}
}Claude Code — use the claude mcp add CLI rather than editing config by
hand. Dev-mode via tsx directly against source (no build step, faster
iteration):
claude mcp add --scope user preflight -- npx tsx /absolute/path/to/preflight-mcp/src/index.tsOr against the built output:
claude mcp add --scope user preflight -- node /absolute/path/to/preflight-mcp/dist/index.js--scope user makes the server available in every project. Use
--scope project instead if you want it scoped to a single project's
.mcp.json (shareable via that project's repo). Verify registration with
claude mcp list, then start a new Claude Code session — existing
sessions won't pick up a newly added server.
Both approaches produce the same underlying command/args you'd write by
hand into a .mcp.json:
{
"mcpServers": {
"preflight": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/preflight-mcp/src/index.ts"]
}
}
}Both use stdio transport — no auth or network exposure required, since the server runs locally alongside the client.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/eddiehale3/preflight-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server