UNPKG

claude-code-subagents-orchestrator

Version:

Claude Code Sub-agents Orchestrator - A powerful MCP server for orchestrating multiple AI sub-agents for complex task execution in Claude Code

1,172 lines (1,035 loc) 37.7 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { promises as fs } from 'fs'; import * as path from 'path'; import { homedir, platform } from 'os'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); /** * Claude Code Subagents Orchestrator MCP Server * Provides delegation tools for multi-agent coordination */ class SubagentsOrchestratorServer { constructor() { this.server = new Server( { name: 'claude-code-subagents-orchestrator', version: '1.0.3', }, { capabilities: { tools: {}, }, } ); // Initialize paths this.outputsDir = path.join(process.cwd(), 'specialist_outputs'); this.agentsDir = this.resolveAgentsDirectory(); this.taskRegistry = new Map(); // Store active tasks this.setupTools(); } /** * Cross-platform agents directory resolution */ resolveAgentsDirectory() { const home = homedir(); const isWSL = process.env.WSL_DISTRO_NAME !== undefined; if (isWSL) { // Check for Windows user profile via WSL const mountedCDrive = '/mnt/c'; try { const windowsUser = process.env.USER || 'Administrator'; const windowsPath = path.join(mountedCDrive, 'Users', windowsUser, '.claude', 'agents'); if (fs.existsSync(windowsPath)) { return windowsPath; } } catch (error) { console.error('Failed to resolve Windows path in WSL:', error); } } // Default to home directory return path.join(home, '.claude', 'agents'); } setupTools() { this.server.setRequestHandler( CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'listAgents': return await this.listAgents(args); case 'delegateTask': return await this.delegateTask(args); case 'getAgentCapabilities': return await this.getAgentCapabilities(args); case 'getTaskStatus': return await this.getTaskStatus(args); case 'listSpecialistOutputs': return await this.listSpecialistOutputs(args); case 'analyzeProjectState': return await this.analyzeProjectState(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { console.error(`Error in tool ${name}:`, error); return { error: { code: -32603, message: error.message } }; } }); this.server.setRequestHandler( ListToolsRequestSchema, async () => { return { tools: [ { name: 'listAgents', description: 'List all available specialist agents for delegation', inputSchema: { type: 'object', properties: {}, required: [] } }, { name: 'delegateTask', description: 'Delegate a task to a specialist agent', inputSchema: { type: 'object', properties: { agentType: { type: 'string', description: 'Type of specialist agent (frontend-developer, backend-architect, etc.)' }, task: { type: 'string', description: 'Detailed task description' }, context: { type: 'object', description: 'Additional context and requirements' } }, required: ['agentType', 'task'] } }, { name: 'getAgentCapabilities', description: 'Get detailed capabilities of a specific agent type', inputSchema: { type: 'object', properties: { agentType: { type: 'string', description: 'Type of specialist agent to query' } }, required: ['agentType'] } }, { name: 'getTaskStatus', description: 'Get status and results of a delegated task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'Task identifier returned from delegateTask' } }, required: ['taskId'] } }, { name: 'listSpecialistOutputs', description: 'List specialist output files and artifacts', inputSchema: { type: 'object', properties: { filter: { type: 'string', description: 'Optional filter pattern for outputs' }, agentType: { type: 'string', description: 'Filter by specific agent type' } }, required: [] } }, { name: 'analyzeProjectState', description: 'Analyze current project state for recovery and continuation', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Project path to analyze (optional)' } }, required: [] } } ] }; } ); } async listAgents(args) { try { // Read actual agent specifications from ~/.claude/agents/ const agents = []; try { const agentFiles = await fs.readdir(this.agentsDir); const mdFiles = agentFiles.filter(f => f.endsWith('.md')); for (const file of mdFiles) { try { const filePath = path.join(this.agentsDir, file); const content = await fs.readFile(filePath, 'utf-8'); // Parse agent metadata from markdown frontmatter const agentInfo = this.parseAgentMetadata(content, file); if (agentInfo) { agents.push(agentInfo); } } catch (error) { console.error(`Failed to read agent file ${file}:`, error); } } } catch (error) { console.error('Failed to read agents directory:', error); // Return default agents if directory doesn't exist return this.getDefaultAgents(); } // Sort agents by category and name agents.sort((a, b) => { if (a.category !== b.category) { return a.category.localeCompare(b.category); } return a.type.localeCompare(b.type); }); return { success: true, agents: agents.length > 0 ? agents : this.getDefaultAgents().agents, totalAgents: agents.length, agentsDirectory: this.agentsDir }; } catch (error) { console.error('Error listing agents:', error); return { success: false, error: error.message, agents: this.getDefaultAgents().agents }; } } async delegateTask(args) { const { agentType, task, context = {} } = args; const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; console.error(`[MCP] Delegating task to ${agentType}: ${task}`); try { // Ensure outputs directory exists await fs.mkdir(this.outputsDir, { recursive: true }); // Generate the delegation prompt in the required format const delegationPrompt = this.generateDelegationPrompt(agentType, task, context); // Create artifact filename const timestamp = new Date().toISOString().split('T')[0]; const taskSlug = this.generateTaskSlug(task); const artifactName = `${agentType}-${taskSlug}-${timestamp}.md`; const artifactPath = path.join(this.outputsDir, artifactName); // Get agent specification if available let agentSpec = null; try { const agentFiles = await fs.readdir(this.agentsDir); const agentFile = agentFiles.find(f => f.toLowerCase().includes(agentType.replace('-', '')) || f.toLowerCase() === `${agentType}.md` ); if (agentFile) { const content = await fs.readFile(path.join(this.agentsDir, agentFile), 'utf-8'); agentSpec = this.parseAgentMetadata(content, agentFile); } } catch (error) { console.error('Failed to load agent specification:', error); } // Create artifact content const artifactContent = `# Delegation to ${agentType} ## Task ${task} ## Context ${JSON.stringify(context, null, 2)} ## Generated Delegation Prompt \`\`\` ${delegationPrompt} \`\`\` ## Agent Specification ${agentSpec ? `- **Name**: ${agentSpec.name} - **Category**: ${agentSpec.category} - **Description**: ${agentSpec.description} - **Capabilities**: ${agentSpec.capabilities.join(', ')}` : 'Agent specification not available'} ## Task Details - **Task ID**: ${taskId} - **Status**: Delegated - **Created**: ${new Date().toISOString()} ## Expected Deliverables ${this.generateExpectedDeliverables(agentType, task, context)} --- *This delegation artifact was generated by the Claude Code Subagents Orchestrator*`; // Write artifact await fs.writeFile(artifactPath, artifactContent, 'utf-8'); // Register task in task registry this.taskRegistry.set(taskId, { taskId, agentType, task, context, artifactPath, delegationPrompt, status: 'delegated', createdAt: new Date(), lastUpdated: new Date() }); return { success: true, taskId, agentType, status: 'delegated', message: `Task successfully delegated to ${agentType}`, delegationPrompt, artifactPath, timestamp: new Date().toISOString(), context, next_steps: [ `Delegation artifact created at: ${artifactPath}`, 'Use getTaskStatus to monitor progress', 'Results will be available in specialist outputs' ] }; } catch (error) { console.error('Failed to delegate task:', error); return { success: false, error: error.message, taskId, agentType, status: 'failed', message: `Failed to delegate task: ${error.message}` }; } } /** * Generate delegation prompt in the required format */ generateDelegationPrompt(agentName, task, context) { // Generate the exact format required for delegation const prompt = `I need the ${agentName} sub-agent to ${task} ${agentName} sub-agent: ${this.formatDetailedInstructions(task, context)} Before completing this task, save your analysis/recommendations/deliverables as a persistent artifact in the /specialist_outputs/ folder. Use clear naming: ${agentName}-${this.generateTaskSlug(task)}-${new Date().toISOString().split('T')[0]}.md. Include in your artifact: work completed, current status, next steps needed, and dependencies for continuation.`; return prompt; } /** * Format detailed instructions with context */ formatDetailedInstructions(task, context) { let instructions = task; if (context.requirements) { instructions += `\n\nRequirements:\n${Array.isArray(context.requirements) ? context.requirements.map(r => `- ${r}`).join('\n') : context.requirements}`; } if (context.constraints) { instructions += `\n\nConstraints:\n${Array.isArray(context.constraints) ? context.constraints.map(c => `- ${c}`).join('\n') : context.constraints}`; } if (context.dependencies) { instructions += `\n\nDependencies:\n${Array.isArray(context.dependencies) ? context.dependencies.map(d => `- ${d}`).join('\n') : context.dependencies}`; } if (context.priority) { instructions += `\n\nPriority: ${context.priority}`; } return instructions; } /** * Generate task slug from task description */ generateTaskSlug(task) { return task .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .substring(0, 50); } /** * Generate expected deliverables based on agent type and task */ generateExpectedDeliverables(agentType, task, context) { const deliverables = { 'frontend-developer': [ 'Component implementation with proper TypeScript types', 'Styled components or CSS modules', 'Unit tests for components', 'Documentation of props and usage' ], 'backend-architect': [ 'API design and specification', 'Database schema if applicable', 'Implementation with proper error handling', 'Integration tests', 'API documentation' ], 'devops-engineer': [ 'Infrastructure configuration files', 'CI/CD pipeline configuration', 'Deployment scripts', 'Monitoring setup', 'Documentation of deployment process' ], 'test-automator': [ 'Test suite implementation', 'Test coverage report', 'Test execution results', 'Documentation of test scenarios' ], 'data-engineer': [ 'Data pipeline implementation', 'ETL scripts or configurations', 'Data quality checks', 'Performance metrics', 'Documentation of data flow' ], 'ai-engineer': [ 'Model implementation or integration', 'Training/inference code', 'Performance metrics', 'Model documentation', 'Deployment considerations' ] }; const defaultDeliverables = [ 'Implementation or solution', 'Tests if applicable', 'Documentation', 'Next steps and recommendations' ]; const specificDeliverables = deliverables[agentType] || defaultDeliverables; return specificDeliverables.map(d => `- ${d}`).join('\n'); } async getAgentCapabilities(args) { const { agentType } = args; try { // Try to load actual agent specification const agentFiles = await fs.readdir(this.agentsDir); const agentFile = agentFiles.find(f => f.toLowerCase().includes(agentType.replace('-', '')) || f.toLowerCase() === `${agentType}.md` ); if (agentFile) { const filePath = path.join(this.agentsDir, agentFile); const content = await fs.readFile(filePath, 'utf-8'); const agentSpec = this.parseAgentMetadata(content, agentFile); if (agentSpec) { // Parse detailed capabilities from content const detailedCapabilities = this.parseDetailedCapabilities(content); return { success: true, agentType, agentName: agentSpec.name, category: agentSpec.category, description: agentSpec.description, capabilities: detailedCapabilities, available: true, source: 'agent-specification', filename: agentFile }; } } } catch (error) { console.error('Failed to load agent specification:', error); } // Fallback to default capabilities const defaultCapabilities = { 'frontend-developer': { primary: ['React', 'Vue', 'Angular', 'Svelte'], styling: ['CSS', 'Sass', 'Tailwind', 'Styled Components'], tools: ['Webpack', 'Vite', 'ESLint', 'TypeScript'], specialties: ['Responsive Design', 'Performance Optimization', 'Accessibility'] }, 'backend-architect': { apis: ['REST', 'GraphQL', 'gRPC'], databases: ['PostgreSQL', 'MongoDB', 'Redis'], frameworks: ['Express', 'FastAPI', 'Spring', 'Django'], specialties: ['Microservices', 'Authentication', 'Performance', 'Security'] }, 'devops-engineer': { containers: ['Docker', 'Kubernetes', 'Podman'], cicd: ['GitHub Actions', 'GitLab CI', 'Jenkins'], cloud: ['AWS', 'Azure', 'GCP'], specialties: ['Infrastructure as Code', 'Monitoring', 'Security', 'Scalability'] }, 'test-automator': { frameworks: ['Jest', 'Cypress', 'Playwright', 'Selenium'], types: ['Unit Testing', 'Integration Testing', 'E2E Testing'], specialties: ['Test Strategy', 'Quality Assurance', 'Performance Testing'] }, 'data-engineer': { processing: ['ETL Pipelines', 'Stream Processing', 'Batch Processing'], tools: ['Apache Spark', 'Airflow', 'Kafka'], databases: ['Data Warehouses', 'Time Series DBs', 'Analytics'], specialties: ['Data Architecture', 'Performance Optimization', 'Analytics'] }, 'ai-engineer': { ml: ['Model Training', 'Feature Engineering', 'Model Deployment'], frameworks: ['TensorFlow', 'PyTorch', 'scikit-learn'], nlp: ['Prompt Engineering', 'LLM Integration', 'Text Processing'], specialties: ['AI Architecture', 'Model Optimization', 'Production ML'] } }; return { success: true, agentType, capabilities: defaultCapabilities[agentType] || {}, available: true, description: `Detailed capabilities for ${agentType}`, source: 'default-capabilities' }; } /** * Parse detailed capabilities from agent content */ parseDetailedCapabilities(content) { const capabilities = {}; // Extract sections that define capabilities const sections = [ { key: 'primary', patterns: ['## Primary Skills', '## Core Skills', '## Main Skills'] }, { key: 'languages', patterns: ['## Languages', '## Programming Languages'] }, { key: 'frameworks', patterns: ['## Frameworks', '## Libraries'] }, { key: 'tools', patterns: ['## Tools', '## Development Tools'] }, { key: 'specialties', patterns: ['## Specialties', '## Expertise', '## Focus Areas'] }, { key: 'databases', patterns: ['## Databases', '## Data Storage'] }, { key: 'cloud', patterns: ['## Cloud', '## Cloud Platforms'] }, { key: 'testing', patterns: ['## Testing', '## Testing Frameworks'] } ]; sections.forEach(({ key, patterns }) => { for (const pattern of patterns) { const regex = new RegExp(`${pattern}[\\s\\S]*?(?=##|$)`, 'i'); const match = content.match(regex); if (match) { const section = match[0]; const items = []; // Extract bullet points const bullets = section.match(/^\s*[-*]\s+(.+)$/gm) || []; bullets.forEach(bullet => { const item = bullet.replace(/^\s*[-*]\s+/, '').trim(); if (item && item.length < 100) { items.push(item); } }); if (items.length > 0) { capabilities[key] = items; } break; } } }); // If no structured capabilities found, use basic extraction if (Object.keys(capabilities).length === 0) { capabilities.skills = this.extractCapabilities(content); } return capabilities; } async getTaskStatus(args) { const { taskId } = args; // Check task registry const taskInfo = this.taskRegistry.get(taskId); if (!taskInfo) { return { success: false, error: 'Task not found', taskId, status: 'not_found', message: `No task found with ID: ${taskId}` }; } // Check if the artifact file still exists let artifactExists = false; let artifactContent = null; try { await fs.access(taskInfo.artifactPath); artifactExists = true; // Try to read artifact for status updates artifactContent = await fs.readFile(taskInfo.artifactPath, 'utf-8'); } catch (error) { console.error('Failed to access artifact file:', error); } // Check for related output files const relatedOutputs = []; if (artifactExists) { try { const outputFiles = await fs.readdir(this.outputsDir); const taskSlug = this.generateTaskSlug(taskInfo.task); for (const file of outputFiles) { if (file.includes(taskInfo.agentType) || file.includes(taskSlug)) { const filePath = path.join(this.outputsDir, file); const stats = await fs.stat(filePath); relatedOutputs.push({ filename: file, path: path.relative(process.cwd(), filePath), created: stats.birthtime.toISOString(), size: stats.size }); } } } catch (error) { console.error('Failed to check for related outputs:', error); } } // Determine status based on task age and artifact existence const taskAge = Date.now() - taskInfo.createdAt.getTime(); const status = artifactExists ? (taskAge > 300000 ? 'completed' : 'in_progress') : // 5 minutes to assume completion 'delegated'; return { success: true, taskId, agentType: taskInfo.agentType, task: taskInfo.task, status, artifactPath: taskInfo.artifactPath, artifactExists, delegationPrompt: taskInfo.delegationPrompt, createdAt: taskInfo.createdAt.toISOString(), lastUpdated: taskInfo.lastUpdated.toISOString(), taskAge: Math.floor(taskAge / 1000), // seconds context: taskInfo.context, relatedOutputs, message: this.getStatusMessage(status, taskInfo), timestamp: new Date().toISOString() }; } /** * Generate appropriate status message */ getStatusMessage(status, taskInfo) { switch (status) { case 'completed': return `Task completed - check artifact at: ${taskInfo.artifactPath}`; case 'in_progress': return 'Task is being processed by the specialist agent'; case 'delegated': return 'Task has been delegated and is awaiting processing'; default: return 'Unknown task status'; } } async listSpecialistOutputs(args) { const { filter, agentType } = args || {}; try { // Ensure outputs directory exists await fs.mkdir(this.outputsDir, { recursive: true }); // Read directory contents const files = await fs.readdir(this.outputsDir); // Get file details const outputs = await Promise.all( files .filter(file => file.endsWith('.md') || file.endsWith('.txt')) .map(async (file) => { const filePath = path.join(this.outputsDir, file); const stats = await fs.stat(filePath); // Extract agent type from filename const agentMatch = file.match(/^([a-z-]+)-/); const agent = agentMatch ? agentMatch[1] : 'unknown'; // Determine type from filename or content let type = 'general'; if (file.includes('architecture')) type = 'architecture'; else if (file.includes('implementation')) type = 'implementation'; else if (file.includes('test')) type = 'testing'; else if (file.includes('analysis')) type = 'analysis'; else if (file.includes('design')) type = 'design'; else if (file.includes('deployment')) type = 'deployment'; // Read first few lines for preview let preview = ''; try { const content = await fs.readFile(filePath, 'utf-8'); const lines = content.split('\n'); preview = lines.slice(0, 5).join('\n'); // Extract task ID if present const taskIdMatch = content.match(/Task ID[:\s]+([a-zA-Z0-9_]+)/); const taskId = taskIdMatch ? taskIdMatch[1] : null; return { file: path.relative(process.cwd(), filePath), name: file, type, agent, size: stats.size, created: stats.birthtime.toISOString(), modified: stats.mtime.toISOString(), timestamp: stats.mtime.toISOString(), preview, taskId }; } catch (error) { console.error(`Failed to read file ${file}:`, error); return null; } }) ); // Filter out null entries let validOutputs = outputs.filter(o => o !== null); // Apply filters if (agentType) { validOutputs = validOutputs.filter(output => output.agent === agentType); } if (filter) { const filterLower = filter.toLowerCase(); validOutputs = validOutputs.filter(output => output.file.toLowerCase().includes(filterLower) || output.type.toLowerCase().includes(filterLower) || output.agent.toLowerCase().includes(filterLower) || output.preview.toLowerCase().includes(filterLower) ); } // Sort by modified date (newest first) validOutputs.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()); // Generate summary statistics const summary = this.generateOutputsSummary(validOutputs); return { success: true, outputs: validOutputs, total: validOutputs.length, outputsDirectory: this.outputsDir, summary }; } catch (error) { console.error('Failed to list specialist outputs:', error); return { success: false, error: error.message, outputs: [], total: 0, outputsDirectory: this.outputsDir }; } } /** * Generate summary statistics for outputs */ generateOutputsSummary(outputs) { const summary = { byAgent: {}, byType: {}, byDate: {}, totalSize: 0 }; outputs.forEach(output => { // By agent summary.byAgent[output.agent] = (summary.byAgent[output.agent] || 0) + 1; // By type summary.byType[output.type] = (summary.byType[output.type] || 0) + 1; // By date const date = output.created.split('T')[0]; summary.byDate[date] = (summary.byDate[date] || 0) + 1; // Total size summary.totalSize += output.size; }); // Sort summaries summary.byAgent = Object.fromEntries( Object.entries(summary.byAgent).sort((a, b) => b[1] - a[1]) ); summary.byType = Object.fromEntries( Object.entries(summary.byType).sort((a, b) => b[1] - a[1]) ); summary.byDate = Object.fromEntries( Object.entries(summary.byDate).sort((a, b) => b[0].localeCompare(a[0])) ); return summary; } async analyzeProjectState(args) { const { path: projectPath } = args || {}; const analyzePath = projectPath || process.cwd(); try { // Analyze specialist outputs const outputsResult = await this.listSpecialistOutputs({}); const outputs = outputsResult.outputs || []; // Analyze task registry const activeTasks = Array.from(this.taskRegistry.values()); const completedTasks = activeTasks.filter(t => { const age = Date.now() - t.createdAt.getTime(); return age > 300000; // 5 minutes }).length; const pendingTasks = activeTasks.length - completedTasks; // Get unique agents utilized const agentsUtilized = [...new Set(outputs.map(o => o.agent))]; // Determine last activity const lastActivity = outputs.length > 0 ? outputs[0].modified : new Date().toISOString(); // Generate recommendations based on analysis const recommendations = this.generateProjectRecommendations(outputs, activeTasks); return { success: true, projectName: 'claude-code-subagents-orchestrator', path: analyzePath, status: 'active', statistics: { totalOutputs: outputs.length, totalTasks: activeTasks.length, completedTasks, pendingTasks, outputsByAgent: outputsResult.summary?.byAgent || {}, outputsByType: outputsResult.summary?.byType || {}, totalSize: outputsResult.summary?.totalSize || 0 }, lastActivity, agents_utilized: agentsUtilized, recommendations, recovery_options: [ 'Resume from last checkpoint using existing artifacts', 'Re-delegate specific agent tasks', 'Analyze specialist outputs for continuation', 'Generate new delegation prompts' ], recentTasks: activeTasks.slice(-5).map(t => ({ taskId: t.taskId, agentType: t.agentType, task: t.task.substring(0, 100) + (t.task.length > 100 ? '...' : ''), createdAt: t.createdAt.toISOString() })) }; } catch (error) { console.error('Failed to analyze project state:', error); return { success: false, error: error.message, projectName: 'claude-code-subagents-orchestrator', path: analyzePath, status: 'error', message: 'Failed to analyze project state - manual inspection may be required' }; } } /** * Generate project recommendations based on analysis */ generateProjectRecommendations(outputs, tasks) { const recommendations = []; // Check if any outputs exist if (outputs.length === 0) { recommendations.push('No specialist outputs found - consider delegating initial tasks'); } // Check task completion rate const completionRate = tasks.length > 0 ? tasks.filter(t => Date.now() - t.createdAt.getTime() > 300000).length / tasks.length : 0; if (completionRate < 0.5 && tasks.length > 0) { recommendations.push('Several tasks are pending - monitor progress with getTaskStatus'); } // Check for recent activity if (outputs.length > 0) { const latestOutput = new Date(outputs[0].modified); const hoursSinceLastActivity = (Date.now() - latestOutput.getTime()) / (1000 * 60 * 60); if (hoursSinceLastActivity > 24) { recommendations.push('No recent activity detected - consider resuming work'); } } // Agent-specific recommendations const agentCounts = {}; outputs.forEach(o => { agentCounts[o.agent] = (agentCounts[o.agent] || 0) + 1; }); if (!agentCounts['test-automator'] && outputs.length > 5) { recommendations.push('Consider delegating testing tasks to test-automator'); } if (!agentCounts['devops-engineer'] && outputs.length > 10) { recommendations.push('Consider setting up deployment with devops-engineer'); } // Default recommendations if (recommendations.length === 0) { recommendations.push( 'Continue with current implementation progress', 'Review specialist outputs for quality', 'Consider additional delegation for complex tasks' ); } return recommendations; } async run() { try { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Claude Code Subagents Orchestrator MCP Server started successfully'); } catch (error) { console.error('Failed to start MCP server:', error); process.exit(1); } } /** * Parse agent metadata from markdown content */ parseAgentMetadata(content, filename) { try { // Extract agent type from filename const type = filename.replace('.md', '').toLowerCase(); // Parse YAML frontmatter if present const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); if (frontmatterMatch) { // Simple YAML parsing for key fields const frontmatter = frontmatterMatch[1]; const getName = () => { const match = frontmatter.match(/name:\s*(.+)/); return match ? match[1].trim() : type; }; const getCategory = () => { const match = frontmatter.match(/category:\s*(.+)/); return match ? match[1].trim() : 'general'; }; const getDescription = () => { const match = frontmatter.match(/description:\s*(.+)/); return match ? match[1].trim() : `${type} specialist`; }; // Extract capabilities from content const capabilities = this.extractCapabilities(content); return { type, name: getName(), status: 'available', category: getCategory(), description: getDescription(), capabilities, filename }; } // Fallback parsing without frontmatter const firstHeading = content.match(/^#\s+(.+)/m); const name = firstHeading ? firstHeading[1].trim() : type; return { type, name, status: 'available', category: this.inferCategory(type), description: `${name} specialist agent`, capabilities: this.extractCapabilities(content), filename }; } catch (error) { console.error(`Failed to parse agent metadata from ${filename}:`, error); return null; } } /** * Extract capabilities from agent content */ extractCapabilities(content) { const capabilities = []; // Look for capabilities section const capabilitiesMatch = content.match(/##\s*(?:Capabilities|Skills|Expertise)[\s\S]*?(?=##|$)/i); if (capabilitiesMatch) { const section = capabilitiesMatch[0]; // Extract bullet points const bullets = section.match(/^\s*[-*]\s+(.+)$/gm) || []; bullets.forEach(bullet => { const capability = bullet.replace(/^\s*[-*]\s+/, '').trim(); if (capability && capability.length < 100) { capabilities.push(capability); } }); } // Also look for technology keywords const techKeywords = [ 'JavaScript', 'TypeScript', 'Python', 'Java', 'Go', 'Rust', 'C++', 'React', 'Vue', 'Angular', 'Svelte', 'Next.js', 'Nuxt', 'Node.js', 'Express', 'FastAPI', 'Django', 'Spring', 'Docker', 'Kubernetes', 'AWS', 'Azure', 'GCP', 'PostgreSQL', 'MongoDB', 'Redis', 'MySQL', 'REST', 'GraphQL', 'gRPC', 'WebSocket', 'CI/CD', 'DevOps', 'Testing', 'Security' ]; techKeywords.forEach(tech => { if (content.includes(tech) && !capabilities.includes(tech)) { capabilities.push(tech); } }); return capabilities.slice(0, 10); // Limit to 10 capabilities } /** * Infer category from agent type */ inferCategory(type) { const categoryMap = { 'frontend': 'frontend', 'backend': 'backend', 'fullstack': 'fullstack', 'devops': 'devops', 'test': 'testing', 'qa': 'testing', 'security': 'security', 'data': 'data', 'ml': 'data', 'ai': 'data' }; for (const [key, category] of Object.entries(categoryMap)) { if (type.includes(key)) { return category; } } return 'general'; } /** * Get default agents when directory is not available */ getDefaultAgents() { return { success: true, agents: [ { type: 'frontend-developer', name: 'Frontend Developer', status: 'available', category: 'frontend', capabilities: ['React', 'Vue', 'Angular', 'CSS', 'JavaScript', 'TypeScript'], description: 'Frontend development specialist' }, { type: 'backend-architect', name: 'Backend Architect', status: 'available', category: 'backend', capabilities: ['API design', 'Database', 'Microservices', 'Authentication'], description: 'Backend architecture and API specialist' }, { type: 'devops-engineer', name: 'DevOps Engineer', status: 'available', category: 'devops', capabilities: ['Docker', 'CI/CD', 'Cloud', 'Kubernetes'], description: 'DevOps and infrastructure specialist' }, { type: 'test-automator', name: 'Test Automator', status: 'available', category: 'testing', capabilities: ['Testing', 'QA', 'Automation', 'E2E'], description: 'Test automation and quality assurance specialist' } ] }; } } // Start the server if (import.meta.url === `file://${process.argv[1]}`) { const server = new SubagentsOrchestratorServer(); server.run().catch(error => { console.error('Server error:', error); process.exit(1); }); } export { SubagentsOrchestratorServer };