UNPKG

stellar-cyber-mcp-agents

Version:

Model Context Protocol (MCP) server for Stellar Cyber security operations with specialized multi-agent analysis capabilities

762 lines 33.2 kB
import { EventEmitter } from 'eventemitter3'; import { InMemoryAgentRegistry } from './core/agent-registry.js'; import { InMemoryCommunicationChannel } from './core/communication-channel.js'; import { HubAgent, createHubAgentMetadata } from './agents/hub-agent.js'; import { InvestigationAgent, createInvestigationAgentMetadata } from './agents/investigation-agent.js'; import { CorrelationAgent, createCorrelationAgentMetadata } from './agents/correlation-agent.js'; import { NetworkAnalysisAgent, createNetworkAnalysisAgentMetadata } from './agents/network-agent.js'; import { AgentState, AgentHealth } from './types/agent.js'; class SimpleLogger { level; enableConsole; enableFile; filePath; constructor(config) { this.level = config.level; this.enableConsole = config.enableConsole; this.enableFile = config.enableFile; this.filePath = config.filePath; } debug(message, meta) { if (this.shouldLog('debug')) { this.log('DEBUG', message, meta); } } info(message, meta) { if (this.shouldLog('info')) { this.log('INFO', message, meta); } } warn(message, meta) { if (this.shouldLog('warn')) { this.log('WARN', message, meta); } } error(message, meta) { if (this.shouldLog('error')) { this.log('ERROR', message, meta); } } fatal(message, meta) { this.log('FATAL', message, meta); } shouldLog(level) { const levels = ['debug', 'info', 'warn', 'error', 'fatal']; return levels.indexOf(level) >= levels.indexOf(this.level); } log(level, message, meta) { const timestamp = new Date().toISOString(); const logEntry = `${timestamp} [${level}] ${message}`; if (this.enableConsole) { console.log(logEntry, meta ? JSON.stringify(meta, null, 2) : ''); } if (this.enableFile && this.filePath) { // In a real implementation, you would write to file // For now, just log to console with file indicator console.log(`[FILE] ${logEntry}`, meta ? JSON.stringify(meta, null, 2) : ''); } } } class SimpleMetrics { counters = new Map(); gauges = new Map(); histograms = new Map(); timers = new Map(); incrementCounter(name, value = 1) { const current = this.counters.get(name) || 0; this.counters.set(name, current + value); } recordGauge(name, value) { this.gauges.set(name, value); } recordHistogram(name, value) { if (!this.histograms.has(name)) { this.histograms.set(name, []); } this.histograms.get(name)?.push(value); } recordTimer(name, duration) { if (!this.timers.has(name)) { this.timers.set(name, []); } this.timers.get(name)?.push(duration); } getMetrics() { const metrics = { counters: Object.fromEntries(this.counters), gauges: Object.fromEntries(this.gauges), histograms: {}, timers: {} }; // Calculate histogram statistics for (const [name, values] of this.histograms) { if (values.length > 0) { metrics.histograms[name] = { count: values.length, min: Math.min(...values), max: Math.max(...values), avg: values.reduce((a, b) => a + b, 0) / values.length }; } } // Calculate timer statistics for (const [name, values] of this.timers) { if (values.length > 0) { metrics.timers[name] = { count: values.length, min: Math.min(...values), max: Math.max(...values), avg: values.reduce((a, b) => a + b, 0) / values.length }; } } return metrics; } } export class MultiAgentOrchestrator extends EventEmitter { config; registry; channel; logger; metrics; agents = new Map(); hubAgent = null; status = 'initializing'; startTime = new Date(); constructor(config) { super(); this.config = config; // Initialize core services this.registry = new InMemoryAgentRegistry(); this.channel = new InMemoryCommunicationChannel(); this.logger = new SimpleLogger(config.logging); this.metrics = new SimpleMetrics(); this.logger.info('MultiAgentOrchestrator initialized', { config }); } async start() { this.logger.info('Starting MultiAgentOrchestrator...'); try { this.status = 'initializing'; this.startTime = new Date(); // Initialize agents await this.initializeAgents(); // Start all agents await this.startAgents(); // Set up monitoring this.setupMonitoring(); this.status = 'running'; this.logger.info('MultiAgentOrchestrator started successfully'); this.emit('started'); } catch (error) { this.status = 'error'; this.logger.error('Failed to start MultiAgentOrchestrator', { error }); throw error; } } async stop() { this.logger.info('Stopping MultiAgentOrchestrator...'); try { this.status = 'stopping'; // Stop all agents await this.stopAgents(); // Close communication channel await this.channel.close(); this.status = 'stopped'; this.logger.info('MultiAgentOrchestrator stopped successfully'); this.emit('stopped'); } catch (error) { this.status = 'error'; this.logger.error('Failed to stop MultiAgentOrchestrator', { error }); throw error; } } async getStatus() { const agentStatuses = await this.registry.getAllAgentStatuses(); const registryStats = await this.registry.getRegistryStats(); const metrics = this.metrics.getMetrics?.() || {}; const agentHealthCounts = { total: agentStatuses.length, running: agentStatuses.filter(s => s.state === AgentState.READY).length, healthy: agentStatuses.filter(s => s.health === AgentHealth.HEALTHY).length, degraded: agentStatuses.filter(s => s.health === AgentHealth.DEGRADED).length, unhealthy: agentStatuses.filter(s => s.health === AgentHealth.UNHEALTHY).length }; return { status: this.status, uptime: Date.now() - this.startTime.getTime(), agents: agentHealthCounts, performance: { totalRequests: registryStats.totalRequests, successfulRequests: metrics.counters['requests.success'] || 0, failedRequests: metrics.counters['requests.failed'] || 0, averageResponseTime: metrics.timers['requests.response_time']?.avg || 0 }, lastUpdate: new Date().toISOString() }; } async executeWorkflow(workflowId, input) { if (!this.hubAgent) { throw new Error('Hub agent not available'); } this.logger.info('Executing workflow through orchestrator', { workflowId, input }); const response = await this.hubAgent.sendRequest(this.hubAgent.getId(), 'orchestrate_workflow', { workflowId, input }); if (!response.success) { throw new Error(`Workflow execution failed: ${response.error?.message || 'Unknown error'}`); } return response.result.executionId; } async investigateCase(caseId, options = {}) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Investigating case through orchestrator', { caseId, options }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'investigate_case', { caseId, options }); if (!response.success) { throw new Error(`Case investigation failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async correlateCase(caseId, method = 'observables') { const correlationAgent = this.findAgentByType('correlation'); if (!correlationAgent) { throw new Error('Correlation agent not available'); } this.logger.info('Correlating case through orchestrator', { caseId, method }); const response = await correlationAgent.sendRequest(correlationAgent.getId(), 'find_related_cases', { caseId, method }); if (!response.success) { throw new Error(`Case correlation failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async analyzeNetwork(caseId, timeRange, options = {}) { const networkAgent = this.findAgentByType('network'); if (!networkAgent) { throw new Error('Network agent not available'); } this.logger.info('Analyzing network through orchestrator', { caseId, timeRange, options }); const response = await networkAgent.sendRequest(networkAgent.getId(), 'analyze_network_activity', { caseId, timeRange, options }); if (!response.success) { throw new Error(`Network analysis failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getAgentMetrics() { const registryStats = await this.registry.getRegistryStats(); const channelStats = this.channel.getChannelStats?.() || {}; const systemMetrics = this.metrics.getMetrics?.() || {}; return { registry: registryStats, channel: channelStats, system: systemMetrics, agents: await this.getAgentSpecificMetrics() }; } async searchCases(query, filters = {}) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Searching cases through orchestrator', { query, filters }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'search_cases', { query, filters }); if (!response.success) { throw new Error(`Case search failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getCaseActivities(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting case activities through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_case_activities', { caseId }); if (!response.success) { throw new Error(`Get case activities failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getCaseObservables(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting case observables through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_case_observables', { caseId }); if (!response.success) { throw new Error(`Get case observables failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getCaseAlerts(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting case alerts through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_case_alerts', { caseId }); if (!response.success) { throw new Error(`Get case alerts failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getCaseComments(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting case comments through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_case_comments', { caseId }); if (!response.success) { throw new Error(`Get case comments failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getCaseDetailedSummary(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting case detailed summary through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_case_details', { caseId }); if (!response.success) { throw new Error(`Get case detailed summary failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getCaseScores(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting case scores through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_case_scores', { caseId }); if (!response.success) { throw new Error(`Get case scores failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async analyzeCaseObservables(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Analyzing case observables through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'analyze_case_observables', { caseId }); if (!response.success) { throw new Error(`Analyze case observables failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getCaseTimeline(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting case timeline through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_case_timeline', { caseId }); if (!response.success) { throw new Error(`Get case timeline failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getCaseArtifacts(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting case artifacts through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_case_artifacts', { caseId }); if (!response.success) { throw new Error(`Get case artifacts failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async suggestWorkflow(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Suggesting workflow through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'suggest_workflow', { caseId }); if (!response.success) { throw new Error(`Suggest workflow failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async updateCaseStatus(caseId, status, comment) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Updating case status through orchestrator', { caseId, status, comment }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'update_case_status', { caseId, status, comment }); if (!response.success) { throw new Error(`Update case status failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async addCaseComment(caseId, comment) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Adding case comment through orchestrator', { caseId, comment }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'add_case_comment', { caseId, comment }); if (!response.success) { throw new Error(`Add case comment failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getRelatedCases(caseId, method = 'observables') { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting related cases through orchestrator', { caseId, method }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_related_cases', { caseId, method }); if (!response.success) { throw new Error(`Get related cases failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getThreatIntelligence(caseId) { const investigationAgent = this.findAgentByType('investigation'); if (!investigationAgent) { throw new Error('Investigation agent not available'); } this.logger.info('Getting threat intelligence through orchestrator', { caseId }); const response = await investigationAgent.sendRequest(investigationAgent.getId(), 'get_threat_intelligence', { caseId }); if (!response.success) { throw new Error(`Get threat intelligence failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async detectCampaigns(criteria = {}) { const correlationAgent = this.findAgentByType('correlation'); if (!correlationAgent) { throw new Error('Correlation agent not available'); } this.logger.info('Detecting campaigns through orchestrator', { criteria }); const response = await correlationAgent.sendRequest(correlationAgent.getId(), 'detect_campaigns', criteria); if (!response.success) { throw new Error(`Detect campaigns failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async analyzeCaseSimilarity(caseId1, caseId2) { const correlationAgent = this.findAgentByType('correlation'); if (!correlationAgent) { throw new Error('Correlation agent not available'); } this.logger.info('Analyzing case similarity through orchestrator', { caseId1, caseId2 }); const response = await correlationAgent.sendRequest(correlationAgent.getId(), 'analyze_case_similarity', { caseId1, caseId2 }); if (!response.success) { throw new Error(`Analyze case similarity failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async detectLateralMovement(caseId, timeRange) { const networkAgent = this.findAgentByType('network'); if (!networkAgent) { throw new Error('Network agent not available'); } this.logger.info('Detecting lateral movement through orchestrator', { caseId, timeRange }); const response = await networkAgent.sendRequest(networkAgent.getId(), 'detect_lateral_movement', { caseId, timeRange }); if (!response.success) { throw new Error(`Detect lateral movement failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async detectC2Communication(caseId, timeRange) { const networkAgent = this.findAgentByType('network'); if (!networkAgent) { throw new Error('Network agent not available'); } this.logger.info('Detecting C2 communication through orchestrator', { caseId, timeRange }); const response = await networkAgent.sendRequest(networkAgent.getId(), 'detect_c2_communication', { caseId, timeRange }); if (!response.success) { throw new Error(`Detect C2 communication failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async huntNetworkThreats(query, timeRange) { const networkAgent = this.findAgentByType('network'); if (!networkAgent) { throw new Error('Network agent not available'); } this.logger.info('Hunting network threats through orchestrator', { query, timeRange }); const response = await networkAgent.sendRequest(networkAgent.getId(), 'hunt_network_threats', { query, timeRange }); if (!response.success) { throw new Error(`Hunt network threats failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async getWorkflowStatus(workflowId) { const hubAgent = this.findAgentByType('hub'); if (!hubAgent) { throw new Error('Hub agent not available'); } this.logger.info('Getting workflow status through orchestrator', { workflowId }); const response = await hubAgent.sendRequest(hubAgent.getId(), 'get_workflow_status', { workflowId }); if (!response.success) { throw new Error(`Get workflow status failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async cancelWorkflow(workflowId) { const hubAgent = this.findAgentByType('hub'); if (!hubAgent) { throw new Error('Hub agent not available'); } this.logger.info('Canceling workflow through orchestrator', { workflowId }); const response = await hubAgent.sendRequest(hubAgent.getId(), 'cancel_workflow', { workflowId }); if (!response.success) { throw new Error(`Cancel workflow failed: ${response.error?.message || 'Unknown error'}`); } return response.result; } async initializeAgents() { this.logger.info('Initializing agents...'); const stellarConfig = { apiUrl: this.config.stellar.apiUrl, apiToken: this.config.stellar.apiToken }; // Initialize Hub Agent if (this.config.agents.hub.enabled) { await this.createHubAgent(stellarConfig); } // Initialize Investigation Agent if (this.config.agents.investigation.enabled) { await this.createInvestigationAgent(stellarConfig); } // Initialize Correlation Agent if (this.config.agents.correlation.enabled) { await this.createCorrelationAgent(stellarConfig); } // Initialize Network Agent if (this.config.agents.network.enabled) { await this.createNetworkAgent(stellarConfig); } this.logger.info(`Initialized ${this.agents.size} agents`); } async createHubAgent(stellarConfig) { const metadata = createHubAgentMetadata(); const config = { ...stellarConfig, ...this.config.agents.hub.config }; const agent = new HubAgent(metadata, this.registry, this.channel, this.logger, this.metrics, config); await agent.initialize(); this.agents.set(this.getAgentKey(metadata.id), agent); this.hubAgent = agent; this.logger.info('Hub agent created and initialized', { agentId: metadata.id }); } async createInvestigationAgent(stellarConfig) { const metadata = createInvestigationAgentMetadata(); const config = { ...stellarConfig, ...this.config.agents.investigation.config }; const agent = new InvestigationAgent(metadata, this.registry, this.channel, this.logger, this.metrics, config); await agent.initialize(); this.agents.set(this.getAgentKey(metadata.id), agent); this.logger.info('Investigation agent created and initialized', { agentId: metadata.id }); } async createCorrelationAgent(stellarConfig) { const metadata = createCorrelationAgentMetadata(); const config = { ...stellarConfig, ...this.config.agents.correlation.config }; const agent = new CorrelationAgent(metadata, this.registry, this.channel, this.logger, this.metrics, config); await agent.initialize(); this.agents.set(this.getAgentKey(metadata.id), agent); this.logger.info('Correlation agent created and initialized', { agentId: metadata.id }); } async createNetworkAgent(stellarConfig) { const metadata = createNetworkAnalysisAgentMetadata(); const config = { ...stellarConfig, ...this.config.agents.network.config }; const agent = new NetworkAnalysisAgent(metadata, this.registry, this.channel, this.logger, this.metrics, config); await agent.initialize(); this.agents.set(this.getAgentKey(metadata.id), agent); this.logger.info('Network agent created and initialized', { agentId: metadata.id }); } async startAgents() { this.logger.info('Starting all agents...'); const startPromises = Array.from(this.agents.values()).map(agent => agent.start()); await Promise.all(startPromises); this.logger.info('All agents started successfully'); } async stopAgents() { this.logger.info('Stopping all agents...'); const stopPromises = Array.from(this.agents.values()).map(agent => agent.stop()); await Promise.all(stopPromises); this.logger.info('All agents stopped successfully'); } setupMonitoring() { this.logger.info('Setting up monitoring...'); // Set up periodic health checks setInterval(() => { this.performHealthCheck().catch(error => { this.logger.error('Health check failed', { error }); }); }, 30000); // Every 30 seconds // Set up metrics collection if (this.config.metrics.enabled) { setInterval(() => { this.collectMetrics().catch(error => { this.logger.error('Metrics collection failed', { error }); }); }, this.config.metrics.interval || 60000); // Every minute } // Set up event monitoring this.registry.subscribeToEvents((event) => { this.logger.debug('Agent event received', { event }); this.emit('agent_event', event); }); this.logger.info('Monitoring setup completed'); } async performHealthCheck() { const agentStatuses = await this.registry.getAllAgentStatuses(); for (const status of agentStatuses) { if (status.health === AgentHealth.CRITICAL) { this.logger.warn('Agent in critical state', { agentId: status.id }); this.emit('agent_critical', status); } } } async collectMetrics() { const metrics = await this.getAgentMetrics(); // Emit metrics for external monitoring systems this.emit('metrics_collected', metrics); // Log key metrics this.logger.debug('Metrics collected', { totalAgents: metrics.registry.totalAgents, activeAgents: metrics.registry.activeAgents, totalRequests: metrics.registry.totalRequests }); } async getAgentSpecificMetrics() { const agentMetrics = {}; for (const [key, agent] of this.agents) { const status = agent.getStatus(); agentMetrics[key] = { state: status.state, health: status.health, uptime: status.uptime, activeRequests: status.activeRequests, totalRequests: status.totalRequests, errors: status.errors }; } return agentMetrics; } findAgentByType(type) { for (const [key, agent] of this.agents) { if (agent.getId().type === type) { return agent; } } return null; } getAgentKey(agentId) { return `${agentId.type}:${agentId.instance}:${agentId.uuid}`; } } // Example usage and configuration export function createDefaultConfig() { const apiUrl = process.env['STELLAR_API_URL']; const apiToken = process.env['STELLAR_API_TOKEN']; if (!apiUrl || !apiToken) { console.warn('Warning: STELLAR_API_URL and/or STELLAR_API_TOKEN environment variables are not set'); console.warn('Orchestrator will not function properly without proper configuration'); console.warn('Please set these variables in your MCP client configuration or environment'); } return { stellar: { apiUrl: apiUrl || '', apiToken: apiToken || '' }, agents: { hub: { enabled: true, config: { maxConcurrentRequests: 100, requestTimeout: 30000, loadBalancing: 'least-busy' } }, investigation: { enabled: true, config: { requestTimeout: 60000, maxRetries: 3 } }, correlation: { enabled: true, config: { correlationThreshold: 0.7, timeWindowHours: 168, enableCampaignDetection: true } }, network: { enabled: true, config: { analysisTimeout: 300000, maxConcurrentAnalysis: 10, enableDeepPacketAnalysis: true } } }, logging: { level: 'info', enableConsole: true, enableFile: false, filePath: './logs/orchestrator.log' }, metrics: { enabled: true, interval: 60000 } }; } // Main entry point for testing export async function main() { const config = createDefaultConfig(); const orchestrator = new MultiAgentOrchestrator(config); try { await orchestrator.start(); // Example workflow execution console.log('=== Multi-Agent System Started ==='); console.log('Available operations:'); console.log('1. investigateCase(caseId)'); console.log('2. correlateCase(caseId)'); console.log('3. analyzeNetwork(caseId, timeRange)'); console.log('4. executeWorkflow(workflowId, input)'); console.log('5. getStatus()'); console.log('6. getAgentMetrics()'); // Keep the process alive process.on('SIGINT', async () => { console.log('\nShutting down...'); await orchestrator.stop(); process.exit(0); }); } catch (error) { console.error('Failed to start orchestrator:', error); process.exit(1); } } // Run if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { main().catch(console.error); } //# sourceMappingURL=orchestrator.js.map