UNPKG

stellar-cyber-mcp-agents

Version:

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

217 lines 7.46 kB
import { EventEmitter } from 'eventemitter3'; import { AgentEventType, AgentState, AgentHealth } from '../types/agent.js'; export class InMemoryAgentRegistry extends EventEmitter { agents = new Map(); agentStatus = new Map(); capabilityIndex = new Map(); constructor() { super(); } async registerAgent(metadata) { const agentKey = this.getAgentKey(metadata.id); if (this.agents.has(agentKey)) { throw new Error(`Agent ${agentKey} is already registered`); } this.agents.set(agentKey, metadata); // Index capabilities for (const capability of metadata.capabilities) { if (!this.capabilityIndex.has(capability.name)) { this.capabilityIndex.set(capability.name, new Set()); } this.capabilityIndex.get(capability.name).add(agentKey); } // Initialize status const status = { id: metadata.id, state: AgentState.INITIALIZING, health: AgentHealth.HEALTHY, lastHeartbeat: new Date(), uptime: 0, activeRequests: 0, totalRequests: 0, errors: 0 }; this.agentStatus.set(agentKey, status); // Emit registration event const event = { id: crypto.randomUUID(), sourceAgentId: metadata.id, type: AgentEventType.AGENT_STARTED, data: { metadata }, timestamp: new Date() }; this.emit('agent_registered', event); } async unregisterAgent(agentId) { const agentKey = this.getAgentKey(agentId); const metadata = this.agents.get(agentKey); if (!metadata) { throw new Error(`Agent ${agentKey} is not registered`); } // Remove from capability index for (const capability of metadata.capabilities) { const capabilitySet = this.capabilityIndex.get(capability.name); if (capabilitySet) { capabilitySet.delete(agentKey); if (capabilitySet.size === 0) { this.capabilityIndex.delete(capability.name); } } } this.agents.delete(agentKey); this.agentStatus.delete(agentKey); // Emit unregistration event const event = { id: crypto.randomUUID(), sourceAgentId: agentId, type: AgentEventType.AGENT_STOPPED, data: { metadata }, timestamp: new Date() }; this.emit('agent_unregistered', event); } async getAgent(agentId) { const agentKey = this.getAgentKey(agentId); return this.agents.get(agentKey) || null; } async listAgents() { return Array.from(this.agents.values()); } async findAgentsByCapability(capability) { const agentKeys = this.capabilityIndex.get(capability); if (!agentKeys) { return []; } const agents = []; for (const agentKey of agentKeys) { const metadata = this.agents.get(agentKey); if (metadata) { agents.push(metadata); } } return agents; } async updateAgentStatus(status) { const agentKey = this.getAgentKey(status.id); if (!this.agents.has(agentKey)) { throw new Error(`Agent ${agentKey} is not registered`); } const previousStatus = this.agentStatus.get(agentKey); this.agentStatus.set(agentKey, status); // Emit status change event if state or health changed if (previousStatus && (previousStatus.state !== status.state || previousStatus.health !== status.health)) { const event = { id: crypto.randomUUID(), sourceAgentId: status.id, type: AgentEventType.AGENT_HEARTBEAT, data: { previousStatus, currentStatus: status }, timestamp: new Date() }; this.emit('agent_status_changed', event); } } async getAgentStatus(agentId) { const agentKey = this.getAgentKey(agentId); return this.agentStatus.get(agentKey) || null; } async getAllAgentStatuses() { return Array.from(this.agentStatus.values()); } subscribeToEvents(callback) { this.on('agent_registered', callback); this.on('agent_unregistered', callback); this.on('agent_status_changed', callback); } unsubscribeFromEvents(callback) { this.off('agent_registered', callback); this.off('agent_unregistered', callback); this.off('agent_status_changed', callback); } /** * Health check methods */ async performHealthCheck() { const statuses = await this.getAllAgentStatuses(); let healthy = 0; let unhealthy = 0; for (const status of statuses) { if (status.health === AgentHealth.HEALTHY) { healthy++; } else { unhealthy++; } } return { healthy, unhealthy, total: statuses.length }; } /** * Get agents by state */ async getAgentsByState(state) { const agents = []; for (const [agentKey, status] of this.agentStatus) { if (status.state === state) { const metadata = this.agents.get(agentKey); if (metadata) { agents.push(metadata); } } } return agents; } /** * Get agents by health */ async getAgentsByHealth(health) { const agents = []; for (const [agentKey, status] of this.agentStatus) { if (status.health === health) { const metadata = this.agents.get(agentKey); if (metadata) { agents.push(metadata); } } } return agents; } /** * Get registry statistics */ getRegistryStats() { return this.getAllAgentStatuses().then(statuses => { const activeAgents = statuses.filter(s => s.state === AgentState.READY).length; const totalUptime = statuses.reduce((sum, s) => sum + s.uptime, 0); const totalRequests = statuses.reduce((sum, s) => sum + s.totalRequests, 0); const totalErrors = statuses.reduce((sum, s) => sum + s.errors, 0); return { totalAgents: statuses.length, activeAgents, capabilities: this.capabilityIndex.size, averageUptime: statuses.length > 0 ? totalUptime / statuses.length : 0, totalRequests, totalErrors }; }); } getAgentKey(agentId) { return `${agentId.type}:${agentId.instance}:${agentId.uuid}`; } } export class DistributedAgentRegistry extends InMemoryAgentRegistry { // TODO: Implement distributed registry using Redis or similar // This would allow multiple hub agents to share the same registry // For now, we'll use the in-memory implementation constructor() { super(); // Future: Add Redis/database connection } } //# sourceMappingURL=agent-registry.js.map