UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

460 lines (459 loc) 18.8 kB
/** * Skill-based MCP Selection System * Automatically selects MCP servers based on agent skills and requirements */ const fs = require('fs').promises; const path = require('path'); const AgentTokenManager = require('../cli/agent-token-manager.js'); let SkillMCPSelector = class SkillMCPSelector { constructor(options = {}){ this.tokenManager = new AgentTokenManager(options); this.agentConfigPath = options.agentConfigPath || './config/agent-whitelist.json'; this.skillConfigPath = options.skillConfigPath || './config/skill-requirements.json'; this.mcpServersPath = options.mcpServersPath || './config/mcp-servers.json'; this.agentWhitelist = new Map(); this.skillRequirements = new Map(); this.mcpServers = new Map(); } async initialize() { await this.tokenManager.initialize(); await this.loadConfigurations(); console.log('[SkillMCPSelector] Initialized successfully'); } async loadConfigurations() { // Load agent whitelist const agentConfig = await this.loadJson(this.agentConfigPath); this.agentWhitelist.clear(); for (const agent of agentConfig.agents){ this.agentWhitelist.set(agent.type, agent); } // Load skill requirements const skillConfig = await this.loadJson(this.skillConfigPath); this.skillRequirements.clear(); for (const [tool, requirements] of Object.entries(skillConfig.tools)){ this.skillRequirements.set(tool, requirements); } // Load MCP server configurations try { const mcpConfig = await this.loadJson(this.mcpServersPath); for (const [name, config] of Object.entries(mcpConfig.servers)){ this.mcpServers.set(name, config); } } catch (error) { console.warn('[SkillMCPSelector] MCP servers config not found, using defaults'); this.initializeDefaultMCPServers(); } } async loadJson(filePath) { const resolvedPath = path.resolve(filePath); const content = await fs.readFile(resolvedPath, 'utf8'); return JSON.parse(content); } initializeDefaultMCPServers() { // Default MCP server configurations const defaultServers = { 'playwright': { name: 'playwright', displayName: 'Playwright Browser Automation', description: 'Browser automation and screenshot capabilities', containerImage: 'claude-flow-novice:mcp-playwright', tools: [ 'take_screenshot', 'search_google', 'navigate_and_interact' ], skills: [ 'browser-automation', 'screenshot-capture', 'web-interaction' ], resourceRequirements: { memoryMB: 1024, cpuUnits: 2 } }, 'redis': { name: 'redis', displayName: 'Redis Database', description: 'Redis key-value store operations', containerImage: 'claude-flow-novice:mcp-redis', tools: [ 'redis_get', 'redis_set', 'redis_keys' ], skills: [ 'redis-operations', 'cache-management' ], resourceRequirements: { memoryMB: 256, cpuUnits: 1 } }, 'postgres': { name: 'postgres', displayName: 'PostgreSQL Database', description: 'PostgreSQL database operations', containerImage: 'claude-flow-novice:mcp-postgres', tools: [ 'postgres_query', 'postgres_schema', 'postgres_migrate' ], skills: [ 'database-design', 'sql-operations' ], resourceRequirements: { memoryMB: 512, cpuUnits: 2 } }, 'security-scanner': { name: 'security-scanner', displayName: 'Security Scanner', description: 'Security vulnerability scanning and analysis', containerImage: 'claude-flow-novice:mcp-security', tools: [ 'security_scan', 'vulnerability_check', 'compliance_validate' ], skills: [ 'security-auditing', 'vulnerability-scanning' ], resourceRequirements: { memoryMB: 1536, cpuUnits: 4 } } }; for (const [name, config] of Object.entries(defaultServers)){ this.mcpServers.set(name, config); } } /** * Determine which MCP servers an agent needs based on their skills */ selectMCPServers(agentType, agentSkills = null) { const agentConfig = this.agentWhitelist.get(agentType); if (!agentConfig) { throw new Error(`Unknown agent type: ${agentType}`); } // Use provided skills or fall back to agent config const skills = agentSkills || agentConfig.skills; // Determine required MCP servers based on skills const requiredMCPServers = new Set(); const skillToMCPServerMap = this.getSkillToMCPServerMapping(); // Map skills to required MCP servers for (const skill of skills){ if (skillToMCPServerMap.has(skill)) { const servers = skillToMCPServerMap.get(skill); servers.forEach((server)=>requiredMCPServers.add(server)); } } // Add explicitly allowed MCP servers from agent config if (agentConfig.allowedMcpServers) { agentConfig.allowedMcpServers.forEach((server)=>requiredMCPServers.add(server)); } // Convert to array and sort by priority const selectedServers = Array.from(requiredMCPServers).filter((server)=>this.mcpServers.has(server)).sort((a, b)=>{ const priorityA = this.mcpServers.get(a).priority || 999; const priorityB = this.mcpServers.get(b).priority || 999; return priorityA - priorityB; }); return { agentType, agentSkills: skills, selectedMCPServers, serverDetails: selectedServers.map((server)=>this.mcpServers.get(server)), totalMemoryRequired: selectedServers.reduce((sum, server)=>sum + (this.mcpServers.get(server).resourceRequirements?.memoryMB || 512), 0), totalCPURequired: selectedServers.reduce((sum, server)=>sum + (this.mcpServers.get(server).resourceRequirements?.cpuUnits || 1), 0) }; } /** * Get mapping from skills to required MCP servers */ getSkillToMCPServerMapping() { const skillMap = new Map(); // Build mapping from MCP server configurations for (const [serverName, serverConfig] of this.mcpServers.entries()){ for (const skill of serverConfig.skills || []){ if (!skillMap.has(skill)) { skillMap.set(skill, new Set()); } skillMap.get(skill).add(serverName); } } // Also add mappings from tool requirements for (const [toolName, toolConfig] of this.skillRequirements.entries()){ for (const skill of toolConfig.requiredSkills || []){ // Find MCP servers that provide this tool for (const [serverName, serverConfig] of this.mcpServers.entries()){ if (serverConfig.tools?.includes(toolName)) { if (!skillMap.has(skill)) { skillMap.set(skill, new Set()); } skillMap.get(skill).add(serverName); } } } } return skillMap; } /** * Generate tokens for selected MCP servers */ async generateMCPTokens(agentType, selectedServers, options = {}) { const tokens = []; for (const serverName of selectedServers){ try { const serverConfig = this.mcpServers.get(serverName); if (!serverConfig) { console.warn(`[SkillMCPSelector] Server configuration not found: ${serverName}`); continue; } // Generate server-specific token const tokenData = await this.tokenManager.registerAgentToken(agentType, { expiresIn: options.expiresIn || '24h', description: `Token for ${serverConfig.displayName}`, createdBy: 'skill-mcp-selector', mcpServer: serverName }); tokens.push({ serverName, displayName: serverConfig.displayName, token: tokenData.token, expiresAt: tokenData.expiresAt, containerImage: serverConfig.containerImage, connectionInfo: this.generateConnectionInfo(serverName, serverConfig, tokenData.token) }); console.log(`[SkillMCPSelector] Generated token for ${agentType}${serverName}`); } catch (error) { console.error(`[SkillMCPSelector] Failed to generate token for ${serverName}:`, error); } } return tokens; } /** * Generate connection information for MCP server */ generateConnectionInfo(serverName, serverConfig, token) { const baseInfo = { serverName, token, authentication: { type: 'token-based', header: 'x-agent-token', agentTypeHeader: 'x-agent-type' } }; // Add server-specific connection details if (serverConfig.connectionType === 'docker') { return { ...baseInfo, type: 'docker-container', containerName: `mcp-${serverName}`, containerImage: serverConfig.containerImage, dockerArgs: [ 'run', '--rm', '--init', '--name', `mcp-${serverName}`, '--memory', `${serverConfig.resourceRequirements?.memoryMB || 512}m`, '--cpus', `${serverConfig.resourceRequirements?.cpuUnits || 1}`, '-e', `MCP_SERVER=${serverName}`, '-e', `MCP_AUTH_REQUIRED=true`, // FIX: Default to 'localhost' for host execution, Docker deployments should set CFN_REDIS_HOST explicitly '-e', `MCP_REDIS_URL=${process.env.CFN_REDIS_URL || process.env.MCP_REDIS_URL || `redis://${process.env.CFN_REDIS_HOST || 'localhost'}:${process.env.CFN_REDIS_PORT || 6379}`}`, serverConfig.containerImage, 'node', `/app/mcp-${serverName}-server.js` ] }; } else { return { ...baseInfo, type: 'http-endpoint', url: serverConfig.url || `http://localhost:${3000 + this.mcpServers.size}`, headers: { 'Content-Type': 'application/json', 'x-agent-token': token, 'x-agent-type': 'dynamic' } }; } } /** * Get complete MCP configuration for an agent */ async getAgentMCPConfiguration(agentType, agentSkills = null, options = {}) { try { // Select required MCP servers const selection = this.selectMCPServers(agentType, agentSkills); // Generate tokens for selected servers const tokens = await this.generateMCPTokens(agentType, selection.selectedMCPServers, options); return { agentType, agentSkills: selection.agentSkills, mcpConfiguration: { mcpServers: tokens.reduce((config, tokenInfo)=>{ config[tokenInfo.serverName] = { command: 'docker', args: tokenInfo.connectionInfo.dockerArgs, env: { 'MCP_SERVER': tokenInfo.serverName, 'MCP_AUTH_REQUIRED': 'true', 'AGENT_TOKEN': tokenInfo.token } }; return config; }, {}) }, selection, tokens, resourceSummary: { totalMemoryMB: selection.totalMemoryRequired, totalCPUUnits: selection.totalCPUUnits, serverCount: selection.selectedMCPServers.length } }; } catch (error) { console.error(`[SkillMCPSelector] Failed to get MCP configuration for ${agentType}:`, error); throw error; } } /** * Generate Docker Compose configuration for agent with MCP servers */ async generateDockerComposeConfiguration(agentType, agentSkills = null, options = {}) { const config = await this.getAgentMCPConfiguration(agentType, agentSkills, options); const dockerCompose = { version: '3.8', services: {}, networks: { 'mcp-network': { driver: 'bridge' } } }; // Add agent service dockerCompose.services[`${agentType}-agent`] = { image: 'claude-flow-novice:agent-container', container_name: `agent-${agentType}-${Date.now()}`, networks: [ 'mcp-network' ], environment: { 'AGENT_TYPE': agentType, 'AGENT_MODE': 'containerized', 'MCP_AUTH_ENABLED': 'true', 'REDIS_URL': process.env.MCP_REDIS_URL || 'redis://redis:6379' }, volumes: [ '${PWD}/.claude:/app/.claude:ro', '${PWD}/screenshots:/app/screenshots' ], mem_limit: `${config.resourceSummary.totalMemoryMB + 512}m`, depends_on: config.selection.selectedMCPServers.map((server)=>`mcp-${server}`).join(' ') }; // Add MCP server services for (const tokenInfo of config.tokens){ const serverName = `mcp-${tokenInfo.serverName}`; const serverConfig = this.mcpServers.get(tokenInfo.serverName); dockerCompose.services[serverName] = { image: tokenInfo.containerImage, container_name: `${serverName}-${Date.now()}`, networks: [ 'mcp-network' ], environment: { 'MCP_SERVER': tokenInfo.serverName, 'MCP_AUTH_REQUIRED': 'true', 'MCP_REDIS_URL': process.env.MCP_REDIS_URL || 'redis://redis:6379', 'AGENT_TOKEN': tokenInfo.token }, volumes: [ '${PWD}/screenshots:/app/screenshots' ], mem_limit: `${serverConfig.resourceRequirements?.memoryMB || 512}m`, cpus: `${(serverConfig.resourceRequirements?.cpuUnits || 1) / 4}` }; } // Add Redis service if not present if (!dockerCompose.services.redis) { dockerCompose.services.redis = { image: 'redis:7-alpine', container_name: 'mcp-redis', networks: [ 'mcp-network' ], volumes: [ 'redis-data:/data' ], mem_limit: '256m' }; dockerCompose.volumes = { 'redis-data': {} }; } return dockerCompose; } /** * Get statistics about skill-MCP mappings */ getStatistics() { const skillMap = this.getSkillToMCPServerMapping(); return { totalAgents: this.agentWhitelist.size, totalMCPServers: this.mcpServers.size, totalSkills: skillMap.size, agentTypes: Array.from(this.agentWhitelist.keys()), mcpServerNames: Array.from(this.mcpServers.keys()), skillCoverage: Array.from(skillMap.entries()).map(([skill, servers])=>({ skill, requiredServers: Array.from(servers), serverCount: servers.size })) }; } /** * Validate agent-MCP configuration */ validateConfiguration(agentType, mcpConfiguration) { const agentConfig = this.agentWhitelist.get(agentType); if (!agentConfig) { return { valid: false, errors: [ `Unknown agent type: ${agentType}` ] }; } const errors = []; const warnings = []; // Check required MCP servers const requiredServers = agentConfig.allowedMcpServers || []; const configuredServers = Object.keys(mcpConfiguration.mcpServers || {}); for (const server of requiredServers){ if (!configuredServers.includes(server)) { errors.push(`Required MCP server missing: ${server}`); } } // Check for unauthorized servers for (const server of configuredServers){ if (!requiredServers.includes(server)) { warnings.push(`Potentially unauthorized MCP server: ${server}`); } } return { valid: errors.length === 0, errors, warnings }; } async shutdown() { await this.tokenManager.shutdown(); } }; module.exports = SkillMCPSelector; //# sourceMappingURL=skill-mcp-selector.js.map