tak-server-mcp-skyfi
Version:
Model Context Protocol (MCP) server for TAK Server integration with geospatial support
480 lines (470 loc) ⢠19.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeospatialResearchAgent = void 0;
const sdk_1 = require("@anthropic-ai/sdk");
const mcp_client_1 = require("../clients/mcp-client");
const tak_server_1 = require("../clients/tak-server");
const events_1 = require("events");
const pino_1 = __importDefault(require("pino"));
const chalk_1 = __importDefault(require("chalk"));
const prompts_1 = __importDefault(require("prompts"));
const logger = (0, pino_1.default)({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
});
class GeospatialResearchAgent extends events_1.EventEmitter {
anthropic;
mcpClient;
takClient;
situation;
monitoring = false;
config;
constructor(config) {
super();
this.config = config;
// Initialize clients
this.anthropic = new sdk_1.Anthropic({
apiKey: config.anthropicApiKey
});
this.mcpClient = new mcp_client_1.MCPClient({
url: config.mcpServerUrl,
transport: 'http'
});
this.takClient = new tak_server_1.TAKServerClient({
url: config.takServerUrl,
apiToken: config.takApiToken
});
this.situation = {
friendlyForces: [],
hostileForces: [],
unknownForces: [],
neutralForces: [],
alerts: [],
geofences: [],
missions: [],
lastUpdate: new Date()
};
}
async initialize() {
logger.info('Initializing Geospatial Research Agent...');
// Test connections
await this.takClient.testConnection();
await this.mcpClient.connect();
// Load initial situation
await this.updateSituation();
logger.info('Agent initialized successfully');
}
async updateSituation() {
logger.debug('Updating tactical situation...');
try {
// Get all entities
const entities = await this.takClient.getEntities();
// Categorize by affiliation
this.situation.friendlyForces = entities.filter(e => e.type.startsWith('a-f'));
this.situation.hostileForces = entities.filter(e => e.type.startsWith('a-h'));
this.situation.unknownForces = entities.filter(e => e.type.startsWith('a-u'));
this.situation.neutralForces = entities.filter(e => e.type.startsWith('a-n'));
// Get active alerts
this.situation.alerts = await this.takClient.getAlerts(true);
// Get missions
this.situation.missions = await this.takClient.getMissions();
this.situation.lastUpdate = new Date();
this.emit('situation-updated', this.situation);
}
catch (error) {
logger.error('Failed to update situation:', error);
}
}
async startMonitoring() {
if (this.monitoring)
return;
logger.info('Starting real-time monitoring...');
this.monitoring = true;
// Subscribe to CoT events
await this.takClient.subscribeToCotEvents({
types: ['a-h-*', 'b-a-o-tbl'], // Hostile forces and emergencies
}, (event) => {
this.handleRealtimeEvent(event);
});
// Periodic situation updates
const updateInterval = setInterval(async () => {
if (!this.monitoring) {
clearInterval(updateInterval);
return;
}
await this.updateSituation();
}, 30000); // Every 30 seconds
}
async stopMonitoring() {
logger.info('Stopping monitoring...');
this.monitoring = false;
await this.takClient.unsubscribe();
}
async handleRealtimeEvent(event) {
logger.info('Real-time event received:', event.type);
// Check if this is a high-priority event
if (event.type === 'b-a-o-tbl' || event.type.startsWith('a-h')) {
const analysis = await this.analyzeEvent(event);
this.emit('critical-event', { event, analysis });
}
}
async analyzeEvent(event) {
const tools = await this.mcpClient.listTools();
const response = await this.anthropic.messages.create({
model: this.config.model || 'claude-3-opus-20240229',
max_tokens: 1024,
tools: tools,
messages: [
{
role: 'user',
content: `Analyze this TAK event and provide assessment:
Event Type: ${event.type}
Location: ${event.point.lat}, ${event.point.lon}
Time: ${event.time}
Callsign: ${event.detail?.contact?.callsign || 'Unknown'}
Current situation summary:
- Friendly forces: ${this.situation.friendlyForces.length}
- Hostile forces: ${this.situation.hostileForces.length}
- Active alerts: ${this.situation.alerts.length}
Provide:
1. Event classification and threat level
2. Potential impact on friendly forces
3. Recommended actions`
}
]
});
return response.content[0].text;
}
async performGeospatialAnalysis(query) {
logger.info('Performing geospatial analysis:', query);
const tools = await this.mcpClient.listTools();
const response = await this.anthropic.messages.create({
model: this.config.model || 'claude-3-opus-20240229',
max_tokens: 2048,
tools: tools,
tool_choice: { type: 'auto' },
messages: [
{
role: 'user',
content: `You are a geospatial intelligence analyst with access to real-time TAK Server data.
Current tactical situation:
- Friendly forces: ${this.situation.friendlyForces.length}
- Hostile forces: ${this.situation.hostileForces.length}
- Unknown forces: ${this.situation.unknownForces.length}
- Active alerts: ${this.situation.alerts.length}
- Last update: ${this.situation.lastUpdate.toISOString()}
Use the available tools to gather data and provide detailed analysis.`
},
{
role: 'user',
content: query
}
]
});
// Process tool calls
const toolResults = [];
for (const content of response.content) {
if (content.type === 'tool_use') {
const result = await this.mcpClient.callTool(content.name, content.input);
toolResults.push({ tool: content.name, result });
}
}
return {
analysis: response.content.find(c => c.type === 'text')?.text,
toolResults,
timestamp: new Date()
};
}
async generateSituationReport() {
logger.info('Generating situation report...');
const response = await this.anthropic.messages.create({
model: this.config.model || 'claude-3-opus-20240229',
max_tokens: 4096,
messages: [
{
role: 'user',
content: 'You are a military intelligence analyst. Generate a comprehensive situation report (SITREP) based on the current tactical data.'
},
{
role: 'user',
content: `Generate a detailed SITREP based on this data:
FRIENDLY FORCES (${this.situation.friendlyForces.length} total):
${JSON.stringify(this.situation.friendlyForces.slice(0, 5), null, 2)}
HOSTILE FORCES (${this.situation.hostileForces.length} total):
${JSON.stringify(this.situation.hostileForces.slice(0, 5), null, 2)}
UNKNOWN CONTACTS (${this.situation.unknownForces.length} total):
${JSON.stringify(this.situation.unknownForces.slice(0, 5), null, 2)}
ACTIVE ALERTS:
${JSON.stringify(this.situation.alerts, null, 2)}
CURRENT MISSIONS:
${JSON.stringify(this.situation.missions, null, 2)}
Include:
1. Executive Summary
2. Enemy Forces (composition, disposition, strength)
3. Friendly Forces (status, readiness)
4. Analysis and Assessment
5. Recommended Actions
6. Intelligence Gaps`
}
]
});
return response.content[0].text;
}
async runInteractiveDemo() {
console.log(chalk_1.default.bold.blue('\nš°ļø TAK Server Geospatial Research Agent Demo\n'));
await this.initialize();
await this.startMonitoring();
const demos = [
{
title: 'Threat Detection & Analysis',
value: 'threat-detection',
description: 'Identify and analyze potential threats in an area'
},
{
title: 'Pattern Recognition',
value: 'pattern-recognition',
description: 'Detect movement patterns and anomalies'
},
{
title: 'Geofence Monitoring',
value: 'geofence',
description: 'Create geofences and monitor breaches'
},
{
title: 'Situation Report',
value: 'sitrep',
description: 'Generate comprehensive situation report'
},
{
title: 'Real-time Monitoring',
value: 'realtime',
description: 'Monitor live events with AI analysis'
},
{
title: 'Custom Query',
value: 'custom',
description: 'Ask any geospatial question'
}
];
while (true) {
const { demo } = await (0, prompts_1.default)({
type: 'select',
name: 'demo',
message: 'Select a demo:',
choices: demos,
initial: 0
});
if (!demo)
break;
switch (demo) {
case 'threat-detection':
await this.runThreatDetectionDemo();
break;
case 'pattern-recognition':
await this.runPatternRecognitionDemo();
break;
case 'geofence':
await this.runGeofenceDemo();
break;
case 'sitrep':
await this.runSitrepDemo();
break;
case 'realtime':
await this.runRealtimeDemo();
break;
case 'custom':
await this.runCustomQueryDemo();
break;
}
const { continue: cont } = await (0, prompts_1.default)({
type: 'confirm',
name: 'continue',
message: 'Run another demo?',
initial: true
});
if (!cont)
break;
}
await this.stopMonitoring();
console.log(chalk_1.default.green('\nā
Demo completed. Thank you!\n'));
}
async runThreatDetectionDemo() {
console.log(chalk_1.default.yellow('\nšÆ Running Threat Detection Demo...\n'));
const { lat, lon, radius } = await (0, prompts_1.default)([
{
type: 'number',
name: 'lat',
message: 'Enter center latitude:',
initial: 37.7749
},
{
type: 'number',
name: 'lon',
message: 'Enter center longitude:',
initial: -122.4194
},
{
type: 'number',
name: 'radius',
message: 'Enter search radius (km):',
initial: 50
}
]);
const analysis = await this.performGeospatialAnalysis(`Identify all potential threats within ${radius}km of coordinates ${lat}, ${lon}.
Analyze hostile forces, unknown contacts, and any emergency signals.
Assess threat levels and recommend defensive measures.`);
console.log(chalk_1.default.bold('\nš Analysis Results:\n'));
console.log(analysis.analysis);
if (analysis.toolResults.length > 0) {
console.log(chalk_1.default.bold('\nš§ Data Sources Used:\n'));
analysis.toolResults.forEach((result) => {
console.log(chalk_1.default.cyan(`- ${result.tool}`));
});
}
}
async runPatternRecognitionDemo() {
console.log(chalk_1.default.yellow('\nš Running Pattern Recognition Demo...\n'));
const analysis = await this.performGeospatialAnalysis(`Analyze movement patterns of all tracked entities over the past hour.
Identify any unusual behavior, formation changes, or coordinated movements.
Look for patterns that might indicate tactical maneuvers or threats.`);
console.log(chalk_1.default.bold('\nš Pattern Analysis:\n'));
console.log(analysis.analysis);
}
async runGeofenceDemo() {
console.log(chalk_1.default.yellow('\nš§ Running Geofence Demo...\n'));
const { name, lat, lon, radius } = await (0, prompts_1.default)([
{
type: 'text',
name: 'name',
message: 'Enter geofence name:',
initial: 'Restricted Zone Alpha'
},
{
type: 'number',
name: 'lat',
message: 'Enter center latitude:',
initial: 37.7749
},
{
type: 'number',
name: 'lon',
message: 'Enter center longitude:',
initial: -122.4194
},
{
type: 'number',
name: 'radius',
message: 'Enter radius (meters):',
initial: 5000
}
]);
// Create geofence
const geofence = await this.mcpClient.callTool('tak_create_geofence', {
name,
center: [lat, lon],
radius,
alertOnEntry: true,
alertOnExit: true,
monitorTypes: ['a-h-*', 'a-u-*'] // Hostile and unknown
});
console.log(chalk_1.default.green(`\nā
Geofence "${name}" created successfully!\n`));
// Monitor for 30 seconds
console.log(chalk_1.default.cyan('Monitoring geofence for 30 seconds...\n'));
let breaches = 0;
const monitor = setInterval(async () => {
const events = await this.mcpClient.callTool('tak_check_geofence', {
geofenceId: geofence.id
});
if (events.breaches && events.breaches.length > 0) {
breaches += events.breaches.length;
console.log(chalk_1.default.red(`ā ļø BREACH DETECTED: ${events.breaches.length} entities entered geofence!`));
for (const breach of events.breaches) {
const analysis = await this.analyzeEvent(breach);
console.log(chalk_1.default.yellow(`\nBreach Analysis:\n${analysis}\n`));
}
}
}, 5000);
await new Promise(resolve => setTimeout(resolve, 30000));
clearInterval(monitor);
console.log(chalk_1.default.bold(`\nš Monitoring Summary: ${breaches} total breaches detected\n`));
}
async runSitrepDemo() {
console.log(chalk_1.default.yellow('\nš Generating Situation Report...\n'));
const sitrep = await this.generateSituationReport();
console.log(chalk_1.default.bold('SITUATION REPORT\n'));
console.log(chalk_1.default.gray('ā'.repeat(60)));
console.log(sitrep);
console.log(chalk_1.default.gray('ā'.repeat(60)));
}
async runRealtimeDemo() {
console.log(chalk_1.default.yellow('\nš” Starting Real-time Monitoring Demo...\n'));
console.log(chalk_1.default.cyan('Monitoring for critical events for 60 seconds...\n'));
let eventCount = 0;
const eventHandler = (data) => {
eventCount++;
console.log(chalk_1.default.red(`\nšØ CRITICAL EVENT DETECTED:`));
console.log(chalk_1.default.yellow(`Type: ${data.event.type}`));
console.log(chalk_1.default.yellow(`Location: ${data.event.point.lat}, ${data.event.point.lon}`));
console.log(chalk_1.default.yellow(`Time: ${new Date(data.event.time).toLocaleString()}`));
console.log(chalk_1.default.bold('\nAI Analysis:'));
console.log(data.analysis);
console.log(chalk_1.default.gray('ā'.repeat(60)));
};
this.on('critical-event', eventHandler);
// Simulate some events for demo
setTimeout(() => {
this.emit('critical-event', {
event: {
type: 'a-h-A',
point: { lat: 37.8, lon: -122.4 },
time: new Date(),
detail: { contact: { callsign: 'HOSTILE-1' } }
},
analysis: 'Hostile aircraft detected entering monitored airspace. Recommend immediate defensive posture.'
});
}, 5000);
await new Promise(resolve => setTimeout(resolve, 60000));
this.off('critical-event', eventHandler);
console.log(chalk_1.default.bold(`\nš Monitoring Summary: ${eventCount} critical events detected\n`));
}
async runCustomQueryDemo() {
console.log(chalk_1.default.yellow('\nš¬ Custom Query Demo\n'));
const { query } = await (0, prompts_1.default)({
type: 'text',
name: 'query',
message: 'Enter your geospatial query:',
initial: 'What is the current disposition of all forces within 100km of our position?'
});
console.log(chalk_1.default.cyan('\nš Processing query...\n'));
const analysis = await this.performGeospatialAnalysis(query);
console.log(chalk_1.default.bold('\nš Analysis Results:\n'));
console.log(analysis.analysis);
if (analysis.toolResults.length > 0) {
console.log(chalk_1.default.bold('\nš§ Data Sources Used:\n'));
analysis.toolResults.forEach((result) => {
console.log(chalk_1.default.cyan(`- ${result.tool}`));
if (result.result.summary) {
console.log(chalk_1.default.gray(` Summary: ${JSON.stringify(result.result.summary, null, 2)}`));
}
});
}
}
}
exports.GeospatialResearchAgent = GeospatialResearchAgent;
// Demo runner
if (require.main === module) {
const agent = new GeospatialResearchAgent({
anthropicApiKey: process.env.ANTHROPIC_API_KEY || '',
mcpServerUrl: process.env.MCP_SERVER_URL || 'http://localhost:3000',
takServerUrl: process.env.TAK_SERVER_URL || '',
takApiToken: process.env.TAK_SERVER_API_TOKEN
});
agent.runInteractiveDemo().catch(console.error);
}
//# sourceMappingURL=agent.js.map