tak-server-mcp-skyfi
Version:
Model Context Protocol (MCP) server for TAK Server integration with geospatial support
211 lines • 8.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.spatialQueryTool = void 0;
const turf = __importStar(require("@turf/turf"));
const mgrs_1 = __importDefault(require("mgrs"));
const h3 = __importStar(require("h3-js"));
exports.spatialQueryTool = {
name: 'tak_spatial_query',
description: 'Query TAK entities within a geographic area using point-radius or polygon',
category: 'geospatial',
requiresAuth: true,
requiresWrite: false,
inputSchema: {
type: 'object',
properties: {
center: {
type: 'array',
items: { type: 'number' },
minItems: 2,
maxItems: 2,
description: 'Center point [lat, lon] for radius search'
},
radius: {
type: 'number',
minimum: 0,
description: 'Search radius in meters (required if using center point)'
},
polygon: {
type: 'array',
items: {
type: 'array',
items: { type: 'number' },
minItems: 2,
maxItems: 2
},
minItems: 3,
description: 'Polygon coordinates [[lat, lon], ...] for polygon search'
},
types: {
type: 'array',
items: { type: 'string' },
description: 'Filter by entity types'
},
timeWindow: {
type: 'object',
properties: {
start: { type: 'string', format: 'date-time' },
end: { type: 'string', format: 'date-time' }
},
description: 'Time window for historical queries'
},
includeStale: {
type: 'boolean',
default: false,
description: 'Include stale entities in results'
}
},
oneOf: [
{ required: ['center', 'radius'] },
{ required: ['polygon'] }
]
},
async handler(context) {
const { takClient, params, logger } = context;
logger.info('Performing spatial query', { params });
try {
// Perform the spatial query
const events = await takClient.spatialQuery({
center: params.center,
radius: params.radius,
polygon: params.polygon,
types: params.types,
timeWindow: params.timeWindow ? {
start: new Date(params.timeWindow.start),
end: new Date(params.timeWindow.end)
} : undefined
});
// Filter out stale events if requested
const now = new Date();
const filteredEvents = params.includeStale
? events
: events.filter(event => new Date(event.stale) > now);
// Calculate additional geospatial properties
const enrichedResults = filteredEvents.map(event => {
const point = turf.point([event.point.lon, event.point.lat]);
let distanceFromCenter = null;
let bearing = null;
if (params.center && params.radius) {
const centerPoint = turf.point([params.center[1], params.center[0]]);
distanceFromCenter = turf.distance(centerPoint, point, { units: 'meters' });
bearing = turf.bearing(centerPoint, point);
}
return {
uid: event.uid,
type: event.type,
callsign: event.detail?.contact?.callsign,
location: {
lat: event.point.lat,
lon: event.point.lon,
alt: event.point.hae,
mgrs: convertToMGRS(event.point.lat, event.point.lon), // Placeholder
h3Index: getH3Index(event.point.lat, event.point.lon, 9) // Placeholder
},
time: event.time,
stale: event.stale,
distanceFromCenter,
bearing,
team: event.detail?.group?.name,
role: event.detail?.group?.role,
status: event.detail?.status
};
});
// Group results by type
const groupedByType = enrichedResults.reduce((acc, event) => {
const type = event.type || 'unknown';
if (!acc[type])
acc[type] = [];
acc[type].push(event);
return acc;
}, {});
return {
success: true,
totalCount: enrichedResults.length,
results: enrichedResults,
summary: {
byType: Object.entries(groupedByType).map(([type, items]) => ({
type,
count: items.length
})),
bounds: calculateBounds(enrichedResults),
timeRange: {
earliest: enrichedResults.reduce((min, e) => new Date(e.time) < new Date(min) ? e.time : min, enrichedResults[0]?.time),
latest: enrichedResults.reduce((max, e) => new Date(e.time) > new Date(max) ? e.time : max, enrichedResults[0]?.time)
}
}
};
}
catch (error) {
logger.error('Spatial query failed', error);
throw error;
}
}
};
// Helper functions with real implementations
function convertToMGRS(lat, lon) {
try {
return mgrs_1.default.forward([lon, lat]);
}
catch (error) {
// Fallback if MGRS conversion fails
return `${Math.abs(lat).toFixed(6)}${lat >= 0 ? 'N' : 'S'} ${Math.abs(lon).toFixed(6)}${lon >= 0 ? 'E' : 'W'}`;
}
}
function getH3Index(lat, lon, resolution) {
try {
return h3.latLngToCell(lat, lon, resolution);
}
catch (error) {
// Fallback if H3 conversion fails
return `${lat.toFixed(2)}_${lon.toFixed(2)}_r${resolution}`;
}
}
function calculateBounds(events) {
if (events.length === 0)
return null;
const lats = events.map(e => e.location.lat);
const lons = events.map(e => e.location.lon);
return [
Math.min(...lons),
Math.min(...lats),
Math.max(...lons),
Math.max(...lats)
];
}
//# sourceMappingURL=spatial-query.js.map