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