A resolved-canonical-entity ontology fuses TAK, ADS-B, ASTERIX, AIS, and Remote ID into one typed object graph — so the same helicopter heard on three feeds is one entity, not three. Then an in-process MCP (Model Context Protocol) server exposes that graph as tools an AI agent can call directly: resolve_entity, find_paths, get_neighbors. The agent reasons over structured objects, not hand-written SQL — running entirely on your hardware.
Every legacy link graph treats each feed-native id (a CoT uid, an ICAO24 hex, an MMSI) as a separate node. So an aircraft heard simultaneously on TAK, ADS-B, and ASTERIX shows up three times with no connection between them. The ontology fixes this with deterministic cross-feed resolution: a shared key (ICAO24 for crewed aircraft, Operator+Serial for drones, MMSI for vessels, CoT Uid for ground units) collapses every observation of the same platform into one canonical entity.
ResolveByAlias(source, nativeId), the single lookup the resolver does on every observation.<dataDir>/gpkg/ontology.gpkg holds entities, links, aliases, and embeddings; survives restarts, opens in QGIS/ArcGIS Pro.# cross-feed fusion by shared key Aircraft (crewed) → ICAO24 joins ASTERIX CAT021/062 + ADS-B + CoT UAS (drones) → OperatorId:Serial joins RemoteID + SDR UAS fields Vessels → MMSI AIS only (no cross-feed merge partner) Ground / Person → CoT Uid TAK-native Organization → op:<operatorId> ● no fuzzy matching · no false positives
An in-process MCP server (official ModelContextProtocol SDK, Streamable HTTP, Stateless mode) exposes the ontology as eight typed tools at /mcp on the existing Kestrel. Cursor, Claude Desktop, or your own automation gets structured JSON it can reason over.
Map a feed-native id (CoT uid, ICAO24 hex, MMSI) to its canonical entity id. The single lookup that powers every downstream operation.
Fetch an entity plus its full alias history — which feeds saw it, when, the provenance/lineage view in one call.
The "Search Around" / Expand operation — a canonical entity's 1-to-3-hop ego-network (nodes + edges).
Shortest paths between two entities — the Palantir "Find Paths". Pure C# BFS over the persisted links, bounded by maxHops.
Centrality over the entity graph (onager when available; degree-centrality fallback) — find the hubs.
Community detection — cluster the graph into neighborhoods to surface operational groupings.
The Palantir AIP insight, copied locally: instead of naive RAG (hand the LLM raw document snippets), retrieve typed, governed ontology objects and their links and inject them as structured context. The LLM reasons over explicit entities and relations rather than re-deriving them from prose. Two-tier retrieval: deterministic ID extraction (callsign / ICAO hex / MMSI regex → alias lookup) plus optional embeddings cosine search when the sidecar is up.
# the 8 typed tools resolve_entity → {entityId} get_entity → entity + aliases list_entities → filtered by type get_neighbors → ego-network find_paths → shortest paths pagerank → centrality louvain → communities summary → counts + breakdown # one write tool, gated merge_entities → AllowWrites=false by default ● read-only by default · loopback only
The MCP endpoint is loopback-only by default (the host binds IPAddress.Loopback). The existing ApiKeyMiddleware covers /api/*; /mcp is off unless Mcp:Enabled=true in appsettings.json. LAN exposure + MCP OAuth is a documented follow-up. The ontology schema is a Phase-1 static type registry; runtime-mutable "Dynamic" schema (Palantir's pitch) and probabilistic matching are deferred — deterministic keys + a manual merge action handle the realistic cases without the false-positive risk.
Resolve across feeds, store as typed objects, expose as tools — and a connected AI agent can ask "who has N12345 been co-located with?" and get the resolved entity's neighborhood back as structured data. All on your hardware, no cloud round-trip.