velocity
Velocity
A self-hosted live map of aircraft, ships and satellites that records what it sees, so you can rewind to any past moment. No API keys, no account.
git clone https://github.com/AndrewCTF/velocity.git && cd velocity
docker compose up # then open http://localhost:8080Why it exists
Free trackers only let you look back a little: Flightradar24 gives 7 days of replay on the free tier, MarineTraffic 24 hours, and ADS-B Exchange dropped its free API. Velocity pulls the same public feeds and writes every position to your own disk, so the history is yours.
History you can replay | Self-hosted | Account / API key | Who owns the archive | |
Velocity | As much as your disk holds | Yes | None | You |
Flightradar24 | 7 days on the free tier | No | Account | Them |
MarineTraffic | 24 hours free (down from 72) | No | Account | Them |
ADS-B Exchange | Free API tier discontinued | No | Paid API | Them |
Free tiers as of July 2026; each cell is checkable on the vendor's pricing page.
Related MCP server: digitraffic-ais
What it does
Records history. Every position goes into SQLite by default, or Postgres/TimescaleDB if you set
HISTORY_PG_DSN. The default keeps a rolling window; setARCHIVE_MODE=1to keep everything. A scrubber at the bottom rewinds the map to any moment you recorded.Says where each contact came from. Every aircraft and ship shows which feeds reported it, how many agreed, and how old the position is, so a stale fix doesn't look live.
/api/status/provenancegives the same per feed.Puts more on the same map. GPS jamming, ships with AIS switched off, flight restrictions, military bases, naval warnings, earthquakes, internet outages and conflict events.
Keeps evidence. Captures get a SHA-256 hash and an append-only custody log, and a case exports to an HTML or PPTX report that cites its sources.
Talks to AI agents. It runs as an MCP server, so an agent can ask about live traffic instead of guessing. Anything automated is labelled as such.
Quick start · The apps · Take the tour · Query it from an AI agent
Limits
It's built for one person, not a team.
Coverage is whatever the public feeds see. It's good over Europe and North America and thin elsewhere; ship data is densest around Northern Europe. The map tells you where coverage is thin.
Some live state (current incidents, area selections, briefs) clears on restart. History, saved objects, cases and the evidence log stay on disk. See Scope and limits.
The 3D satellite view needs a lot of GPU memory.
Use at your own risk. No warranty; the authors take no responsibility for
how you use this tool or the data it pulls. Velocity pulls from third-party
sources that have not authorised it: their terms apply to you, the requests
come from your IP, and rate limits, bans and legal exposure are yours. Feeds can
be incomplete, delayed or spoofed, and AI summaries can be wrong. Don't use it
for navigation, safety-of-life, or anything where being wrong hurts someone.
Read DISCLAIMER.md before you run it. Security
self-assessments: ISO/IEC 27001:2022 controls,
OWASP ASVS 5.0 L2.
Prerequisites
Honestly, not much. There are two ways to run it, pick whichever you're set up for:
The easy way, with Docker. If you have Docker (24 or newer, with the
composeplugin that ships with Docker Desktop), that's the whole list. One command and you're up.Running the pieces yourself. If you'd rather not use Docker, you'll need Node 20+, pnpm 9 (
corepack enablepicks the right version for you), Python 3.12 for the backend, anduvto pull its dependencies. A plain venv works too if you don't haveuv.
Either way, you need a browser that can do WebGL2, so any recent Chrome, Edge or Firefox. The globe leans on your GPU, so on a laptop with switchable graphics do yourself a favour and push it onto the discrete card before you judge the frame rate.
And no keys. The core feeds (planes, ships, quakes, satellites, the basemap) all run without a single API key. Keys only ever add reach; see What it pulls in for what each one buys you.
Quick start
Docker is the short road. It brings up the API, the web app and nginx together:
git clone https://github.com/AndrewCTF/velocity.git
cd velocity
cp .env.example .env # optional, leave it empty and it still works
docker compose up # api + web + nginx on :8080Now open http://localhost:8080. It comes up live, planes moving, ships,
quakes, the lot, with nothing to configure in between — and position history
is already recording to a Docker volume, so it's still there the next time you
run docker compose up (set ARCHIVE_MODE=1 in .env for an open-ended
archive instead of the default rolling window; see
Scope and limits). The first time in, a short tour points
out where things live; you can pull it back up whenever from ⚙ Settings.

That dock along the bottom is the archive. Pick a window (1h, 6h, 24h, 3d, 7d),
click anywhere on the density strip, and the globe rewinds to that moment; the
header swaps from Live to Replay, and one button puts you back. Playback
runs at 1x through 3600x. The dated day-picker and the recording since … · GB
· fixes ownership chip live in the 2D console at /2d, which reads the same
/api/history/coverage archive.
The Play button drives the clock through half-hour tar1090 replay chunks with a
24-entry cache, decoding readsb's 16-byte heatmap entries in a Web Worker and
appending each contact's samples to its SampledPositionProperty. Aircraft
reach back to 2024 through the public readsb heatmap archives the backend
proxies and caches (GET /api/history/upstream/chunk|tracks|coverage); one
decoded adsb.lol chunk (2024-06-01 12:00 UTC) held 180 slices of 10 s and 3,953
aircraft tracks over Europe. Vessels have no public equivalent, so they replay
only as far back as your own archive goes.
Local (open) mode. The compute-heavy endpoints — the LLM-backed Reports tabs, recon, and OSINT enrichment — fail closed on a keyless box so a public deployment can't be made to spend GPU/credits by an anonymous caller. The dev
docker compose upsetsALLOW_UNAUTHENTICATED=1for you (the stack is loopback-only), so those features work out of the box and the UI shows an open-mode banner. ConfigureAPI_KEYor Supabase in.envand real auth takes over (the flag becomes irrelevant). For an internet-facing box usedocker-compose.prod.yml, which leaves the flag unset. The globe, replay, evidence capture, and all keyless feeds never needed the flag.
Open it on your phone
The stack listens on 127.0.0.1 only, on purpose: the dev stack runs in open
mode, so anything that can reach it can use it. To reach your own instance from
your phone, put it on your tailnet instead of your LAN:
tailscale serve --bg 8080 # https://<this-machine>.<your-tailnet>.ts.netThen add that origin to .env and restart, or the API refuses the new Host
header (the DNS-rebinding guard in app/origin_guard.py):
CORS_ORIGINS=http://localhost:8080,https://<this-machine>.<your-tailnet>.ts.netOnly devices signed in to your tailnet can open it. Don't use tailscale funnel,
which publishes it to the whole internet. For anything wider than your own
devices, run docker-compose.prod.yml with Supabase sign-in configured
(docs/security/auth-and-sessions.md); it fails closed without a credential.
make install # pnpm install + api venv
cd apps/api && .venv/bin/uvicorn app.main:app # backend on :8000
pnpm dev # vite on :5173, proxies /api to localhost:8000Set API_PROXY_TARGET if the backend isn't on http://127.0.0.1:8000; the dev
server proxies /api, /tiles and /ws there, so the browser stays same-origin.
(VITE_API_URL is different: it makes the browser call the API directly,
cross-origin, and is only for builds with no proxy in front.) API_KEY
is for servers, the CLI and the MCP server. Never build it into a hosted web
bundle, because anyone who loads the page can read it. Browsers sign in with
Supabase instead (docs/security/auth-and-sessions.md).
The apps
Velocity isn't one mega-dashboard; it's a workspace of eighteen apps that all share the same selection and time context, so switching never loses the object you had selected. The launcher in the top bar groups them:
Group | Apps | What they're for |
Live | Map · Sim | The Cesium globe (always mounted behind everything else) and a browser war-game overlay. |
AI | AI · Models | The analyst agent, automated watch and alerts, and a registry of local models (installed, catalog, presets, engine). |
Analyze | Explorer · Graph · Investigate · Targeting · Video · Country · Markets · Quiver · Vertex | Filter the live object store, grow a link-analysis graph, run digital OSINT (domains/people/wallets), a notional kill-chain board, FMV/ground recon, per-country World-Bank/UN stat dossiers, indices/commodities/FX with a stress index, time-series cards over the recorded archive, and a system graph of sources, feeds and layers. |
Data | Foundry · Workflows · Workshop | Bring your own data through governed pipelines; automate the live feeds with a node-graph editor; compose modules from live widgets. |
Product | Reports | Case files, watch-officer briefs, dossiers, and case→report export. |
3D | City 3D | Gaussian-splat city scenes built from a keyless satellite AOI. |
The rest of this README tours the ones you'll live in. Everything below is a
real screenshot of the current build (v1.0.1), taken against live feeds on
2026-08-08, not a mockup. node scripts/screenshot-readme.mjs re-shoots the
whole set.
Take the tour
1. The whole planet, live. Thousands of aircraft plus vessels, satellites, quakes and more on one Cesium globe. Every aircraft and ship renders as its category icon, coloured and rotated to its heading; dense areas cluster into count badges you can drill into.

2. Zoom anywhere, toggle everything. Drag into a region and traffic, labels and coastlines fill in. The left rail is the whole intel stack in one tree — air, maritime, space, ground hazards, signals and 18 infrastructure layers — each with its live count. A right-side map toolbar adds the analyst verbs on top of the globe: measure a running great-circle distance, area-select a box and search every object inside it, annotate with dropped markers, or move a marker — plus one-click camera reset and zoom.

3. Click anything. Select an aircraft or vessel and the right panel fills with its dossier: identity, kinematics, the flight/route, ACARS, a pattern-of-life baseline, GPS integrity and the raw fields — while a magenta line traces its recent track on the globe. An AI assessment block (labelled, and grounded in the observed track) summarises what the dossier fields imply. Click empty space and it all clears.

4. Or query the whole store at once. The Explorer app is every live object in one filterable, sortable table — tens of thousands of tracks across vessels, aircraft and quakes, each tagged with the feed it came from and how long ago it was seen. Facet it, save the search, export the result to CSV.

5. Chokepoints and AOIs, watched continuously. A curated library of the world's maritime chokepoints — Hormuz, Bab-el-Mandeb, Malacca, the Taiwan Strait, the English Channel, undersea-cable corridors — each with live transit counts and the daily oil-flow figures that make it matter. Standing detections fire against them in the background.

6. Airspace, bases, ports and airports — operational context, still keyless. Toggle the TFR/Airspace layer and live FAA temporary flight restrictions draw as real polygons, coloured by reason with their altitude bands. Alongside them: 7,183 military bases (air, naval and army, from Wikidata), NGA NAVAREA broadcast warnings with mine areas flagged, 3,804 ports from the NGA World Port Index (harbour size and type, shelter, repairs, dry dock, pier and channel depths), and airports enriched with runways (length, surface, lighting, per-end ILS category from FAA NASR), tower/ground/ATIS frequencies, live METAR with flight category, and a LiveATC linkout. A basemap picker swaps between nine keyless map styles (Carto dark, 3D satellite + terrain, Apple satellite, Esri imagery/topo/dark, OpenTopoMap, USGS, Sentinel-2 cloudless), each probed live before it's offered and each carrying its required attribution.

7. Space, with real physics. ~16k satellites from CelesTrak, propagated in-browser with actual SGP4 orbital mechanics and enriched from SATCAT with owner, launch date, radar cross-section and orbit class.

8. The watch officer works the inbox. An autonomous watch officer fuses
jamming, dark vessels, military air activity and conflict events into ranked,
cited alerts — GPS spoofing, jamming cells, dark-vessel candidates — each with an
[inferred] reasoning line and a one-click slew to. It runs detect_deception
and deep_analyze against the live picture, so the inbox fills itself on a fresh
keyless boot with no operator setup.

9. …and writes the brief. Every couple of minutes it promotes the convergences — "vessels went dark near reported activity", "spoofed tracks co-located with an AIS gap" — into ranked incident cards with a written, cited summary and a slew to button. Actionable incidents are auto-promoted into the investigation graph, so the ontology grows itself.

10. Keep it as evidence. Anything worth keeping goes into the
chain-of-custody evidence locker: capture a URL snapshot, upload a file, grab
a screenshot, or freeze the current feed state, and each artifact is hashed with
SHA-256, written to ./data/evidence, and logged to an append-only custody
chain in the local ontology store. Attach it to a situation, re-verify the hash
any time, or roll a whole case into a signed hash-of-hashes manifest. Fully
keyless. From there, Reports exports the case (see Export) as a
JSON bundle, a self-contained HTML report with per-claim provenance footnotes,
or a PPTX brief — with any AI narrative confined to a clearly labelled block.
11. A tool that draws on the map gets its own window. The annotation editor, the watchbox rules and the field kit open as free-floating, draggable, resizable windows over the globe rather than stealing the rail from the layer tree — arm a tool from the map toolbar, set up what it draws beside the thing you are drawing on, and Window → Re-dock every floating panel clears them all. Positions persist for the session.

12. Bring your own data — the Foundry tab. Upload a CSV/JSON/NDJSON, shape it through a governed pipeline (13 transform steps: filter, derive, join, aggregate, union, window, pivot, dedup, cast, sort…), gate every version with data-health checks (freshness SLAs, schema-drift, uniqueness…), and bind it into the same ontology graph as the live feeds. Lineage, immutable versions with rollback, and a dead-letter for rows that fail a transform all come along; the pipeline graph flags exactly which outputs went stale.

13. Automate it — the Workflows tab. A node-graph editor over the same live feeds and ontology: wire sources (aircraft, vessels, quakes, alerts, datasets, ontology, countries) through sandboxed Python/SQL/LLM transform blocks into sinks (alerts, ontology objects, datasets, persistent memory), then run it on a schedule. Control blocks can reach outward too — webhooks to your own server, or a MAVLink bridge for drone tasking that rehearses log-only without a vehicle.

Scope and limits
The point is fusion. Plenty of sites already do one feed well: Flightradar24 for planes, MarineTraffic for ships, GPSJam for jamming. Velocity goes after the seam between them: AIS plus radar imagery flags a ship that's switched its transponder off; a cluster of aircraft reporting bad GPS integrity becomes a jamming hotspot; when two or more of those line up in the same place and time, they get promoted to a single incident with a written, cited summary.
A few things worth knowing up front, because I'd rather you read them here than be annoyed later:
It's built for one analyst. One optional API key, no accounts or roles.
What persists vs what clears. Durable under
./data/or the Timescale service: the position-history archive (a Postgres + TimescaleDB hypertable by default; SQLite at./data/history.dbremains the keyless fallback when noHISTORY_PG_DSNis set, with one boot warning naming the switch), the local ontology store (ontology objects, case files, situations;intel/ontology_local.py,./data/ontology.db), plus separate SQLite files for Foundry, Workflows, alert rules, cached news and the Instability Index scorer (foundry.db,workflows.db,alert_rules.db,news_history.db,instability.db), and a keyless-local audit log (audit_log.db— only materializes when noSUPABASE_URLis configured; a keyed deployment writes audit rows to Supabase instead and never creates the file). That's 8 SQLite files, not one — back up the whole./datavolume, not a single DB. Also durable: the evidence locker (blobs under./data/evidence, custody chain in the ontology store's append-onlyassertionstable), a disk tile cache (./data/tilecache) and any downloaded local models (./data/models). Volatile, and cleared on backend restart: the live incident list, transient AOI selections, and generated watch-officer briefs. The Docker Compose volumes mean the durable stores survivedocker compose down, not just a process restart.Retention, honestly. The dev compose sets
HISTORY_RETENTION_HOURS=48(2 days), but that's a ceiling, not a promise:HISTORY_MAX_BYTES(2 GB by default) binds first, and at measured global ADS-B+AIS ingest (~3 GB/h on a keyless box pulling full-breadth feeds) the byte cap fills well before the hour window matters, leaving well under 2 hours of effective depth out of the box — not 48. The hour window only becomes the binding limit once disk is large enough for it to be the tighter cap; a genuine 48h global archive needs on the order of 150 GB (~3 GB/h * 48h). RaiseHISTORY_MAX_BYTES, or setARCHIVE_MODE=1withHISTORY_DISK_BUDGET_GBsized to what you're willing to spend on disk (the production compose profile does this, 5 GB to start)./api/history/coveragereports the ACTUAL current depth (oldest_ts), not the configured ceiling, and the 2D console's day-picker and ownership chip render it, so you can see what you're really getting. Depth answers now come from the hourlypositions_hourlyaggregate; the old SQLite coverage scan took 73 s over 78M fixes, which is why the rollup exists.AIS runs keyless and global (~50k vessels tracked, MMSI-deduped across ShipXplorer, MyShipTracking, Digitraffic and Kystverket), but coverage is densest over Northern Europe and the Baltic and thins out elsewhere; an AISStream key fills in the gaps. Sparse regions still lean on the radar (SAR) layer.
The two headless-Chrome sidecars are degraded under Docker, not crashed. ADS-B (OpenSky + the httpx tar1090 mirrors + ShipXplorer AIS) and the regional Kystverket/Digitraffic AIS feeds are direct HTTP calls and run at full strength in the container. But the tar1090 ADS-B sidecar and MyShipTracking (the enabled primary keyless wide-area AIS source) drive a real Chromium via Node, which the slim API image doesn't ship — they log a warning and sit idle rather than crash the backend, so you lose their extra coverage, not the app. Run bare-metal (
bash scripts/run-api.sh) for full sidecar coverage.Some Analyze apps are notional by design — the Targeting kill-chain board and the Video/FMV sensor HUD carry a "NOTIONAL // SIMULATED" banner and are driven by the Sim overlay, not a live weapons or ISR feed.
The 3D satellite view will eat your VRAM. The default 2D dark map runs on a laptop; check System requirements before switching the heavy mode on.
None of it needs an API key to start. Keys only add reach.
What is enforced
Not promises, checks in the tree. Each line names the route or test that holds it:
Clearance on reads and writes. One predicate,
intel/ontology.visible_to, filters local reads (get,list_by_kind,search, links, assertions) and the situations, maps and evidence routes by the caller's level and compartments; keyless callers are clearance 0, and a write above clearance or onto a hidden row answers 403. →apps/api/tests/test_clearance_local_reads.pyCustody-chained evidence. Every capture is hashed with SHA-256 and appended to an immutable custody log;
GET /api/evidence/{sha}/verifyre-checks the bytes. →apps/api/tests/test_evidence.pyHash-chained audit. Mutations append to
action_log.db;GET /api/audit/verifywalks the chain. →apps/api/tests/test_asvs_fix_a_logging.pyCitations withheld when ungrounded.
LLM_REQUIRE_CITATIONS=1(default) withholds a selection or country brief that cites none of its own ids;GET /api/ai/guardrailsreports the posture. →apps/api/tests/test_citations_hard.pyHuman-in-the-loop write-back. A write-back action is operator-only and runs only from a proposal the operator approves; the audit row carries the payload's SHA-256, never the payload. →
apps/api/tests/test_writeback_action.py
What it pulls in
Live numbers measured off a running keyless backend on 2026-08-08; they move around through the day:
Feed | Typical live count | Where it comes from |
Aircraft (ADS-B) | 12–15k (14,436 measured) | OpenSky + airplanes.live + adsb.lol + FR24 |
Military aircraft | ~30–60 | adsb.lol + airplanes.live |
Vessels (AIS) | ~50k tracked, global | ShipXplorer + MyShipTracking + Digitraffic + Kystverket |
GPS jamming | ~220 flagged 1° cells | ADS-B NACp/NIC, the GPSJam method |
Dark vessels | radar change-detection | Sentinel-1 SAR |
Fused incidents | correlation-driven | the correlation engine |
Satellites | 16,095, SATCAT-enriched | CelesTrak |
Airspace (TFRs) | live restriction polygons | FAA |
Military bases | 7,183 (+1,330 MIRTA/Wikidata) | Wikidata + DIA MIRTA |
Naval broadcast warnings | ~800 plotted points | NGA NAVAREA |
Ports | 3,804, with harbour detail | NGA World Port Index |
Airports | runways + ILS + frequencies + live METAR | FAA NASR, NOAA, LiveATC |
Earthquakes | ~290/day | USGS + EMSC |
News + fact-check | ~370 articles (off by default) | publisher RSS |
Internet outages | country level | IODA, Cloudflare |
Submarine cables | ~700 | TeleGeography |
Conflict events | ~1.5k live | GDELT, EONET, ACLED |
Wildfires | VIIRS hotspots | NASA FIRMS (needs a key) |
3D war damage, imagery, webcams | varies | Sentinel, GIBS, OSM |
Per-country toolkits | 53 countries | open-data / registries / sanctions links |
Optional keys, if you want more reach: AISStream for global AIS, an OpenSky
login for a bigger ADS-B budget, FIRMS_MAP_KEY for fires, an ACLED key for
conflict events, CLOUDFLARE_TOKEN for outages. GET /api/intel/sources
reports what's actually live versus what's still waiting on a key.
Use your own receiver
If you run an ADS-B receiver (readsb, ultrafeeder, tar1090), Velocity folds its
aircraft into the same picture as the public feeds and records them to your
archive like everything else. Point ADSB_FEED_URLS in .env at your
tar1090's aircraft.json. The variable replaces the default list, so keep the
defaults in it if you still want them:
ADSB_FEED_URLS=http://192.168.1.20:8080/data/aircraft.json,https://globe.theairtraffic.com/data/aircraft.json,https://skylink.hpradar.com/data/aircraft.json,https://api.adsb.lol/v2/point/0/0/20000Contacts are merged by ICAO address, freshest fix wins. Inside Docker,
localhost is the API container, so use the receiver's LAN address.
For a small desk display of what is overhead (ESP32, e-ink), GET /api/overhead
returns the nearest aircraft as compact JSON from the same snapshot, with no
upstream call of its own: see docs/desk-display.md.
MCP server: query the live console from an AI agent
The part I think is genuinely new: the backend doubles as a Model Context
Protocol server, so an AI agent can interrogate the same warm feeds the globe
renders without scraping a dozen sites or flooding its own context. Ask "where
is GPS being jammed right now?" and it answers from the live feed. Full
architecture + /api/intel/* HTTP reference: docs/mcp-server.md.
It exposes 94 tools over app.mcp_server (a representative slice below; run
--list-tools for the full set):
Tool | What it returns |
| Global summary: aircraft by category, GNSS-degraded count, emergencies, worst jamming cells, vessel/alert counts. The cheap first call. |
| Loads a region PRIMARY (dedicated fresh |
| Grid of cells (count, by category, GNSS-degraded) for an area. |
| GPSJam-method assessment (ADS-B NACp<8 / NIC<7, 1° bins): flagged cells, severity, affected aircraft. Global or scoped. |
| Filtered query (bbox/centre, category, squawk, callsign, altitude band, emergency / gnss_degraded / on_ground). |
| One aircraft by ICAO24 or callsign + integrity/threat assessment. |
| AIS vessels in an area, classified; |
| Fused report: emergencies, jamming hotspots, dark vessels, alerts + a triage threat level. |
| Flag spoofed/co-located tracks; triangulate a jamming source from degraded-integrity aircraft. |
| Delta since a timestamp; the incident timeline for an area. |
| Gathers the relevant intel JSON and has a reasoning model reason over it (DeepSeek when configured, else a local Ollama model), so heavy analysis stays off the agent's context and only the conclusion returns. |
Every tool returns compact, bounded JSON (counts, grids, ≤50-item samples), so
an agent can sweep the planet for a few hundred tokens instead of pulling 15k
features. Heavy tools also take detail='short' (the default digest — top-N
of each list plus a <field>_total) or detail='long' (the full bundle), so
an agent sweeps in short and drills in long. Area-primary loading means the
agent's region of interest stays fresh and dense even while the global firehose is
being rate-limited; the rest of the world keeps streaming from the sticky snapshot.
The same fusion powers the in-app AI selection brief: click an entity and
POST /api/ai/selection/brief fuses its registry enrichment and pattern-of-life
dossier into a selection-tier model prompt — every claim cites the dossier field
it came from, an uncited answer is withheld rather than shown, and it degrades
to the raw props if no model is configured.
Install as a Claude Code plugin (skill + commands + agent)
The repo is also a Claude Code plugin marketplace. One install wires the MCP
server plus an analyst skill (osint-intel), slash commands (/osint-brief,
/osint-watch, /osint-jamming), and a osint-watch-officer agent. Start the
backend (bash scripts/run-api.sh), then in Claude Code:
/plugin marketplace add /path/to/OSINT
/plugin install osint-geoint@osint-velocitySet repo_dir and python (the repo's venv interpreter) when prompted; the
plugin runs that Python directly, so it works on Windows, macOS, and Linux. The
installer prints the exact commands for your OS: bash plugin/osint-geoint/install.sh (Linux/macOS, -y to register the MCP server) or
plugin\osint-geoint\install.ps1 (Windows, -Run to register).
Point your agent at your own instance
The MCP server is mounted into the backend at /mcp (streamable-HTTP), so
once you're running Velocity yourself there's nothing extra to install to
query it from an agent. Register it with any MCP client against your own
host:
claude mcp add --transport http osint-geoint \
http://localhost:8000/mcp \
--header "Authorization: Bearer $VELOCITY_TOKEN"$VELOCITY_TOKEN is only required if you've set API_KEY on the backend; a
keyless local instance needs no header at all.
Self-host / develop
# 1. backend must be running (provides the warm feeds)
uv run --project apps/api uvicorn app.main:app --port 8000
# 2a. MCP server over stdio (Claude Code / Desktop / Agent SDK), cross-platform
uv run --project apps/api python -m app.mcp_server
# 2b. or streamable-HTTP
uv run --project apps/api python -m app.mcp_server --http --port 8765
# introspect (no backend needed)
uv run --project apps/api python -m app.mcp_server --list-toolsTo register the local stdio server with Claude Code, run from the repo root:
claude mcp add osint-geoint -- uv run --project apps/api python -m app.mcp_serveruv run resolves the right interpreter on Linux, macOS, and Windows without
hardcoding a venv path. No uv? Point it at the venv Python directly:
apps/api/.venv/bin/python -m app.mcp_server (Linux/macOS) or
apps\api\.venv\Scripts\python.exe -m app.mcp_server (Windows), run from
apps/api.
Config (env or apps/api/.env): API_BASE, API_KEY, OLLAMA_HOST,
OLLAMA_MODEL (empty picks the smallest installed model; deep_analyze
degrades to returning raw JSON if Ollama is absent). The MCP server never
crashes a tool call: backend down returns a structured backend_unreachable
error; Ollama down falls back to raw intel JSON.
Export
Two ways out, for two jobs:
The live picture.
GET /api/export?fmt=geojson|csv|kml&kinds=aircraft,vessels&bbox=min_lon,min_lat,max_lon,max_lat&limit=N
downloads the current snapshot the globe renders as GeoJSON (QGIS /
kepler.gl / Leaflet), CSV (spreadsheets), or KML (Google Earth). bbox
clips to a viewport; kinds is comma-separated (default aircraft); limit
caps features; vessels are best-effort.
A finished case.
POST /api/situations/{id}/export?fmt=json|html|pptx walks a situation's
children, its sourced assertions and every attached piece of evidence into a
shareable artifact: a self-describing JSON bundle, a self-contained HTML
report with a provenance footnote on each claim, or a PPTX brief. Any AI-written
narrative is confined to one labelled block — the rest is your cited evidence.
System requirements
The heavy component is the client, a CesiumJS WebGL2 globe. It is GPU- and
browser-main-thread-bound, and the backend is light. WebGL2 is required
(Chrome/Edge 110+, Firefox 110+). On hybrid-graphics laptops, force the discrete
GPU (chrome://gpu, look for adapter "ACTIVE").
VRAM depends heavily on which mode you run:
2D-dark (default basemap): light. The globe is a proxied 2D raster basemap plus the entity layers (aircraft/vessels). Runs on integrated graphics / ~2–4 GB VRAM, which is the right mode for modest hardware.
3D-sat (satellite imagery + world terrain + OSM 3D buildings, optional Google Photorealistic 3D): VRAM-heavy. CesiumJS streams terrain meshes, high-res imagery, and 3D-tile building/photogrammetry sets, and it caches into whatever VRAM is available, measured at 20+ GB on a 32 GB card. Tilesets are now individually cache-capped (Google 3D ~1.5 GB, OSM buildings ~0.5 GB) and MSAA is off (FXAA instead), but with terrain + global imagery + a high-DPI/4K canvas the resident set is still large. On a card with less VRAM, Cesium evicts and re-fetches more aggressively (lower fps, more pop-in) and still runs.
Tier | GPU | RAM | Display | What you get |
Edge / lite | WebGL2 integrated (Iris Xe / Vega / M1) | 8 GB | 1080p |
|
Recommended | Discrete ≥8 GB VRAM (RTX 3060 / RX 6700 / M-Pro) | 16 GB | 1080p–1440p | Full profile with all feeds. 2D-dark smooth; 3D-sat usable at city scale. |
Full / 3D-sat | RTX 4070+/16 GB VRAM or better | 32 GB | up to 4K | All feeds + browser sidecars + 3D-sat terrain + buildings; best frame rate. |
These tiers come from a cgroup-isolated benchmark (tools/perf/bench_ram.sh)
that constrains backend + Vite + Chrome inside a single MemoryMax and measures
real fps, renderMs, entity counts, and OOM kills at each RAM level. 3D-sat
genuinely wants a lot of VRAM, and the low-VRAM minimum only holds for the
2D-dark map; switch on 3D-sat and you'll want a discrete card with headroom.
Backend (server): Python 3.12, outbound HTTPS. The lite profile
(default) disables browser sidecars, history, and the FR24/OpenSky-gaps tiers,
measuring ~530 MB steady-state RSS — comfortably fits an 8 GB box alongside
the browser. The full profile with all direct feeds warm measures ~3.2 GB.
The optional headless-Chrome coverage sidecars are the real memory line item:
the ADS-B scraping tier measured ~11 GB RSS across its ~50-process Chrome tree
and the AIS tier ~3-6 GB across its ~20-36 (both measured 2026-07-24 at full
coverage), so budget roughly 15 GB on top of the backend when both are
enabled — or keep lite and use the direct feeds.
Stack
Frontend: Vite + React 18 + TypeScript + CesiumJS + MapLibre GL JS v5.24 + Tailwind + Zustand
Backend: FastAPI (Python 3.12) + httpx + websockets. Live derived state is in-process (bounded observation store + disk tile cache); durable state (the position-track replay archive, the ontology store: objects / case files / situations, and the evidence locker's custody chain) persists to Postgres + TimescaleDB or SQLite, on Docker volumes in Compose, with a rolling window by default or an open-ended disk-budgeted archive via
ARCHIVE_MODEAgent access: Model Context Protocol server (
app.mcp_server, MCP SDK) + optional local Ollama analysisData: PostgreSQL 16 + TimescaleDB backs the position archive today (6 hour chunks, columnar compression, hourly
positions_hourlyrollup, SQLite as the keyless fallback). PostGIS + Redis remain planned.Infra: Docker Compose, nginx reverse proxy
Layout
osint/
├── apps/web/ # React + Cesium console (14-app workspace)
│ └── src/
│ ├── globe/ # Cesium globe, layers, map toolbar (measure/area/annotate)
│ ├── entity-panel/ # right-rail dossier / inspector
│ ├── evidence/ # chain-of-custody evidence locker UI
│ ├── foundry/ # FOUNDRY surface (datasets, pipeline DAG, builds, ontology)
│ └── shell/ # app switcher + detachable floating panels
├── apps/api/ # FastAPI backend
│ └── app/
│ ├── intel/ # agent-facing analytics, local ontology store, evidence, case export
│ ├── foundry/ # BYO-data layer: datasets, transforms, builds, checks, binding
│ ├── routes/intel.py # /api/intel/* deep-query JSON API
│ ├── routes/evidence.py# /api/evidence/* capture / verify / attach / manifest
│ ├── routes/foundry.py # /api/foundry/* datasets/pipelines/checks/bindings
│ └── mcp_server.py # Model Context Protocol server
├── packages/shared/ # Shared TS types (LayerDescriptor, Observation)
├── docs/ # design notes, decisions.md, foundry-plan.md
└── infra/ # Docker, nginx, db initTests
# from the repo ROOT (running from apps/api makes .env auth resolve → a wall of 401s)
OSINT_DISABLE_BACKGROUND=1 apps/api/.venv/bin/pytest apps/api -q # 3276 passed + 21 skipped
pnpm -r test # vitest (web, shared)
pnpm -r typecheck
bash scripts/verify.sh # typecheck + lint + web unit + api tests in one shot
# manual MCP integration drivers (need backend on :8000):
# apps/api/.venv/bin/python tests/mcp_client_check.py # stdio handshake
# apps/api/.venv/bin/python tests/mcp_full_check.py # tools end-to-end + OllamaPhase status
This is the internal build log, in order shipped — not the pitch (that's above). Legend: ✅ shipped · 🚧 in progress
✅ Phase 0 — Foundation
✅ Phase 1 — MVP: live ADS-B / AIS / quakes / GPS-jamming layers on the globe
✅ Phase 2: Replay + drill-in. The timeline scrubber and the history archive ship with a Postgres + TimescaleDB backend (48h rolling window by default, or an open-ended disk-budgeted archive with
ARCHIVE_MODE), SQLite as the keyless fallback, and a tar1090-style half-hour chunk replay pipeline that carries aircraft back to 2024 from public readsb heatmap archives; drilling into any past moment works today. PostGIS + Redis for the rest of the platform's state are a deferred scaling upgrade, not a blocker✅ Phase 3 — Fusion engine + alerts (correlation rules) + 2D mirror
🚧 Phase 4 — Advanced sensors + agent access. MCP server + intel API (now with a Claude Code plugin and
detail=short|longtool variants), Sentinel-1 SAR dark-vessel detection, an autonomous watch-officer that writes cited incident briefs and auto-populates the ontology graph from them, a keyless infra/domain OSINT layer, a places/airspace layer (FAA TFR polygons, military bases, NGA naval warnings, World Port Index port detail, NASR/METAR airport enrichment, an 8-way keyless basemap picker), a chain-of-custody evidence locker and case→report export (JSON/HTML/PPTX with per-claim provenance), a globe map toolbar (measure/area-select/annotate) and detachable floating panels, a photo-geolocation pipeline, a City 3D Gaussian-splat viewer, optional local-GPU (Ollama) inference, and a first-run onboarding tour. More sensors and deeper analysis are ongoing.🚧 Phase 5 — Foundry: a keyless, local, single-operator data-integration loop. Upload → transform (governed 13-step DSL with lineage) → build (dependency DAG, staleness, cycle rejection) → data-health checks → bind into the local ontology graph. In: immutable versions + rollback, row-level quarantine/dead-letter, freshness/schema-drift SLAs, window/pivot analytics, entity resolution. Deliberately out of scope (single-operator identity): multi-tenant MLS, distributed compute, streaming CDC, connector catalogs. Next: ontology Actions (audited write-back) and dataset branches.
🚧 Phase 6 — Workflows: a node-graph automation layer over the same live feeds and ontology. 20 blocks — sources (aircraft, vessels, quakes, alerts, datasets, ontology, countries), transforms (
op.python/op.sql/op.llmsandboxed subprocesses,op.geo,op.http,op.steps), sinks (alert, ontology, dataset, persistent memory), and external-actuation control blocks that reach out of the platform:control.webhookto any server, andcontrol.drone/control.deviceto command a UAV or hardware via a JSON envelope. A first-class MAVLink bridge (app.mavlink_bridge, ArduPilot/PX4, log-only without a vehicle so you can rehearse) ships in-repo. Seedocs/workflows-control-blocks.md.
See docs/ for the per-feature design notes and pipeline writeups.
License
AGPL-3.0-or-later covers Velocity's source code. Upstream data
carries its own licenses; several feeds are non-commercial / academic (e.g.
ACLED, adsb.fi, OpenSky). See NOTICE for per-source attribution and
terms, and verify each upstream's current terms before any commercial or
redistributive use.
No warranty, and no liability for how you collect or use the data — see
DISCLAIMER.md, which also covers scraping, upstream terms,
personal data, and prohibited uses.
Available Tools
94 toolsadsb_by_callsignC
Live aircraft by callsign (e.g. 'UAL123').
Inputs: cs (required). Returns: object
adsb_by_hexB
Live aircraft by ICAO24 hex code (e.g. 'a1b2c3').
Inputs: icao (required). Returns: object
adsb_by_registrationB
Live aircraft by tail/registration number (e.g. 'N12345').
Inputs: reg (required). Returns: object
adsb_by_typeB
Live aircraft by ICAO type designator (e.g. 'B738', 'A320').
Inputs: type_code (required). Returns: object
adsb_laddA
Aircraft on FAA's Limiting Aircraft Data Displayed (LADD) list.
Inputs: no named parameters. Returns: object
adsb_piaC
Aircraft using Privacy ICAO Address (PIA) — randomized hex codes.
Inputs: no named parameters. Returns: object
aircraft_densityA
Aircraft density over an area as a grid of cells (count, by category, GNSS-degraded per cell) plus totals, peak cell, and in-area vessel count.
Give either a centre (lat, lon [, radius_nm]) or an explicit bbox (min_lon, min_lat, max_lon, max_lat). cell_deg sets grid resolution.
Inputs: lat, lon, detail, max_lat, +5 more. Returns: object
aircraft_dossierB
Pattern-of-life dossier for one aircraft (ICAO24 hex or callsign): recent track, gaps, derived speed profile, GNSS-integrity, emergency/military flags, and which live incidents it appears in.
Inputs: ident (required), detail. Returns: object
air_qualityB
Air quality (US AQI + PM2.5/PM10) sampled across major world cities — a proxy for industrial activity, smoke drift, and chemical release.
Inputs: detail. Returns: object
anomaliesB
Fused anomaly report for an area (or global if no coords): emergency aircraft, GPS-jamming hotspots, dark-vessel candidates, recent fusion alerts, plus a triage threat_level (low|elevated|high) and score.
Inputs: lat, lon, detail, max_lat, +4 more. Returns: object
answerA
Named questions answered with a verdict, its rule, and the evidence age.
The one call that returns a CONCLUSION rather than data to interpret. Each
answer carries verdict (open / reduced / closed / unknown), threshold
(the rule that produced it, in words), data_lag_s (how old the newest
evidence is), and stale.
Two contracts worth relaying to a user verbatim rather than paraphrasing:
unknown is a real answer and its detail says why we cannot answer yet,
and data_lag_s of null means NO EVIDENCE WAS OBSERVED - never "fresh".
Report stale alongside any verdict you quote.
Omit question_id to list every registered question.
Inputs: question_id. Returns: object
aoi_imageryA
Building imagery for a location at two dates — set time + place directly.
Give a location (lat/lon + radius_km, OR an explicit min/max bbox) and a
before + after date (YYYY-MM-DD). Returns what imagery is available for each
date WITHOUT downloading: Maxar Open Data VHR (~0.3-0.5 m, event-gated — only
where a disaster/conflict event covers the AOI) and Sentinel (10 m, global,
any date). best_source says which to use. Reconstruction downloads the
scenes to a temp dir on demand and discards them — nothing is stored.
Args: before, after: dates as YYYY-MM-DD. lat, lon, radius_km: centre + radius (km) of the AOI. min_lon/min_lat/max_lon/max_lat: explicit bbox (overrides lat/lon). window_days: ± days around each date to search Maxar (events are sparse).
Inputs: after (required), before (required), lat, lon, +6 more. Returns: object
approve_proposalA
Approve and EXECUTE a queued proposal (POST /api/actions/proposals/{id}/approve).
Only call this when the operator has told you to. The receipt carries the audit row the execution wrote. 404 for an unknown or expired proposal (the queue has a 15-minute TTL); 403 when the action is operator-only and the caller is not the operator; 401 on a multi-user deployment by design — see propose_action.
Inputs: proposal_id (required). Returns: object
area_baselineB
Is this normal? Current vessel / dark-vessel / jamming / military counts z-scored against a rolling baseline, with anomalies called out (e.g. "dark vessels +5σ", "traffic -3σ"). Global uses the background sampler; polling an AOI repeatedly builds that area's baseline. Distinguishes a real shift from a normal day.
Inputs: lat, lon, detail, radius_nm. Returns: object
aviation_sigmetC
AIRMET/SIGMET hazard polygons (turbulence, icing, IFR, volcanic ash, convective) constraining the aircraft layer.
Inputs: detail. Returns: object
cisa_kevC
CISA Known Exploited Vulnerabilities catalog — what's actively being exploited in the wild right now.
Inputs: detail. Returns: object
climate_anomaliesC
Temperature/precipitation anomalies (vs. a 5-year same-window baseline) over conflict-dense country centroids.
Inputs: detail. Returns: object
contact_provenanceA
Which sources are seeing the sky right now, and how much they agree.
Per tier: how many contacts it reported and how many ONLY it reported, plus the share of contacts with two or more independent observers and the confidence rule in force.
The exclusive count is the column that matters. Crowd-sourced ADS-B accepts whatever is uploaded to it, so a contact only one tier can see is not corroborated, and a tier contributing thousands of exclusive contacts is either unique coverage or an unverifiable claim. Say which when you report it, and never present an uncorroborated contact as confirmed.
Inputs: no named parameters. Returns: object
courtlistener_searchC
Search US court opinions on CourtListener.
Inputs: q (required). Returns: object
create_watch_ruleA
Create a standing watch rule (POST /api/alerts/rules).
A rule needs a gate: either an identity pin (icao24/mmsi/callsign — follows that one entity globally, no AOI required) or a complete AOI (lat, lon; radius_nm defaults to 50 nm if omitted). kinds filters which signal kinds fire it — jamming/dark_vessel/military_air/military_vessel/incident/quake/ fire/ais_gap/rendezvous/loiter (omit for all). channel is 'inapp'|'discord'|'webhook' ('discord'/'webhook' require sink_url). The route validates and 400/422s on a bad gate, channel, sink_url, or kind — this wrapper does not re-validate, it passes the error through.
Inputs: label (required), lat, lon, mmsi, +8 more. Returns: object
data_sourcesA
Which feeds are always-on vs key-gated, and the configured Ollama host/model. Use to explain coverage gaps (e.g. fires need a FIRMS key).
Inputs: no named parameters. Returns: object
deep_analyzeA
Have a reasoning model reason deeply over the live intel.
Gathers the relevant data (global situation, plus a focused area bundle +
jamming + anomalies when lat/lon given) and feeds it to a real reasoning
model — DeepSeek (deepseek-reasoner) when configured, else a local
Ollama model. Heavy reasoning runs off-context; only the conclusion enters
the agent's context. Falls back to returning the raw structured data
(analysis=null) if no LLM backend is reachable.
Args:
question: what to analyse (e.g. "Is there coordinated GPS jamming near
the Baltic, and which aircraft are affected?").
lat, lon, radius_nm: optional area to focus on (loaded PRIMARY).
model: optional Ollama model override used only on the Ollama fallback.
tier: reason (deepseek-reasoner, default — judgement) or fast
(deepseek-chat — quicker, shallower).
Inputs: question (required), lat, lon, tier, +2 more. Returns: object
deepstate_firmsC
FIRMS fire hotspots within the Ukrainian theater (DeepState).
Inputs: detail. Returns: object
deepstate_radiation_stationsC
Live radiation monitoring stations in Ukraine (DeepState, ~565 stations).
Inputs: detail. Returns: object
deepstate_war_newsC
Geolocated war-news events from the DeepState map.
Inputs: detail. Returns: object
delete_watch_ruleB
Delete a standing watch rule by id (DELETE /api/alerts/rules/{rule_id}).
Inputs: rule_id (required). Returns: object
detect_deceptionA
Denial & deception — "am I being fed?". Flags MANIPULATED tracks distinct from jamming: AIS duplicate-MMSI (one identity, two hulls) and impossible teleports; ADS-B GPS spoofing (many aircraft snapped to one false position) and kinematic position-injection. Run before trusting a feed in a contested area. Omit coords for global.
Inputs: lat, lon, detail, radius_nm. Returns: object
disaster_alertsB
GDACS severity-scored global disasters (earthquake/cyclone/flood/volcano/ drought/wildfire), each Green/Orange/Red by modelled impact.
Inputs: detail. Returns: object
displacementB
UN OCHA HAPI country-level IDP and refugee figures, latest reporting period per country.
Inputs: detail. Returns: object
esri_waybackC
ESRI Wayback time-travel manifest — every historical imagery version since 2014.
Inputs: no named parameters. Returns: object
fact_checkA
Adjudicate one free-text claim against current world-news headlines.
Returns {verdict: true|false|misleading|unverified, reasoning, supporting_sources, confidence}. Use it to sanity-check a statement before
treating it as fact.
Inputs: claim (required). Returns: object
fema_disastersC
Recent FEMA disaster declarations across US states and territories.
Inputs: detail. Returns: object
fire_perimetersA
Active wildfire burn-area polygons (NIFC/WFIGS) — the AREA behind FIRMS hotspot points, with incident name + size in acres.
Inputs: detail. Returns: object
focus_areaA
Load a region PRIMARY and return a full intel bundle for it in one call.
This is the headline tool. It triggers a DEDICATED, always-fresh fetch for just this area (bypassing global rate limits) and registers it for ongoing priority refresh. The bundle contains: fresh aircraft summary + sample, aircraft-density grid, GPS-jamming assessment, vessels, and fused anomalies (emergencies, jamming hotspots, dark vessels, alerts) with a threat level.
Args: lat, lon: centre of the area of interest. Required unless polygon is given, in which case they are ignored. radius_nm: radius in nautical miles (1–250; default 200). label: optional human name for the AOI (e.g. "Kaliningrad"). cell_deg: density grid cell size in degrees (0.1–10; default 1.0). polygon: optional irregular region as ONE ring of [lon, lat] pairs (longitude FIRST; 3–500 vertices, closed or open; no holes, no antimeridian crossing). Overrides lat/lon/radius_nm: its bounding circle (max 250 nm, else polygon_truncated) is loaded PRIMARY and every record outside the ring is dropped. Grid cells and jamming hexes are kept by centre point, so use a small cell_deg for a small ring. A total the capped samples cannot give for the ring comes back as _in_circle (the bounding circle's figure).
Inputs: lat, lon, label, detail, +3 more. Returns: object
fr24_searchC
FlightRadar24 unauthed search — aircraft, airports, flights.
Inputs: q (required). Returns: object
gdelt_articlesC
GDELT DOC 2 API — article-level search across global news.
Inputs: q. Returns: object
get_situationA
Global situational snapshot — the cheap first call to orient.
Returns total aircraft (airborne/ground, by category), GNSS-degraded count, active emergency squawks, the worst GPS-jamming cells worldwide, tracked vessel counts by category, and recent fusion-alert counts. A few hundred tokens describing the whole planet.
detail='short' (default) trims the jamming/vessel/alert samples to their top few; detail='long' returns the full situation bundle.
Inputs: detail. Returns: object
gleif_lookupC
Look up a company's Legal Entity Identifier (LEI) via the GLEIF API.
Inputs: q (required). Returns: object
gps_jammingA
GPS/GNSS jamming assessment (GPSJam method: ADS-B NACp<8 / NIC<7 binned into 1° cells). Returns flagged cells ranked by severity, counts of high/medium cells, and a sample of affected aircraft. Omit all coordinates for a global view; otherwise pass a centre or a bbox to scope it.
Inputs: lat, lon, detail, max_lat, +4 more. Returns: object
hdx_datasetsC
Search humanitarian datasets on HDX (data.humdata.org).
Inputs: q. Returns: object
history_diffA
What changed inside a box between two moments: arrived, departed, stayed.
Answers from OWNED history, so it works for any past window still retained
rather than only the present instant. at_a and at_b are epoch seconds
(at_b defaults to now); each is widened by window_sec because a stored
position is a sample rather than a continuous truth.
recorded: false means nothing was in the archive for that box and window -
which is NOT the same as "nothing changed". Do not report an empty archive
as a quiet period.
Inputs: at_a (required), lamax (required), lamin (required), lomax (required), +4 more. Returns: object
incident_historyA
Timeline of how each incident built up over the recent window — per
incident, a compact series of [time, threat_level, score] points.
Reveals sequence (e.g. jamming first, then dark vessels, then a reported
event). Global uses the background watch history; an AOI uses your prior
watch calls.
The full window can hold many incidents; this returns the limit most
active ones (default 25, alias max_incidents) with each series
downsampled to its threat-level transitions, and reports
incident_count / returned / truncated so you know if more exist.
Raise limit or narrow with lat/lon/hours to drill in.
Inputs: lat, lon, hours, limit, +3 more. Returns: object
intel_briefA
Cross-domain INCIDENT brief — the headline analytic tool.
Fuses the raw layers into ranked, cited INCIDENTS instead of returning
signals you have to correlate yourself. An incident is a CONVERGENCE: signals
within link_km across >=2 domains (GPS-jamming, dark/AIS-off vessels,
military air, AIS gaps, emergencies, geocoded events, quakes) — or a single
critical/high signal. Each incident carries a rule-based narrative, a
threat_level, the contributing evidence (with IDs), and recommended follow-up
queries. Omit coordinates for a global brief; pass a centre+radius or bbox to
scope it. Start here, then drill into an incident's centroid with
query_vessels / gps_jamming / deep_analyze.
Inputs: lat, lon, detail, link_km, +6 more. Returns: object
kiwisdr_stationsC
Map of public KiwiSDR software-defined radio receivers worldwide.
Inputs: detail. Returns: object
list_focus_areasA
List the priority areas currently loaded PRIMARY (with fetch stats and whether each is served by a dedicated fetch or the degraded snapshot fallback).
Inputs: no named parameters. Returns: object
list_proposalsA
Pending action proposals awaiting operator approval, oldest first (GET /api/actions/proposals). 401 on a multi-user deployment by design — see propose_action.
Inputs: no named parameters. Returns: object
list_watch_rulesC
List your standing watch rules (GET /api/alerts/rules).
Inputs: detail. Returns: object
locate_emitterA
Estimate a GPS jammer/spoofer LOCATION from the degraded-ADS-B footprint (severity-weighted centroid + CEP + confidence). Turns "jamming somewhere here" into "emitter ~here ±N km". Footprint-centroid estimate (~tens of km), not RF direction-finding — stated in the response. Scope with lat/lon.
Inputs: lat, lon, detail, radius_nm. Returns: object
lookup_aircraftA
Look up one aircraft by ICAO24 hex (exact) or callsign (substring). Returns its current state, registration, an integrity/threat assessment, and the latest server-held fix.
Inputs: ident (required). Returns: object
marine_buoysC
NDBC marine buoy observations — wave height, wind, pressure, water temp.
Inputs: detail. Returns: object
maritime_chokepointsB
Vessel congestion at named straits (Hormuz/Suez/Malacca/Bosphorus/…), derived live from the AIS union: vessel + stationary counts per chokepoint.
Inputs: detail. Returns: object
maritime_coverageA
Where keyless AIS actually reaches: a dense 36x72 grid of live-vessel counts in 5 degree cells (row 0 = 90S..85S, col 0 = 180W..175W). A zero cell means no feed reports there, not that the sea is empty. Returned whole; a trimmed grid would be unreadable.
Inputs: max_age_s. Returns: object
maritime_pipeline_statsA
Health of the keyless AIS pipeline: vessels per source, the OpenWaters sweep (tiles failed/truncated, whether it completed), fix-age percentiles, and the standing note that keyless AIS is coastal, not open-ocean.
Inputs: no named parameters. Returns: object
maritime_satellite_positionsA
Unidentified satellite AIS positions: where ships are in OPEN OCEAN,
beyond coastal AIS. Each is a position, a type class and a heading only;
upstream publishes no MMSI, name or position age for them, so do not
present one as a tracked, identified vessel. bbox = lon0,lat0,lon1,lat1
(optional). count is the true total even when the list is trimmed.
Inputs: bbox, detail. Returns: object
maritime_vessels_bboxB
Vessels in a box from the freshness-wins AIS store, freshest first, each
with its fix age and every source that reported it. bbox =
lon0,lat0,lon1,lat1 (lon0 > lon1 crosses the antimeridian). count is the
true number in the box even when the list is trimmed.
Inputs: bbox (required), limit, detail, max_age_s. Returns: object
markets_snapshotB
Batched keyless market quote snapshot: indices, commodities, fx, crypto.
Inputs: detail. Returns: object
market_stressC
Composite market-stress score from equity drawdown, gold/oil moves, USD flight-to-safety, and BTC drawdown.
Inputs: detail. Returns: object
mineral_sitesA
USGS MRDS mineral deposit sites inside a bbox.
bbox is REQUIRED for data (lon_min,lat_min,lon_max,lat_max): the whole
catalogue is ~300k sites, so with no viewport this answers an empty
collection. commodity matches the MRDS CODE ("CU", "AU"), not a name.
Inputs: bbox, detail, commodity. Returns: object
ms_global_buildingsB
Microsoft Global Buildings manifest — 1.3 billion footprint polygons, download links per quadkey region.
Inputs: no named parameters. Returns: object
nas_statusB
FAA National Airspace System ground stops, ground delays, arrival/ departure delays, and airport closures, as points at the affected airport.
Inputs: detail. Returns: object
news_analysisA
Cross-source, debiased world-news intelligence.
Scrapes ~12 outlets (BBC, Reuters, AP, Al Jazeera, Guardian, CNN, Fox, …), then a reasoning model strips bias/propaganda and separates VERIFIED FACTS (corroborated by ≥2 independent outlets) from ATTRIBUTED CLAIMS and rhetoric. A leader promising "the war will end soon" is flagged as rhetoric, never reported as fact. Returns events with neutral_summary, verified_facts, attributed_claims, bias_flags, propaganda_techniques, rhetoric_flags and a confidence. May take ~30s when the cache is cold (it reasons over the feed).
Inputs: no named parameters. Returns: object
news_briefA
Latest assembled morning brief: top story per category plus a short
synthesis paragraph tying the leads together. Built on a ~20h cadence by
the background news refresher, not on-demand — use news_analysis or
the edition route for the freshest per-story detail.
Inputs: detail. Returns: object
osm_military_objectsC
Military objects from OpenStreetMap via Overpass API. bbox = south,west,north,east.
Inputs: bbox (required), detail. Returns: object
power_plantsB
WRI global power plants (≥200 MW) — name, fuel, capacity, country. Static energy-infra targets to correlate strikes/outages against.
Inputs: detail. Returns: object
propose_actionA
Queue a governed action for the operator to approve (POST /api/actions/proposals).
This does NOT run the action. name is one of the registered actions
(GET /api/actions lists them and their params): flag_entity,
promote_incident, nominate_target, add_watch, writeback. params is that
action's own param object — validated here, so a malformed proposal is
rejected now rather than at approval time. confidence (0-1) is your own
reported confidence and is recorded with the proposal.
writeback writes a record OUT to a system the operator runs (an
allow-listed HTTP endpoint, or a Foundry sql connection) and is
operator-only: send dry_run: true first, which validates the target and
the SSRF policy without sending anything.
Multi-user deployments: this server authenticates with an INTERNAL token that is deliberately never a user (apps/api/CLAUDE.md, Auth), so on a Supabase deployment these four tools answer 401 by design — a write-back has to be attributable to a person, and the internal token is not one. They are for the single-operator box, where the operator IS the one user.
Inputs: name (required), params, confidence. Returns: object
quakes_nearA
Earthquakes (USGS) within radius_km of a point (GET /api/eq).
lat, lon, and radius_km must ALL be given together — the route 422s on a partial set rather than silently falling back to the unfiltered global feed (agent-safe: pass all three or none). range is 'hour'|'day' (default)|'week'|'month'. For a severity-scored global disaster view instead, use disaster_alerts().
Inputs: lat (required), lon (required), radius_km (required), range, +1 more. Returns: object
query_aircraftA
Filtered aircraft query against the live snapshot. Returns matched_total
a capped list of compact records.
category ∈ airliner|private|helicopter|glider|military|emergency. Combine with area (centre or bbox), squawk, callsign_contains, altitude band (metres), emergency / gnss_degraded / on_ground booleans. limit ≤200.
Inputs: lat, lon, limit, detail, +13 more. Returns: object
query_vesselsB
Vessels (AIS) in an area, classified (cargo/tanker/fishing/passenger/ military/sailing/pleasure/tug). dark_only=True returns only dark-vessel candidates (moving with no static identity). Scoped by centre or bbox.
Inputs: lat, lon, limit, detail, +6 more. Returns: object
radiationC
Recent Safecast crowd radiation measurements (value + unit + location) — a nuclear-incident detection layer.
Inputs: detail. Returns: object
radio_reportsA
Amateur-radio reception reports from PSKReporter, plotted at the transmitter.
Each feature is an emitter that an independent receiving station decoded in
the last minutes, carrying callsign, Maidenhead grid, band, mode and SNR.
Positions are grid-square CENTRES, so read properties.precision_km before
treating one as a location.
Inputs: detail, minutes. Returns: object
reject_proposalA
Drop a queued proposal without executing it (POST /api/actions/proposals/{id}/reject). 401 on a multi-user deployment by design — see propose_action.
Inputs: proposal_id (required). Returns: object
relief_disastersC
ReliefWeb active humanitarian disasters, geocoded to the affected country.
Inputs: detail. Returns: object
sar_scenesA
Recent keyless Sentinel-1 SAR scenes over a box, for water AIS does not
reach. bbox = lon0,lat0,lon1,lat1. By default a SCENE INVENTORY (id, time,
footprint, public download URL). detect=True also returns LOCATED radar
contacts for a box up to one degree a side, each classified against AIS:
'dark' only where enough nearby AIS makes absence meaningful, otherwise
'unknown'. Read note: it says when detection did not run and why.
Inputs: bbox (required), days, limit, detail, +1 more. Returns: object
satnogs_ground_stationsC
Map of active SatNOGS ground stations worldwide.
Inputs: detail. Returns: object
satnogs_observationsC
Recent satellite downlink observations from the SatNOGS ground station network — real decoded telemetry from space.
Inputs: detail. Returns: object
satnogs_transmittersB
Known satellite transmitter frequencies from the SatNOGS database. Pass a NORAD catalog ID to filter to one satellite.
Inputs: detail, satellite. Returns: object
shodan_ipA
Shodan InternetDB lookup for one IPv4 address — ports, vulns, hostnames. NO API key needed.
Inputs: ip (required). Returns: object
source_catalogB
Master catalog of all data sources the platform knows about — tile providers, SAR archives, SDR directories, splat sources, streaming endpoints, and more. Filter by category.
Inputs: category. Returns: object
space_weatherB
SWPC space weather — solar X-ray flares, active alerts, and the auroral oval. The causal layer under GPS/HF degradation.
Inputs: detail. Returns: object
spc_storm_reportsC
Today's SPC severe weather reports — tornado, wind, and hail events.
Inputs: detail. Returns: object
splat_searchC
Search Sketchfab for downloadable 3D Gaussian Splat models.
Inputs: q. Returns: object
system_doctorA
What is configured, what is missing, and the exact line that fixes it.
Use this when a layer looks empty before concluding the world is quiet: a
blank layer is far more often an unconfigured optional source than an
absence of activity. Every capability listed is optional - the console runs
keyless - so a long problems list is not a fault list. Never report a
missing optional key as a defect.
Inputs: no named parameters. Returns: object
telegram_channelC
Scrape recent messages from a Telegram OSINT channel.
Inputs: channel. Returns: object
tinygs_packetsC
Recent LoRa satellite packets from the tinyGS community network.
Inputs: detail. Returns: object
track_historyB
Historical position track for ONE entity (GET /api/history/track).
id is 'aircraft:' / 'vessel:', or a bare id whose shape is unambiguous (6-char ICAO24 hex or 9-digit MMSI) — the route infers the kind from it. from_ts/to_ts are unix seconds (default: the last hour). A id the route can't resolve returns its 422 message as-is (e.g. ambiguous shape — pass a 'kind:id' prefix).
Inputs: id (required), to_ts, detail, from_ts. Returns: object
travel_advisoriesC
Official country-level travel advisories pooled from US State, UK FCDO, and Australia Smartraveller — each country's highest reported level.
Inputs: detail. Returns: object
tropical_cyclonesB
Active tropical cyclones (NHC) with center fix, classification, intensity and pressure.
Inputs: detail. Returns: object
ukraine_alertsC
Live air-raid alert status across Ukraine's 25 oblasts (alerts.com.ua).
Inputs: detail. Returns: object
unhcr_refugeesC
UNHCR refugee/IDP population figures by country.
Inputs: year. Returns: object
vessel_dossierA
Pattern-of-life dossier for one vessel (MMSI): recent track, AIS gaps, derived speed profile (loiter / transit / loiter-then-dash), area covered, which live incidents it appears in, and a behaviour assessment. The track is the store's ~1h retention window.
Inputs: mmsi (required), detail. Returns: object
volcanoesB
Smithsonian GVP Holocene volcanoes — location, type, elevation, last eruption year.
Inputs: detail. Returns: object
weather_sondesC
Live weather balloon (radiosonde) positions worldwide from SondeHub.
Inputs: detail. Returns: object
whats_changedA
Standing watch — what CHANGED since the last check, not the full picture.
Returns incidents that are NEW, ESCALATED, DE-ESCALATED, or RESOLVED. Global (no coords) reflects the background watch loop (recomputed ~every 60s); an AOI (lat/lon) diffs against YOUR previous whats_changed call for that area, so you can poll one region and be told only what moved. Use this to monitor instead of re-reading the whole brief each time.
Inputs: lat, lon, detail, radius_nm. Returns: object
worldpopC
WorldPop population grid datasets metadata. Filter by ISO3 country.
Inputs: iso3. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 94 tools
The tool set contains many overlapping analytic entry points: get_situation, focus_area, anomalies, intel_brief, area_baseline, whats_changed, deep_analyze, and answer all return situation/intel conclusions with subtle boundaries. Aircraft and vessel access is also split across several near-equivalent tools (query_aircraft/lookup_aircraft/adsb_by_*/fr24_search; query_vessels/maritime_vessels_bbox/maritime_satellite_positions), making misselection likely.
Almost all names are lowercase snake_case, which is consistent at the formatting level. However, the naming convention itself is mixed: some tools use verb_noun (query_aircraft, create_watch_rule, detect_deception), while many are bare nouns or domain labels (anomalies, intel_brief, gps_jamming, tinygs_packets, weather_sondes).
94 tools is an extreme surface for a single MCP server, far beyond the 3-15 well-scoped range. While the platform covers many data domains, the count is so large that agents must navigate a sprawling catalogue rather than a focused tool set.
The surface covers an unusually broad OSINT/geospatial domain: situation awareness, aircraft/vessel queries, anomalies, history, watch rules, proposals, imagery, disaster feeds, cyber feeds, and many specialist sources. Minor lifecycle gaps exist, such as no update_watch_rule, but core workflows and many optional layers are represented.
Maintenance
Related MCP Connectors
Read-only access to live ADSBiq aircraft and network state, with community contribution metadata.
Live AIS vessel positions by MMSI, name, area, or radius, plus coverage. No sign-in, no key.
Live AIS ship positions, vessel history, port calls and events, plus sanctions flags. Read-only.
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
- AlicenseNot gradedqualityCmaintenanceProvides live vessel positions from Fintraffic's AIS receivers in Finnish and Baltic waters, with tools to query vessels near a point, count vessels in an area, and get details by MMSI. No authentication required.296 npmMIT
- FlicenseNot gradedqualityBmaintenanceLocal MCP server that exposes fleet telemetry queries as tools for language models, enabling natural language questions about vehicle positions, trips, and alerts. Runs on the operator's machine and returns aggregated results from a SQLite database.-
- FlicenseAqualityCmaintenanceOfficial bounded, read-only access to live community-powered aircraft positions and aggregate ADSBiq network statistics.3-