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
JavaScript
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