devflow-ai
Version: 
Enterprise-grade AI agent orchestration with swarm management UI dashboard
860 lines (736 loc) • 24.1 kB
text/typescript
/**
 * MCP Integration Wrapper for Swarm System
 * 
 * This module provides a comprehensive wrapper around MCP tools to enable
 * seamless integration with the swarm orchestration system. It handles
 * tool discovery, execution, error handling, and result aggregation.
 */
import { EventEmitter } from 'node:events';
import { Logger } from '../core/logger.js';
import { generateId } from '../utils/helpers.js';
import { createClaudeFlowTools } from '../mcp/claude-flow-tools.js';
import { createRuvSwarmTools } from '../mcp/ruv-swarm-tools.js';
import type { MCPTool, MCPContext } from '../utils/types.js';
import type { AdvancedSwarmOrchestrator } from './advanced-orchestrator.js';
import {
  SwarmAgent,
  SwarmTask,
  TaskResult,
  SwarmExecutionContext,
  AgentState,
  TaskDefinition,
} from './types.js';
export interface MCPToolExecutionResult {
  success: boolean;
  result?: any;
  error?: string;
  duration: number;
  toolName: string;
  agentId: string;
  taskId?: string;
  metadata: {
    timestamp: Date;
    executionId: string;
    attempts: number;
    resourcesUsed?: any;
  };
}
export interface MCPToolRegistry {
  tools: Map<string, MCPTool>;
  categories: Map<string, string[]>;
  capabilities: Map<string, string[]>;
  permissions: Map<string, string[]>;
}
export interface MCPExecutionContext extends MCPContext {
  orchestrator: AdvancedSwarmOrchestrator;
  agent: SwarmAgent;
  task?: SwarmTask;
  swarmId: string;
  executionId: string;
  timeout: number;
  maxRetries: number;
}
export interface MCPIntegrationConfig {
  enableClaudeFlowTools: boolean;
  enableRuvSwarmTools: boolean;
  enableCustomTools: boolean;
  toolTimeout: number;
  maxRetries: number;
  enableCaching: boolean;
  cacheTimeout: number;
  enableMetrics: boolean;
  enableLogging: boolean;
  enableErrorRecovery: boolean;
  parallelExecution: boolean;
  maxConcurrentTools: number;
}
export class MCPIntegrationWrapper extends EventEmitter {
  private logger: Logger;
  private config: MCPIntegrationConfig;
  private toolRegistry: MCPToolRegistry;
  private executionCache: Map<string, MCPToolExecutionResult> = new Map();
  private activeExecutions: Map<string, AbortController> = new Map();
  private metrics: MCPIntegrationMetrics;
  constructor(config: Partial<MCPIntegrationConfig> = {}) {
    super();
    
    this.logger = new Logger('MCPIntegrationWrapper');
    this.config = this.createDefaultConfig(config);
    this.toolRegistry = this.initializeToolRegistry();
    this.metrics = this.initializeMetrics();
    this.setupEventHandlers();
  }
  /**
   * Initialize the MCP integration wrapper
   */
  async initialize(): Promise<void> {
    this.logger.info('Initializing MCP integration wrapper...');
    try {
      // Register Claude Flow tools
      if (this.config.enableClaudeFlowTools) {
        await this.registerClaudeFlowTools();
      }
      // Register ruv-swarm tools
      if (this.config.enableRuvSwarmTools) {
        await this.registerRuvSwarmTools();
      }
      // Start cache cleanup if enabled
      if (this.config.enableCaching) {
        this.startCacheCleanup();
      }
      this.logger.info('MCP integration wrapper initialized successfully', {
        totalTools: this.toolRegistry.tools.size,
        categories: this.toolRegistry.categories.size,
        capabilities: this.toolRegistry.capabilities.size,
      });
      this.emit('initialized', {
        toolCount: this.toolRegistry.tools.size,
        config: this.config,
      });
    } catch (error) {
      this.logger.error('Failed to initialize MCP integration wrapper', error);
      throw error;
    }
  }
  /**
   * Shutdown the wrapper gracefully
   */
  async shutdown(): Promise<void> {
    this.logger.info('Shutting down MCP integration wrapper...');
    try {
      // Cancel all active executions
      for (const [executionId, controller] of this.activeExecutions) {
        controller.abort();
        this.logger.debug('Cancelled execution', { executionId });
      }
      this.activeExecutions.clear();
      // Clear cache if needed
      this.executionCache.clear();
      this.logger.info('MCP integration wrapper shut down successfully');
      this.emit('shutdown');
    } catch (error) {
      this.logger.error('Error during MCP wrapper shutdown', error);
      throw error;
    }
  }
  /**
   * Execute an MCP tool within a swarm context
   */
  async executeTool(
    toolName: string,
    input: any,
    context: MCPExecutionContext
  ): Promise<MCPToolExecutionResult> {
    const executionId = generateId('mcp-execution');
    const startTime = performance.now();
    this.logger.info('Executing MCP tool', {
      toolName,
      executionId,
      agentId: context.agent.id,
      taskId: context.task?.id,
      swarmId: context.swarmId,
    });
    try {
      // Check if tool exists
      const tool = this.toolRegistry.tools.get(toolName);
      if (!tool) {
        throw new Error(`Tool not found: ${toolName}`);
      }
      // Check cache if enabled
      if (this.config.enableCaching) {
        const cached = await this.getCachedResult(toolName, input, context);
        if (cached) {
          this.logger.debug('Using cached result', { toolName, executionId });
          return cached;
        }
      }
      // Create abort controller for timeout
      const abortController = new AbortController();
      this.activeExecutions.set(executionId, abortController);
      // Set up timeout
      const timeoutHandle = setTimeout(() => {
        abortController.abort();
      }, context.timeout || this.config.toolTimeout);
      try {
        // Execute tool with retry logic
        const result = await this.executeWithRetry(
          tool,
          input,
          context,
          executionId,
          abortController.signal
        );
        clearTimeout(timeoutHandle);
        const duration = performance.now() - startTime;
        const executionResult: MCPToolExecutionResult = {
          success: true,
          result,
          duration,
          toolName,
          agentId: context.agent.id,
          taskId: context.task?.id,
          metadata: {
            timestamp: new Date(),
            executionId,
            attempts: 1,
          },
        };
        // Cache result if enabled
        if (this.config.enableCaching) {
          await this.cacheResult(toolName, input, context, executionResult);
        }
        // Update metrics
        this.updateMetrics(executionResult);
        this.logger.info('MCP tool executed successfully', {
          toolName,
          executionId,
          duration,
        });
        this.emit('tool:executed', executionResult);
        return executionResult;
      } finally {
        clearTimeout(timeoutHandle);
        this.activeExecutions.delete(executionId);
      }
    } catch (error) {
      const duration = performance.now() - startTime;
      const executionResult: MCPToolExecutionResult = {
        success: false,
        error: error instanceof Error ? error.message : String(error),
        duration,
        toolName,
        agentId: context.agent.id,
        taskId: context.task?.id,
        metadata: {
          timestamp: new Date(),
          executionId,
          attempts: 1,
        },
      };
      this.updateMetrics(executionResult);
      this.logger.error('MCP tool execution failed', {
        toolName,
        executionId,
        error: executionResult.error,
        duration,
      });
      this.emit('tool:failed', executionResult);
      return executionResult;
    }
  }
  /**
   * Execute multiple tools in parallel
   */
  async executeToolsParallel(
    toolExecutions: Array<{
      toolName: string;
      input: any;
      context: MCPExecutionContext;
    }>
  ): Promise<MCPToolExecutionResult[]> {
    if (!this.config.parallelExecution) {
      // Execute sequentially if parallel execution is disabled
      const results: MCPToolExecutionResult[] = [];
      for (const execution of toolExecutions) {
        const result = await this.executeTool(
          execution.toolName,
          execution.input,
          execution.context
        );
        results.push(result);
      }
      return results;
    }
    this.logger.info('Executing tools in parallel', {
      toolCount: toolExecutions.length,
      maxConcurrent: this.config.maxConcurrentTools,
    });
    // Limit concurrent executions
    const semaphore = new Semaphore(this.config.maxConcurrentTools);
    
    const promises = toolExecutions.map(async (execution) => {
      await semaphore.acquire();
      try {
        return await this.executeTool(
          execution.toolName,
          execution.input,
          execution.context
        );
      } finally {
        semaphore.release();
      }
    });
    const results = await Promise.allSettled(promises);
    
    return results.map((result, index) => {
      if (result.status === 'fulfilled') {
        return result.value;
      } else {
        // Create error result
        return {
          success: false,
          error: result.reason instanceof Error ? result.reason.message : String(result.reason),
          duration: 0,
          toolName: toolExecutions[index].toolName,
          agentId: toolExecutions[index].context.agent.id,
          taskId: toolExecutions[index].context.task?.id,
          metadata: {
            timestamp: new Date(),
            executionId: generateId('failed-execution'),
            attempts: 1,
          },
        };
      }
    });
  }
  /**
   * Get available tools with filtering options
   */
  getAvailableTools(options: {
    category?: string;
    capability?: string;
    agent?: SwarmAgent;
  } = {}): MCPTool[] {
    let tools = Array.from(this.toolRegistry.tools.values());
    // Filter by category
    if (options.category) {
      const categoryTools = this.toolRegistry.categories.get(options.category) || [];
      tools = tools.filter(tool => categoryTools.includes(tool.name));
    }
    // Filter by capability
    if (options.capability) {
      const capabilityTools = this.toolRegistry.capabilities.get(options.capability) || [];
      tools = tools.filter(tool => capabilityTools.includes(tool.name));
    }
    // Filter by agent permissions
    if (options.agent) {
      tools = tools.filter(tool => this.hasPermission(tool, options.agent!));
    }
    return tools;
  }
  /**
   * Get tool information
   */
  getToolInfo(toolName: string): MCPTool | null {
    return this.toolRegistry.tools.get(toolName) || null;
  }
  /**
   * Get integration metrics
   */
  getMetrics(): MCPIntegrationMetrics {
    return {
      ...this.metrics,
      cacheHitRate: this.calculateCacheHitRate(),
      averageExecutionTime: this.calculateAverageExecutionTime(),
      toolUsageDistribution: this.calculateToolUsageDistribution(),
    };
  }
  /**
   * Create MCP execution context for swarm operations
   */
  createExecutionContext(
    orchestrator: AdvancedSwarmOrchestrator,
    agent: SwarmAgent,
    swarmId: string,
    task?: SwarmTask
  ): MCPExecutionContext {
    return {
      sessionId: generateId('mcp-session'),
      orchestrator,
      agent,
      task,
      swarmId,
      executionId: generateId('mcp-execution'),
      timeout: this.config.toolTimeout,
      maxRetries: this.config.maxRetries,
    };
  }
  // Private methods
  private async registerClaudeFlowTools(): Promise<void> {
    this.logger.info('Registering Claude Flow tools...');
    
    const claudeFlowTools = createClaudeFlowTools(this.logger);
    
    for (const tool of claudeFlowTools) {
      this.toolRegistry.tools.set(tool.name, tool);
      
      // Categorize tool
      const category = this.categorizeClaudeFlowTool(tool.name);
      if (!this.toolRegistry.categories.has(category)) {
        this.toolRegistry.categories.set(category, []);
      }
      this.toolRegistry.categories.get(category)!.push(tool.name);
      
      // Add capabilities
      const capabilities = this.extractCapabilities(tool);
      for (const capability of capabilities) {
        if (!this.toolRegistry.capabilities.has(capability)) {
          this.toolRegistry.capabilities.set(capability, []);
        }
        this.toolRegistry.capabilities.get(capability)!.push(tool.name);
      }
    }
    this.logger.info(`Registered ${claudeFlowTools.length} Claude Flow tools`);
  }
  private async registerRuvSwarmTools(): Promise<void> {
    this.logger.info('Registering ruv-swarm tools...');
    
    const ruvSwarmTools = createRuvSwarmTools(this.logger);
    
    for (const tool of ruvSwarmTools) {
      this.toolRegistry.tools.set(tool.name, tool);
      
      // Categorize tool
      const category = this.categorizeRuvSwarmTool(tool.name);
      if (!this.toolRegistry.categories.has(category)) {
        this.toolRegistry.categories.set(category, []);
      }
      this.toolRegistry.categories.get(category)!.push(tool.name);
      
      // Add capabilities
      const capabilities = this.extractCapabilities(tool);
      for (const capability of capabilities) {
        if (!this.toolRegistry.capabilities.has(capability)) {
          this.toolRegistry.capabilities.set(capability, []);
        }
        this.toolRegistry.capabilities.get(capability)!.push(tool.name);
      }
    }
    this.logger.info(`Registered ${ruvSwarmTools.length} ruv-swarm tools`);
  }
  private async executeWithRetry(
    tool: MCPTool,
    input: any,
    context: MCPExecutionContext,
    executionId: string,
    signal: AbortSignal
  ): Promise<any> {
    let lastError: Error | null = null;
    const maxRetries = context.maxRetries || this.config.maxRetries;
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        // Check if execution was aborted
        if (signal.aborted) {
          throw new Error('Execution aborted');
        }
        this.logger.debug('Executing tool attempt', {
          toolName: tool.name,
          executionId,
          attempt,
          maxRetries,
        });
        const result = await tool.handler(input, context);
        
        if (attempt > 1) {
          this.logger.info('Tool execution succeeded after retry', {
            toolName: tool.name,
            executionId,
            attempt,
          });
        }
        return result;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));
        
        this.logger.warn('Tool execution attempt failed', {
          toolName: tool.name,
          executionId,
          attempt,
          maxRetries,
          error: lastError.message,
        });
        // Don't retry on certain errors
        if (this.isNonRetryableError(lastError)) {
          break;
        }
        // Wait before retry (exponential backoff)
        if (attempt < maxRetries) {
          const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }
    throw lastError || new Error('Tool execution failed after all retries');
  }
  private isNonRetryableError(error: Error): boolean {
    const nonRetryablePatterns = [
      /not found/i,
      /invalid input/i,
      /permission denied/i,
      /unauthorized/i,
      /forbidden/i,
    ];
    return nonRetryablePatterns.some(pattern => pattern.test(error.message));
  }
  private async getCachedResult(
    toolName: string,
    input: any,
    context: MCPExecutionContext
  ): Promise<MCPToolExecutionResult | null> {
    const cacheKey = this.generateCacheKey(toolName, input, context);
    const cached = this.executionCache.get(cacheKey);
    
    if (cached) {
      const age = Date.now() - cached.metadata.timestamp.getTime();
      if (age < this.config.cacheTimeout) {
        this.metrics.cacheHits++;
        return cached;
      } else {
        // Remove expired entry
        this.executionCache.delete(cacheKey);
      }
    }
    this.metrics.cacheMisses++;
    return null;
  }
  private async cacheResult(
    toolName: string,
    input: any,
    context: MCPExecutionContext,
    result: MCPToolExecutionResult
  ): Promise<void> {
    const cacheKey = this.generateCacheKey(toolName, input, context);
    this.executionCache.set(cacheKey, result);
  }
  private generateCacheKey(
    toolName: string,
    input: any,
    context: MCPExecutionContext
  ): string {
    const inputHash = this.hashObject(input);
    const contextHash = this.hashObject({
      agentId: context.agent.id,
      swarmId: context.swarmId,
      taskId: context.task?.id,
    });
    
    return `${toolName}:${inputHash}:${contextHash}`;
  }
  private hashObject(obj: any): string {
    // Simple hash function for caching
    const str = JSON.stringify(obj, Object.keys(obj).sort());
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32bit integer
    }
    return hash.toString(36);
  }
  private hasPermission(tool: MCPTool, agent: SwarmAgent): boolean {
    // Check if agent has permission to use this tool
    const toolPermissions = this.toolRegistry.permissions.get(tool.name) || [];
    
    // If no specific permissions defined, allow all
    if (toolPermissions.length === 0) {
      return true;
    }
    // Check agent capabilities against tool permissions
    return agent.capabilities.some(capability => 
      toolPermissions.includes(capability)
    );
  }
  private categorizeClaudeFlowTool(toolName: string): string {
    if (toolName.includes('agents/')) return 'agent-management';
    if (toolName.includes('tasks/')) return 'task-management';
    if (toolName.includes('memory/')) return 'memory-management';
    if (toolName.includes('system/')) return 'system-monitoring';
    if (toolName.includes('config/')) return 'configuration';
    if (toolName.includes('workflow/')) return 'workflow-management';
    if (toolName.includes('terminal/')) return 'terminal-management';
    return 'general';
  }
  private categorizeRuvSwarmTool(toolName: string): string {
    if (toolName.includes('swarm_')) return 'swarm-lifecycle';
    if (toolName.includes('agent_')) return 'agent-management';
    if (toolName.includes('task_')) return 'task-orchestration';
    if (toolName.includes('memory_')) return 'memory-persistence';
    if (toolName.includes('neural_')) return 'neural-capabilities';
    if (toolName.includes('benchmark_')) return 'performance-benchmarking';
    return 'general';
  }
  private extractCapabilities(tool: MCPTool): string[] {
    const capabilities: string[] = [];
    
    // Extract capabilities from tool name and description
    const text = `${tool.name} ${tool.description}`.toLowerCase();
    
    const capabilityPatterns = [
      'agent', 'task', 'memory', 'system', 'config', 'workflow',
      'terminal', 'swarm', 'neural', 'benchmark', 'monitoring',
      'orchestration', 'coordination', 'analysis', 'research',
      'development', 'testing', 'documentation', 'optimization',
    ];
    for (const pattern of capabilityPatterns) {
      if (text.includes(pattern)) {
        capabilities.push(pattern);
      }
    }
    return capabilities.length > 0 ? capabilities : ['general'];
  }
  private updateMetrics(result: MCPToolExecutionResult): void {
    this.metrics.totalExecutions++;
    
    if (result.success) {
      this.metrics.successfulExecutions++;
    } else {
      this.metrics.failedExecutions++;
    }
    this.metrics.totalExecutionTime += result.duration;
    // Update tool-specific metrics
    if (!this.metrics.toolExecutions.has(result.toolName)) {
      this.metrics.toolExecutions.set(result.toolName, {
        count: 0,
        totalTime: 0,
        successCount: 0,
        failureCount: 0,
      });
    }
    const toolStats = this.metrics.toolExecutions.get(result.toolName)!;
    toolStats.count++;
    toolStats.totalTime += result.duration;
    
    if (result.success) {
      toolStats.successCount++;
    } else {
      toolStats.failureCount++;
    }
  }
  private calculateCacheHitRate(): number {
    const total = this.metrics.cacheHits + this.metrics.cacheMisses;
    return total > 0 ? this.metrics.cacheHits / total : 0;
  }
  private calculateAverageExecutionTime(): number {
    return this.metrics.totalExecutions > 0 
      ? this.metrics.totalExecutionTime / this.metrics.totalExecutions 
      : 0;
  }
  private calculateToolUsageDistribution(): Record<string, number> {
    const distribution: Record<string, number> = {};
    
    for (const [toolName, stats] of this.metrics.toolExecutions) {
      distribution[toolName] = stats.count;
    }
    return distribution;
  }
  private startCacheCleanup(): void {
    // Clean up expired cache entries every 5 minutes
    setInterval(() => {
      const now = Date.now();
      const expired: string[] = [];
      for (const [key, result] of this.executionCache) {
        const age = now - result.metadata.timestamp.getTime();
        if (age > this.config.cacheTimeout) {
          expired.push(key);
        }
      }
      expired.forEach(key => this.executionCache.delete(key));
      
      if (expired.length > 0) {
        this.logger.debug('Cleaned up expired cache entries', { 
          count: expired.length 
        });
      }
    }, 300000); // 5 minutes
  }
  private initializeToolRegistry(): MCPToolRegistry {
    return {
      tools: new Map(),
      categories: new Map(),
      capabilities: new Map(),
      permissions: new Map(),
    };
  }
  private initializeMetrics(): MCPIntegrationMetrics {
    return {
      totalExecutions: 0,
      successfulExecutions: 0,
      failedExecutions: 0,
      totalExecutionTime: 0,
      cacheHits: 0,
      cacheMisses: 0,
      toolExecutions: new Map(),
      cacheHitRate: 0,
      averageExecutionTime: 0,
      toolUsageDistribution: {},
    };
  }
  private createDefaultConfig(config: Partial<MCPIntegrationConfig>): MCPIntegrationConfig {
    return {
      enableClaudeFlowTools: true,
      enableRuvSwarmTools: true,
      enableCustomTools: true,
      toolTimeout: 30000, // 30 seconds
      maxRetries: 3,
      enableCaching: true,
      cacheTimeout: 300000, // 5 minutes
      enableMetrics: true,
      enableLogging: true,
      enableErrorRecovery: true,
      parallelExecution: true,
      maxConcurrentTools: 5,
      ...config,
    };
  }
  private setupEventHandlers(): void {
    this.on('tool:executed', (result) => {
      if (this.config.enableLogging) {
        this.logger.debug('Tool execution completed', {
          toolName: result.toolName,
          success: result.success,
          duration: result.duration,
        });
      }
    });
    this.on('tool:failed', (result) => {
      if (this.config.enableLogging) {
        this.logger.warn('Tool execution failed', {
          toolName: result.toolName,
          error: result.error,
          duration: result.duration,
        });
      }
    });
  }
}
// Supporting interfaces and classes
interface MCPIntegrationMetrics {
  totalExecutions: number;
  successfulExecutions: number;
  failedExecutions: number;
  totalExecutionTime: number;
  cacheHits: number;
  cacheMisses: number;
  toolExecutions: Map<string, {
    count: number;
    totalTime: number;
    successCount: number;
    failureCount: number;
  }>;
  cacheHitRate: number;
  averageExecutionTime: number;
  toolUsageDistribution: Record<string, number>;
}
class Semaphore {
  private permits: number;
  private waitQueue: Array<() => void> = [];
  constructor(permits: number) {
    this.permits = permits;
  }
  async acquire(): Promise<void> {
    if (this.permits > 0) {
      this.permits--;
      return Promise.resolve();
    }
    return new Promise<void>((resolve) => {
      this.waitQueue.push(resolve);
    });
  }
  release(): void {
    if (this.waitQueue.length > 0) {
      const resolve = this.waitQueue.shift()!;
      resolve();
    } else {
      this.permits++;
    }
  }
}
export default MCPIntegrationWrapper;