UNPKG

tak-server-mcp-skyfi

Version:

Model Context Protocol (MCP) server for TAK Server integration with geospatial support

225 lines 9.52 kB
"use strict"; 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.findNearestTool = void 0; const turf = __importStar(require("@turf/turf")); exports.findNearestTool = { name: 'tak_find_nearest', description: 'Find nearest entities to a point or entity', category: 'geospatial', requiresAuth: true, requiresWrite: false, inputSchema: { type: 'object', properties: { point: { type: 'object', properties: { entityId: { type: 'string', description: 'Entity ID to find nearest entities to' }, coordinates: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: 'Coordinates [lat, lon] to find nearest entities to' } }, description: 'Reference point (provide either entityId or coordinates)' }, maxDistance: { type: 'number', minimum: 0, description: 'Maximum search distance in meters' }, maxResults: { type: 'number', minimum: 1, maximum: 100, default: 10, description: 'Maximum number of results to return' }, entityTypes: { type: 'array', items: { type: 'string' }, description: 'Filter by entity types (e.g., ["a-f-*", "a-h-*"])' }, excludeStale: { type: 'boolean', default: true, description: 'Exclude stale entities from results' }, includeDetails: { type: 'boolean', default: true, description: 'Include full entity details in results' } }, required: ['point'] }, handler: async (context) => { const { takClient, params, logger } = context; try { // Get reference coordinates let referenceCoords; if (params.point.coordinates) { referenceCoords = params.point.coordinates; } else if (params.point.entityId) { const entities = await takClient.getEntities(); const entity = entities.find(e => e.uid === params.point.entityId); if (!entity) { throw new Error(`Entity ${params.point.entityId} not found`); } referenceCoords = [entity.location.lat, entity.location.lon]; } else { throw new Error('Either entityId or coordinates must be provided'); } const referencePoint = turf.point([referenceCoords[1], referenceCoords[0]]); // turf uses [lon, lat] // Get all entities logger.debug('Fetching entities from TAK Server'); const allEntities = await takClient.getEntities(); // Filter entities let filteredEntities = allEntities; // Exclude the reference entity if entityId was provided if (params.point.entityId) { filteredEntities = filteredEntities.filter(e => e.uid !== params.point.entityId); } // Filter by entity types if (params.entityTypes && params.entityTypes.length > 0) { filteredEntities = filteredEntities.filter(entity => { return params.entityTypes.some((typePattern) => { if (typePattern.includes('*')) { const regex = new RegExp('^' + typePattern.replace('*', '.*') + '$'); return regex.test(entity.type); } return entity.type === typePattern; }); }); } // Exclude stale entities if (params.excludeStale !== false) { filteredEntities = filteredEntities.filter(entity => { // Check if entity has lastUpdate field if (!entity.lastUpdate) return true; const lastUpdate = new Date(entity.lastUpdate); const now = new Date(); const timeSinceUpdate = (now.getTime() - lastUpdate.getTime()) / 1000; // Consider stale if not updated in last 5 minutes return timeSinceUpdate < 300; }); } // Calculate distances const entitiesWithDistance = filteredEntities.map(entity => { const entityPoint = turf.point([entity.location.lon, entity.location.lat]); const distance = turf.distance(referencePoint, entityPoint, { units: 'meters' }); const bearing = turf.bearing(referencePoint, entityPoint); return { entity: params.includeDetails !== false ? entity : { uid: entity.uid, type: entity.type, callsign: entity.callsign || entity.uid }, distance: Math.round(distance), bearing: Math.round(bearing), compassDirection: getCompassDirection(bearing), location: { lat: entity.location.lat, lon: entity.location.lon, hae: entity.location.alt || 0 } }; }); // Filter by max distance let results = entitiesWithDistance; if (params.maxDistance) { results = results.filter(item => item.distance <= params.maxDistance); } // Sort by distance and limit results results.sort((a, b) => a.distance - b.distance); results = results.slice(0, params.maxResults || 10); logger.info(`Found ${results.length} nearest entities`); // Calculate some statistics const stats = { totalEntities: allEntities.length, filteredEntities: filteredEntities.length, resultsReturned: results.length, nearestDistance: results[0]?.distance || null, farthestDistance: results[results.length - 1]?.distance || null, averageDistance: results.length > 0 ? Math.round(results.reduce((sum, r) => sum + r.distance, 0) / results.length) : null }; return { success: true, data: { referencePoint: { entityId: params.point.entityId, coordinates: referenceCoords }, nearestEntities: results, stats: stats }, metadata: { timestamp: new Date().toISOString(), source: 'tak-server' } }; } catch (error) { logger.error('Failed to find nearest entities:', error); return { success: false, error: { code: 'TAK_QUERY_ERROR', message: 'Failed to find nearest entities', details: error instanceof Error ? error.message : 'Unknown error' } }; } } }; // Helper function to convert bearing to compass direction function getCompassDirection(bearing) { const directions = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']; const index = Math.round(((bearing + 360) % 360) / 22.5); return directions[index % 16]; } //# sourceMappingURL=find-nearest.js.map