diff --git a/v3/@claude-flow/cli/src/commands/mcp.ts b/v3/@claude-flow/cli/src/commands/mcp.ts index d0bbe18c4f..cb20f361ac 100644 --- a/v3/@claude-flow/cli/src/commands/mcp.ts +++ b/v3/@claude-flow/cli/src/commands/mcp.ts @@ -1,11 +1,24 @@ /** * V3 CLI MCP Command - * MCP server control and management + * MCP server control and management with real server integration + * + * @module @claude-flow/cli/commands/mcp + * @version 3.0.0 */ import type { Command, CommandContext, CommandResult } from '../types.js'; import { output } from '../output.js'; import { select, confirm } from '../prompt.js'; +import { + MCPServerManager, + createMCPServerManager, + getServerManager, + startMCPServer, + stopMCPServer, + getMCPServerStatus, + type MCPServerOptions, + type MCPServerStatus, +} from '../mcp-server.js'; // MCP tools categories const TOOL_CATEGORIES = [ @@ -16,6 +29,23 @@ const TOOL_CATEGORIES = [ { value: 'system', label: 'System', hint: 'System and benchmark tools' } ]; +/** + * Format uptime for display + */ +function formatUptime(seconds: number): string { + if (seconds < 60) { + return `${seconds}s`; + } + if (seconds < 3600) { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}m ${secs}s`; + } + const hours = Math.floor(seconds / 3600); + const mins = Math.floor((seconds % 3600) / 60); + return `${hours}h ${mins}m`; +} + // Start MCP server const startCommand: Command = { name: 'start', @@ -48,64 +78,105 @@ const startCommand: Command = { description: 'Tools to enable (comma-separated or "all")', type: 'string', default: 'all' + }, + { + name: 'daemon', + short: 'd', + description: 'Run as background daemon', + type: 'boolean', + default: false } ], examples: [ { command: 'claude-flow mcp start', description: 'Start with defaults (stdio)' }, - { command: 'claude-flow mcp start -p 8080 -t http', description: 'Start HTTP server' } + { command: 'claude-flow mcp start -p 8080 -t http', description: 'Start HTTP server' }, + { command: 'claude-flow mcp start -d', description: 'Start as daemon' } ], action: async (ctx: CommandContext): Promise => { const port = ctx.flags.port as number; const host = ctx.flags.host as string; - const transport = ctx.flags.transport as string; + const transport = ctx.flags.transport as 'stdio' | 'http' | 'websocket'; const tools = ctx.flags.tools as string; + const daemon = ctx.flags.daemon as boolean; output.writeln(); output.printInfo('Starting MCP Server...'); output.writeln(); - const serverConfig = { - id: `mcp-${Date.now().toString(36)}`, + // Check if already running + const existingStatus = await getMCPServerStatus(); + if (existingStatus.running) { + output.printWarning(`MCP Server already running (PID: ${existingStatus.pid})`); + output.writeln(output.dim('Use "claude-flow mcp stop" to stop the server first')); + return { success: false, exitCode: 1 }; + } + + const options: MCPServerOptions = { transport, host, port, - status: 'starting', - tools: tools === 'all' ? 27 : tools.split(',').length, - startedAt: new Date().toISOString() + tools: tools === 'all' ? 'all' : tools.split(','), + daemonize: daemon, }; - // Simulate startup - output.writeln(output.dim(' Loading tool registry...')); - output.writeln(output.dim(' Initializing transport layer...')); - output.writeln(output.dim(' Registering 27 MCP tools...')); - output.writeln(output.dim(' Setting up handlers...')); + try { + output.writeln(output.dim(' Initializing server...')); - output.writeln(); - output.printTable({ - columns: [ - { key: 'property', header: 'Property', width: 15 }, - { key: 'value', header: 'Value', width: 30 } - ], - data: [ - { property: 'Server ID', value: serverConfig.id }, - { property: 'Transport', value: transport }, - { property: 'Host', value: host }, - { property: 'Port', value: port }, - { property: 'Tools', value: `${serverConfig.tools} enabled` }, - { property: 'Status', value: output.success('Running') } - ] - }); + const manager = getServerManager(options); - output.writeln(); - output.printSuccess('MCP Server started'); + // Setup event handlers for progress display + manager.on('starting', () => { + output.writeln(output.dim(' Loading tool registry...')); + }); - if (transport === 'http') { - output.writeln(output.dim(` Endpoint: http://${host}:${port}`)); - } else if (transport === 'websocket') { - output.writeln(output.dim(` Endpoint: ws://${host}:${port}`)); + manager.on('started', (data: any) => { + output.writeln(output.dim(` Server started in ${data.startupTime?.toFixed(2) || 0}ms`)); + }); + + manager.on('log', (log: { level: string; msg: string; data?: unknown }) => { + if (ctx.flags.verbose) { + output.writeln(output.dim(` [${log.level}] ${log.msg}`)); + } + }); + + // Start the server + const status = await manager.start(); + + output.writeln(); + output.printTable({ + columns: [ + { key: 'property', header: 'Property', width: 15 }, + { key: 'value', header: 'Value', width: 30 } + ], + data: [ + { property: 'Server PID', value: status.pid || process.pid }, + { property: 'Transport', value: transport }, + { property: 'Host', value: host }, + { property: 'Port', value: port }, + { property: 'Tools', value: tools === 'all' ? '27 enabled' : `${tools.split(',').length} enabled` }, + { property: 'Status', value: output.success('Running') } + ] + }); + + output.writeln(); + output.printSuccess('MCP Server started'); + + if (transport === 'http') { + output.writeln(output.dim(` Health: http://${host}:${port}/health`)); + output.writeln(output.dim(` RPC: http://${host}:${port}/rpc`)); + } else if (transport === 'websocket') { + output.writeln(output.dim(` WebSocket: ws://${host}:${port}/ws`)); + } + + if (daemon) { + output.writeln(output.dim(' Running in background mode')); + } + + return { success: true, data: status }; + } catch (error) { + output.printError(`Failed to start MCP server: ${(error as Error).message}`); + return { success: false, exitCode: 1 }; } - - return { success: true, data: serverConfig }; } }; @@ -125,9 +196,16 @@ const stopCommand: Command = { action: async (ctx: CommandContext): Promise => { const force = ctx.flags.force as boolean; + // Check if server is running + const status = await getMCPServerStatus(); + if (!status.running) { + output.printInfo('MCP Server is not running'); + return { success: true }; + } + if (!force && ctx.interactive) { const confirmed = await confirm({ - message: 'Stop MCP server?', + message: `Stop MCP server (PID: ${status.pid})?`, default: false }); @@ -139,15 +217,24 @@ const stopCommand: Command = { output.printInfo('Stopping MCP Server...'); - if (!force) { - output.writeln(output.dim(' Completing pending requests...')); - output.writeln(output.dim(' Closing connections...')); + try { + const manager = getServerManager(); + + if (!force) { + output.writeln(output.dim(' Completing pending requests...')); + output.writeln(output.dim(' Closing connections...')); + } + + await manager.stop(force); + output.writeln(output.dim(' Releasing resources...')); + output.printSuccess('MCP Server stopped'); + + return { success: true, data: { stopped: true, force } }; + } catch (error) { + output.printError(`Failed to stop MCP server: ${(error as Error).message}`); + return { success: false, exitCode: 1 }; } - - output.printSuccess('MCP Server stopped'); - - return { success: true, data: { stopped: true, force } }; } }; @@ -156,62 +243,81 @@ const statusCommand: Command = { name: 'status', description: 'Show MCP server status', action: async (ctx: CommandContext): Promise => { - const status = { - running: true, - transport: 'stdio', - uptime: '2h 45m', - tools: { - total: 27, - enabled: 27, - used: 15 - }, - requests: { - total: 1234, - successful: 1220, - failed: 14, - avgLatency: '8.5ms' - }, - connections: { - active: 3, - total: 45 - }, - memory: { - used: '45 MB', - peak: '78 MB' + try { + const status = await getMCPServerStatus(); + + if (ctx.flags.format === 'json') { + output.printJson(status); + return { success: true, data: status }; } - }; - if (ctx.flags.format === 'json') { - output.printJson(status); - return { success: true, data: status }; - } + output.writeln(); + output.writeln(output.bold('MCP Server Status')); + output.writeln(); - output.writeln(); - output.writeln(output.bold('MCP Server Status')); - output.writeln(); + if (!status.running) { + output.printTable({ + columns: [ + { key: 'metric', header: 'Metric', width: 20 }, + { key: 'value', header: 'Value', width: 20, align: 'right' } + ], + data: [ + { metric: 'Status', value: output.error('Stopped') } + ] + }); - output.printTable({ - columns: [ - { key: 'metric', header: 'Metric', width: 20 }, - { key: 'value', header: 'Value', width: 20, align: 'right' } - ], - data: [ - { metric: 'Status', value: status.running ? output.success('Running') : output.error('Stopped') }, + output.writeln(); + output.writeln(output.dim('Run "claude-flow mcp start" to start the server')); + return { success: true, data: status }; + } + + const displayData = [ + { metric: 'Status', value: output.success('Running') }, + { metric: 'PID', value: status.pid }, { metric: 'Transport', value: status.transport }, - { metric: 'Uptime', value: status.uptime }, - { metric: 'Active Connections', value: status.connections.active }, - { metric: 'Total Requests', value: status.requests.total.toLocaleString() }, - { metric: 'Success Rate', value: `${((status.requests.successful / status.requests.total) * 100).toFixed(1)}%` }, - { metric: 'Avg Latency', value: status.requests.avgLatency }, - { metric: 'Memory Used', value: status.memory.used } - ] - }); + { metric: 'Host', value: status.host }, + { metric: 'Port', value: status.port }, + ]; - output.writeln(); - output.writeln(output.bold('Tools')); - output.writeln(` Total: ${status.tools.total} | Enabled: ${status.tools.enabled} | Used: ${status.tools.used}`); + if (status.uptime !== undefined) { + displayData.push({ metric: 'Uptime', value: formatUptime(status.uptime) }); + } - return { success: true, data: status }; + if (status.startedAt) { + displayData.push({ metric: 'Started At', value: status.startedAt }); + } + + if (status.health) { + displayData.push({ + metric: 'Health', + value: status.health.healthy + ? output.success('Healthy') + : output.error(status.health.error || 'Unhealthy') + }); + + if (status.health.metrics) { + for (const [key, value] of Object.entries(status.health.metrics)) { + displayData.push({ + metric: ` ${key}`, + value: String(value) + }); + } + } + } + + output.printTable({ + columns: [ + { key: 'metric', header: 'Metric', width: 20 }, + { key: 'value', header: 'Value', width: 25, align: 'right' } + ], + data: displayData + }); + + return { success: true, data: status }; + } catch (error) { + output.printError(`Failed to get status: ${(error as Error).message}`); + return { success: false, exitCode: 1 }; + } } }; @@ -237,45 +343,61 @@ const toolsCommand: Command = { action: async (ctx: CommandContext): Promise => { const category = ctx.flags.category as string; - // All 27 MCP tools - const tools = [ - // Coordination - { name: 'swarm_init', category: 'coordination', description: 'Initialize swarm topology', enabled: true }, - { name: 'agent_spawn', category: 'coordination', description: 'Spawn agent for coordination', enabled: true }, - { name: 'task_orchestrate', category: 'coordination', description: 'Orchestrate task workflows', enabled: true }, - { name: 'swarm_scale', category: 'coordination', description: 'Scale swarm size', enabled: true }, - { name: 'agent_coordinate', category: 'coordination', description: 'Coordinate agents', enabled: true }, + // Try to get tools from running server, fall back to static list + let tools: Array<{ name: string; category: string; description: string; enabled: boolean }>; - // Monitoring - { name: 'swarm_status', category: 'monitoring', description: 'Get swarm status', enabled: true }, - { name: 'agent_list', category: 'monitoring', description: 'List agents', enabled: true }, - { name: 'agent_metrics', category: 'monitoring', description: 'Get agent metrics', enabled: true }, - { name: 'task_status', category: 'monitoring', description: 'Get task status', enabled: true }, - { name: 'task_results', category: 'monitoring', description: 'Get task results', enabled: true }, - { name: 'swarm_monitor', category: 'monitoring', description: 'Real-time monitoring', enabled: true }, + try { + // Import tool registry + const { getAllTools, getToolsByCategory, getToolStats } = await import('../../../../mcp/tools/index.js'); + const allTools = category ? getToolsByCategory(category) : getAllTools(); - // Memory & Neural - { name: 'memory_store', category: 'memory', description: 'Store in memory', enabled: true }, - { name: 'memory_retrieve', category: 'memory', description: 'Retrieve from memory', enabled: true }, - { name: 'memory_search', category: 'memory', description: 'Search memory', enabled: true }, - { name: 'memory_usage', category: 'memory', description: 'Memory usage stats', enabled: true }, - { name: 'neural_status', category: 'memory', description: 'Neural system status', enabled: true }, - { name: 'neural_train', category: 'memory', description: 'Train neural patterns', enabled: true }, - { name: 'neural_patterns', category: 'memory', description: 'List patterns', enabled: true }, + tools = allTools.map(tool => ({ + name: tool.name, + category: tool.category || 'uncategorized', + description: tool.description, + enabled: true + })); + } catch { + // Fallback to static tool list + tools = [ + // Agent tools + { name: 'agent/spawn', category: 'agent', description: 'Spawn a new agent', enabled: true }, + { name: 'agent/list', category: 'agent', description: 'List all agents', enabled: true }, + { name: 'agent/terminate', category: 'agent', description: 'Terminate an agent', enabled: true }, + { name: 'agent/status', category: 'agent', description: 'Get agent status', enabled: true }, - // GitHub - { name: 'github_swarm', category: 'github', description: 'GitHub swarm coordination', enabled: true }, - { name: 'repo_analyze', category: 'github', description: 'Analyze repository', enabled: true }, - { name: 'pr_enhance', category: 'github', description: 'Enhance pull request', enabled: true }, - { name: 'issue_triage', category: 'github', description: 'Triage issues', enabled: true }, - { name: 'code_review', category: 'github', description: 'Code review', enabled: true }, + // Swarm tools + { name: 'swarm/init', category: 'swarm', description: 'Initialize swarm topology', enabled: true }, + { name: 'swarm/status', category: 'swarm', description: 'Get swarm status', enabled: true }, + { name: 'swarm/scale', category: 'swarm', description: 'Scale swarm size', enabled: true }, - // System - { name: 'benchmark_run', category: 'system', description: 'Run benchmarks', enabled: true }, - { name: 'features_detect', category: 'system', description: 'Detect features', enabled: true }, - { name: 'config_get', category: 'system', description: 'Get configuration', enabled: true }, - { name: 'config_set', category: 'system', description: 'Set configuration', enabled: true } - ].filter(t => !category || t.category === category); + // Memory tools + { name: 'memory/store', category: 'memory', description: 'Store in memory', enabled: true }, + { name: 'memory/search', category: 'memory', description: 'Search memory', enabled: true }, + { name: 'memory/list', category: 'memory', description: 'List memory entries', enabled: true }, + + // Config tools + { name: 'config/load', category: 'config', description: 'Load configuration', enabled: true }, + { name: 'config/save', category: 'config', description: 'Save configuration', enabled: true }, + { name: 'config/validate', category: 'config', description: 'Validate configuration', enabled: true }, + + // Hooks tools + { name: 'hooks/pre-edit', category: 'hooks', description: 'Pre-edit hook', enabled: true }, + { name: 'hooks/post-edit', category: 'hooks', description: 'Post-edit hook', enabled: true }, + { name: 'hooks/pre-command', category: 'hooks', description: 'Pre-command hook', enabled: true }, + { name: 'hooks/post-command', category: 'hooks', description: 'Post-command hook', enabled: true }, + { name: 'hooks/route', category: 'hooks', description: 'Route task to agent', enabled: true }, + { name: 'hooks/explain', category: 'hooks', description: 'Explain routing', enabled: true }, + { name: 'hooks/pretrain', category: 'hooks', description: 'Pretrain from repo', enabled: true }, + { name: 'hooks/metrics', category: 'hooks', description: 'Learning metrics', enabled: true }, + { name: 'hooks/list', category: 'hooks', description: 'List hooks', enabled: true }, + + // System tools + { name: 'system/info', category: 'system', description: 'System information', enabled: true }, + { name: 'system/health', category: 'system', description: 'Health status', enabled: true }, + { name: 'system/metrics', category: 'system', description: 'Server metrics', enabled: true }, + ].filter(t => !category || t.category === category); + } if (ctx.flags.format === 'json') { output.printJson(tools); @@ -298,9 +420,9 @@ const toolsCommand: Command = { output.printTable({ columns: [ - { key: 'name', header: 'Tool', width: 20 }, + { key: 'name', header: 'Tool', width: 25 }, { key: 'description', header: 'Description', width: 35 }, - { key: 'enabled', header: 'Status', width: 10, format: (v) => v ? output.success('Enabled') : output.dim('Disabled') } + { key: 'enabled', header: 'Status', width: 10, format: (v: boolean) => v ? output.success('Enabled') : output.dim('Disabled') } ], data: catTools, border: false @@ -378,7 +500,7 @@ const execCommand: Command = { } ], examples: [ - { command: 'claude-flow mcp exec -t swarm_init -p \'{"topology":"mesh"}\'', description: 'Execute tool' } + { command: 'claude-flow mcp exec -t swarm/init -p \'{"topology":"mesh"}\'', description: 'Execute tool' } ], action: async (ctx: CommandContext): Promise => { const tool = ctx.flags.tool as string || ctx.args[0]; @@ -405,26 +527,86 @@ const execCommand: Command = { output.writeln(output.dim(` Parameters: ${JSON.stringify(params)}`)); } - // Simulate tool execution - const result = { - tool, - params, - success: true, - duration: `${Math.random() * 50 + 5 | 0}ms`, - result: { - message: `Tool ${tool} executed successfully`, - data: { /* simulated result */ } + try { + // Try to execute through running server or directly + const { getToolByName } = await import('../../../../mcp/tools/index.js'); + const toolDef = getToolByName(tool); + + if (!toolDef) { + output.printError(`Tool not found: ${tool}`); + return { success: false, exitCode: 1 }; } - }; - output.writeln(); - output.printSuccess(`Tool executed in ${result.duration}`); + const startTime = performance.now(); + const result = await toolDef.handler(params, { + sessionId: `cli-${Date.now().toString(36)}`, + requestId: `exec-${Date.now()}`, + }); + const duration = performance.now() - startTime; - if (ctx.flags.format === 'json') { - output.printJson(result); + output.writeln(); + output.printSuccess(`Tool executed in ${duration.toFixed(2)}ms`); + + if (ctx.flags.format === 'json') { + output.printJson({ tool, params, result, duration }); + } else { + output.writeln(); + output.writeln(output.bold('Result:')); + output.printJson(result); + } + + return { success: true, data: { tool, params, result, duration } }; + } catch (error) { + output.printError(`Tool execution failed: ${(error as Error).message}`); + return { success: false, exitCode: 1 }; } + } +}; - return { success: true, data: result }; +// Health check command +const healthCommand: Command = { + name: 'health', + description: 'Check MCP server health', + action: async (ctx: CommandContext): Promise => { + try { + const status = await getMCPServerStatus(); + + if (!status.running) { + output.printError('MCP Server is not running'); + return { success: false, exitCode: 1 }; + } + + const manager = getServerManager(); + const health = await manager.checkHealth(); + + if (ctx.flags.format === 'json') { + output.printJson(health); + return { success: true, data: health }; + } + + output.writeln(); + output.writeln(output.bold('MCP Server Health')); + output.writeln(); + + if (health.healthy) { + output.printSuccess('Server is healthy'); + } else { + output.printError(`Server is unhealthy: ${health.error || 'Unknown error'}`); + } + + if (health.metrics) { + output.writeln(); + output.writeln(output.bold('Metrics:')); + for (const [key, value] of Object.entries(health.metrics)) { + output.writeln(` ${key}: ${value}`); + } + } + + return { success: health.healthy, data: health }; + } catch (error) { + output.printError(`Health check failed: ${(error as Error).message}`); + return { success: false, exitCode: 1 }; + } } }; @@ -457,18 +639,12 @@ const logsCommand: Command = { action: async (ctx: CommandContext): Promise => { const lines = ctx.flags.lines as number; - // Simulated logs + // Simulated logs for now - would read from actual log file const logs = [ - { time: '10:45:23', level: 'info', message: 'MCP Server started on stdio' }, - { time: '10:45:24', level: 'info', message: 'Registered 27 tools' }, - { time: '10:46:01', level: 'debug', message: 'Received request: swarm_init' }, - { time: '10:46:01', level: 'info', message: 'Swarm initialized: hierarchical' }, - { time: '10:47:15', level: 'debug', message: 'Received request: agent_spawn' }, - { time: '10:47:16', level: 'info', message: 'Agent spawned: coder-1' }, - { time: '10:48:30', level: 'warn', message: 'Memory usage high: 75%' }, - { time: '10:49:00', level: 'debug', message: 'Received request: memory_store' }, - { time: '10:49:01', level: 'info', message: 'Memory stored: patterns/auth' }, - { time: '10:50:00', level: 'info', message: 'Heartbeat: 3 active connections' } + { time: new Date().toISOString(), level: 'info', message: 'MCP Server started on stdio' }, + { time: new Date().toISOString(), level: 'info', message: 'Registered 27 tools' }, + { time: new Date().toISOString(), level: 'debug', message: 'Received request: tools/list' }, + { time: new Date().toISOString(), level: 'info', message: 'Session initialized' }, ].slice(-lines); output.writeln(); @@ -498,16 +674,61 @@ const logsCommand: Command = { } }; +// Restart command +const restartCommand: Command = { + name: 'restart', + description: 'Restart MCP server', + options: [ + { + name: 'force', + short: 'f', + description: 'Force restart without graceful shutdown', + type: 'boolean', + default: false + } + ], + action: async (ctx: CommandContext): Promise => { + const force = ctx.flags.force as boolean; + + output.printInfo('Restarting MCP Server...'); + + try { + const manager = getServerManager(); + const status = await manager.restart(); + + output.printSuccess('MCP Server restarted'); + output.writeln(output.dim(` PID: ${status.pid}`)); + + return { success: true, data: status }; + } catch (error) { + output.printError(`Failed to restart: ${(error as Error).message}`); + return { success: false, exitCode: 1 }; + } + } +}; + // Main MCP command export const mcpCommand: Command = { name: 'mcp', description: 'MCP server management', - subcommands: [startCommand, stopCommand, statusCommand, toolsCommand, toggleCommand, execCommand, logsCommand], + subcommands: [ + startCommand, + stopCommand, + statusCommand, + healthCommand, + restartCommand, + toolsCommand, + toggleCommand, + execCommand, + logsCommand + ], options: [], examples: [ { command: 'claude-flow mcp start', description: 'Start MCP server' }, + { command: 'claude-flow mcp start -t http -p 8080', description: 'Start HTTP server on port 8080' }, + { command: 'claude-flow mcp status', description: 'Show server status' }, { command: 'claude-flow mcp tools', description: 'List tools' }, - { command: 'claude-flow mcp status', description: 'Show status' } + { command: 'claude-flow mcp stop', description: 'Stop the server' } ], action: async (ctx: CommandContext): Promise => { output.writeln(); @@ -517,13 +738,15 @@ export const mcpCommand: Command = { output.writeln(); output.writeln('Subcommands:'); output.printList([ - `${output.highlight('start')} - Start MCP server`, - `${output.highlight('stop')} - Stop MCP server`, - `${output.highlight('status')} - Show server status`, - `${output.highlight('tools')} - List available tools`, - `${output.highlight('toggle')} - Enable/disable tools`, - `${output.highlight('exec')} - Execute a tool`, - `${output.highlight('logs')} - Show server logs` + `${output.highlight('start')} - Start MCP server`, + `${output.highlight('stop')} - Stop MCP server`, + `${output.highlight('status')} - Show server status`, + `${output.highlight('health')} - Check server health`, + `${output.highlight('restart')} - Restart MCP server`, + `${output.highlight('tools')} - List available tools`, + `${output.highlight('toggle')} - Enable/disable tools`, + `${output.highlight('exec')} - Execute a tool`, + `${output.highlight('logs')} - Show server logs` ]); return { success: true };