adsb-mcp
You can query live local ADS-B aircraft data through MCP tools backed by your own receiver's aircraft.json.
List aircraft overhead:
aircraft_overheadreturns aircraft currently in receiver range, nearest first when the receiver site is configured, with optional grounded aircraft.Search around any point:
aircraft_nearreturns aircraft within a chosen radius of a supplied latitude/longitude, without needing a receiver location.Find specific aircraft:
find_aircraftmatches partial callsign, ICAO hex, or registration text case-insensitively.Get structured flight data: results can include hex, callsign, registration, type, altitude, ground speed, track, position, distance/bearing, and seen time, with
ok/refused/errorstatus.Use optional recorder features if enabled: history, traffic stats, route enrichment, and the flight board are available per the README configuration, though the provided schema exposes only the three live-query tools.
Provides an airport-style ADS-B flight board that can be embedded in Home Assistant as a Webpage card, showing arrivals, departures, overhead aircraft, and recent passes.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@adsb-mcp what aircraft are flying overhead right now?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
adsb-mcp
Live local ADS-B flight data as MCP tools, over your own receiver's aircraft.json (readsb / tar1090 /
dump1090) — keyless, live, your own data. Answers "what's flying overhead right now?" and, with the
optional recorder on, "have we seen this one before?" / "what flies here most?".
Tools
Tool | What it does |
aircraft_overhead | Aircraft in range of the receiver, nearest first. Distances from the site ( |
aircraft_near | Aircraft within a radius of a point you pass (no receiver location needed). |
find_aircraft | Match by callsign / ICAO hex / registration (partial, case-insensitive). |
traffic_stats | Recorded passes over a window, grouped by airframe, callsign, type, outcome or local hour; most or least common. |
aircraft_history | Past passes of an airframe / callsign / registration: "seen before?", when, how low, landed or went around. |
Data is structured (hex, callsign, registration, type, altitude_ft, ground_speed_kt, track_deg, lat/lon,
distance_km + bearing_deg from the reference point, seen_s). The site's coordinates are never echoed. Every result has a status: ok / refused
(caller-fixable, with advice) / error (receiver unreachable). ADS-B carries no route; the optional
OpenSky enrichment below adds one.
Related MCP server: Aviation MCP Server
Run
uv sync # (--system-certs behind a TLS-inspecting proxy)
# stdio:
uv run adsb-mcp serve
# streamable-http (behind mcpo / on the LAN):
ADSB_MCP_TOKEN=... ADSB_URL=http://<feeder-host>:8080/data/aircraft.json \
ADSB_SITE_LAT=... ADSB_SITE_LON=... ADSB_POLL_SECONDS=15 \
uv run adsb-mcp serve --transport http --host 0.0.0.0 --port 8000
uv run pytest # no network: the feeder is fakedConfig (env)
Var | Default | Notes |
|
| Your feeder's live snapshot (readsb / tar1090 path). |
| — | Receiver location: the exact reference point. Home-location PII — from a secret store, never committed, never echoed in results. |
|
| Off = approximate location: when distances are from the site, |
| — | The nearby airport (e.g. |
| — | Field elevation. With the airport position, turns on arrival / go-around / departure tracking. |
| — | Nearby airfields OpenSky reports instead of your airport (e.g. |
| — | Callsign / registration patterns marking military or state aircraft (e.g. |
| — | Display name for the board header (e.g. |
|
| The flight board at |
| — | Bearer required on streamable-http when set. |
|
| Seconds. |
|
|
|
|
| SQLite store of passes ( |
|
| A pass = an airborne aircraft within this radius of the reference point, or on approach to the airport. |
|
| Close a pass after the aircraft has been gone, or out of the zone, this long. |
|
| An aircraft back within this long of its pass closing continues that pass, so a coverage gap doesn't split one visit in two. |
|
| An aircraft lost on approach stays open this long (it's presumably landing below coverage). |
|
| How far from the airport a descent counts as an approach. |
| — | OpenSky API client (OAuth2). Both set + the recorder on = route enrichment. Secret: from the environment only. |
|
| Most OpenSky credits the server spends per UTC day (a past-day lookup costs ~30). |
|
| Stop for the day when OpenSky reports fewer credits left than this. |
|
| IANA zone for local times and hour-of-day stats (e.g. |
History: passes
With ADSB_POLL_SECONDS>0 and a reference point (the site, else the airport), the server polls the feeder
in the background, whether or not anyone is asking, and records one row per pass: a visit, not a poll.
A ~6-minute overflight is 1 row, not ~24. Each pass keeps first / last seen, min / max altitude, closest
approach, max RSSI and the number of snapshots. The live tools reuse the recorder's latest snapshot while
it's fresh, so the feeder isn't hit twice.
With the airport elevation set, each pass also gets an outcome: arrival (seen landing),
arrival_presumed (lost on approach: coverage usually ends before the runway, so the pass is held open
ADSB_ARRIVAL_GRACE_SECONDS), go_around, departure or overflight, plus a go_arounds count. The
heuristics are simple and documented in passes.py.
A pass that circles — heading changes of 360° or more while staying within about 15 km — is recorded
as loitering (patrols, training, survey flights), and its climbs and descents don't count as go-arounds.
Every pass also carries its ADS-B category (rotorcraft: true for helicopters) and a military flag
(readsb's database flag, or a match in ADSB_MILITARY_PATTERNS). traffic_stats can group by category
and narrow to only="military" or only="rotorcraft".
A failed poll never closes a pass, and open passes are written through, so a restart resumes them. An
aircraft that drops out of coverage briefly (a hold, a turn behind terrain) continues its pass when it
comes back within ADSB_PASS_MERGE_SECONDS; a confirmed landing always ends the pass. With
neither a site nor an airport, passes can't be tracked and the recorder stays off.
Claude Desktop / Claude Code (stdio)
{
"mcpServers": {
"adsb": {
"command": "uv",
"args": ["--directory", "/path/to/adsb-mcp", "run", "adsb-mcp", "serve"],
"env": { "ADSB_URL": "http://<feeder-host>:8080/data/aircraft.json" }
}
}
}Routes from OpenSky (optional)
ADS-B doesn't say where a flight is going. If you feed the OpenSky Network,
your account gets API access (an active feeder gets 8,000 credits a day). Create an API client in your
OpenSky account and set ADSB_OPENSKY_CLIENT_ID / ADSB_OPENSKY_CLIENT_SECRET. A background job then adds
origin and destination airports to recorded passes.
It's built to be a good citizen with access you earned by feeding:
It only looks up aircraft your own receiver saw.
OpenSky processes flights overnight, so it enriches yesterday's passes, after 04:00 UTC.
At most one call per airframe per day, however many times it passed.
It stops for the day at
ADSB_OPENSKY_DAILY_CREDITS, when OpenSky reports fewer thanADSB_OPENSKY_RESERVEcredits left, or when OpenSky says to slow down (HTTP 429).
What you get: past passes show origin / destination, traffic_stats can group by route, and live
aircraft carry a usual_route — where that callsign has usually flown (e.g. 4 of its last 5 passes to
SPJC), so the assistant can say "usually flies to Lima". A past pass that broke from its usual route is
flagged unusual_route (a diversion, an unscheduled landing). The board shows usual routes too.
OpenSky estimates each airport from the last position its network saw, so when coverage ends before the
runway it can name a nearby airfield instead of yours. List those in ADSB_AIRPORT_ALIASES. Each pass's
route_lookup says whether it was matched, looked up with no_match (OpenSky had no overlapping
flight), or is still pending; recorder.enrichment.last_7_days counts each.
With enrichment on, the server contacts OpenSky; the MCP tools themselves still only read the local store. Using OpenSky data is subject to OpenSky's terms.
Flight board
An airport-style arrivals board (FIDS) for an old phone or tablet on the wall, or a Home Assistant card.
With the airport set and streamable-HTTP running, open http://<server>:8000/board.
Arrivals: aircraft on approach (with ETA), ones lost on approach ("Landing"), and ones seen landing.
Departures: aircraft climbing out.
Overhead: everything within
ADSB_PASS_RADIUS_KMof the airport, nearest first; emergency squawks (7500 / 7600 / 7700) flash red.Recent: passes that ended in the last 3 hours, with their outcome (needs the recorder).
The page is one self-contained file: no build, no dependencies, no external fonts. It polls
/board.json every 8 s and renders in the browser, so many displays cost the server almost nothing
(they share one snapshot for at least 5 s). It reflows for landscape, portrait and small embeds, dropping
less important columns as space shrinks. Options: ?lang=es, ?interval=10, ?sections=arr,ovh.
Everything on the board is measured from the airport, a public place, and aircraft positions are left
out, so it carries nothing about the receiver's location. That's why it's served without the bearer by
default (a wall panel or an iframe can't send one); set ADSB_BOARD=token or off to change that.
Full screen on a phone or tablet: tap the Full screen button (Android Chrome, iPad), or use Add to Home Screen. The board ships a manifest, so it then opens without browser bars; on iPhone that's the only way. Android installs it as an app only over https, and keeping the screen awake (Wake Lock) also needs https; over plain http, use the device's stay-awake setting or a kiosk browser app.
In Home Assistant, add a Webpage card with the /board URL. If the dashboard is served over https, serve
the board over https too, or the browser blocks the frame.
Docker
docker build -t adsb-mcp .
docker run --rm -p 8000:8000 -e ADSB_MCP_TOKEN=... \
-e ADSB_URL=http://<feeder-host>:8080/data/aircraft.json \
-e ADSB_SITE_LAT=... -e ADSB_SITE_LON=... adsb-mcpIt needs to reach your feeder over the LAN. Inside the container localhost is the container itself, so
set ADSB_URL to the feeder's address. With the recorder on, mount /data on local disk (SQLite WAL
and network filesystems don't mix) and back it up: the history accumulates and can't be re-fetched.
docker run -d -p 8000:8000 -v /srv/adsb-mcp-data:/data -e ADSB_POLL_SECONDS=15 \
-e ADSB_AIRPORT_ICAO=... -e ADSB_AIRPORT_LAT=... -e ADSB_AIRPORT_LON=... -e ADSB_AIRPORT_ELEV_FT=... \
-e ADSB_URL=http://<feeder-host>:8080/data/aircraft.json -e ADSB_MCP_TOKEN=... adsb-mcpAuth and exposure
Auth is deliberately simple: stdio, or streamable-HTTP with a single bearer token (ADSB_MCP_TOKEN) on a
private network, for example behind mcpo. /healthz stays open, and
so does the flight board (/board, /board.json) unless ADSB_BOARD says otherwise. There
is no OAuth, no users and no audit log, by design. To publish it more widely, put a management layer in
front of it: an MCP gateway or auth proxy that handles OAuth, users and audit for all your servers at once.
ADSB_SITE_LAT/LON is your home location. It is never echoed in a result, and the store holds distances,
not coordinates. An exact aircraft position next to an exact distance and bearing would give the site away
in a single result, so by default aircraft_overhead gives an approximate location, like choosing
"approximate" in a phone's location permission: still good for "about 12 km to the south". Set
ADSB_EXACT_LOCATION=true to get exact values. This makes the site hard to find, not impossible: someone
who collects many results can still narrow it down. If that matters, set only the airport, so results are
measured from a public place.
AI assistance
adsb-mcp is developed openly with the help of Claude (Anthropic). Commits Claude helped write carry a
Co-Authored-By: Claude trailer. The code and design are open source so the work can be inspected, reused,
and given back.
License
MPL-2.0 © Daniel Núñez.
Available Tools
3 toolsaircraft_nearAircraft near a pointARead-onlyIdempotent
Aircraft currently within radius_km of a given point (lat/lon you supply — e.g. a city), nearest
first. Unlike aircraft_overhead this needs no receiver location: distance is measured from the point
you pass.
Args:
latitude/longitude: the centre point to search around.
radius_km: search radius in kilometres (default 50).
max_results: cap on aircraft returned (default 10).
include_grounded: include aircraft on the ground (default False).
Inputs: latitude (required), longitude (required), radius_km, max_results, +1 more.
aircraft_overheadAircraft overheadARead-onlyIdempotent
Aircraft currently in range of the receiver, nearest first. The "what's that plane overhead right now" query. With the receiver location configured, each result carries distance_km + bearing; without it, results are listed newest-position-first and distance is null (set ADSB_SITE_LAT/LON).
Args:
max_results: cap on aircraft returned (default 10).
include_grounded: include aircraft on the ground (default False — only airborne).
Inputs: max_results, include_grounded.
find_aircraftFind aircraftARead-onlyIdempotent
Find aircraft currently in range by a partial match on callsign, ICAO hex, or registration (case-insensitive). Returns matches even if they have no position yet.
Args:
query: text to match against callsign / hex / registration (e.g. "LAN", "e80", "CC-B").
max_results: cap on matches returned (default 10).
Inputs: query (required), max_results.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 3 tools
aircraft_overhead and aircraft_near are both 'aircraft in range, nearest first' spatial queries, but the descriptions clearly distinguish them (implicit receiver location vs. an explicit lat/lon you supply). find_aircraft is a clearly separate identifier-lookup path, so an agent can pick correctly.
Two tools follow an aircraft_<modifier> pattern while the third is a verb_noun form (find_aircraft), and none use a consistent verb prefix. It is still readable and unprefixed-domain-consistent, but the convention is mixed.
Three tools is on the thin side even for a narrow, read-only ADS-B query domain. It is defensible since each tool covers a distinct query mode, but the surface feels minimal.
Core live queries (overhead, radius, identifier lookup) are covered, but there is no tool for detailed single-aircraft data, historical/track positions, or receiver/feeder status. Agents can work around these gaps but cannot fully answer common questions like a plane's route or recent track.
Maintenance
Related MCP Connectors
Live and historical aircraft tracking from an open ADS-B/MLAT network, with a live map in chat
Read-only access to live ADSBiq aircraft and network state, with community contribution metadata.
Aircraft registry search, flight history, and analytics powered by ADS-B Exchange data.
Offline, keyless lookup of the US civil aircraft registry — decode N-numbers, search records.
Related MCP Servers
- AlicenseAqualityDmaintenanceExposes real-time ADS-B aircraft data from a feeder, enabling natural language queries for aircraft positions, receiver statistics, and flight searches.69GPL 3.0
- FlicenseAqualityDmaintenanceEnables querying real-time aircraft data from ADS-B Exchange using multiple query types such as callsign, registration, aircraft type, location, and more, via a FastMCP server with API key authentication.2-
- FlicenseAqualityCmaintenanceOfficial bounded, read-only access to live community-powered aircraft positions and aggregate ADSBiq network statistics.3-
- AlicenseNot gradedqualityBmaintenanceEnables live aircraft tracking by geographic area, individual aircraft lookup, flight route resolution, and airport arrivals/departures using OpenSky Network ADS-B data.319 npmMIT