UNPKG

ludus-mcp

Version:

MCP server for managing Ludus cybersecurity training environments through natural language commands

155 lines (134 loc) 4.62 kB
import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { Logger } from '../utils/logger.js'; import { LudusCliWrapper } from '../ludusMCP/cliWrapper.js'; export interface LudusHelpArgs { command?: string; subcommand?: string; user?: string; } export function createLudusHelpTool(logger: Logger, cliWrapper: LudusCliWrapper): Tool { return { name: 'ludus_help', description: 'Get help information for Ludus CLI commands. Use this to discover available commands, learn syntax, and understand options. Can show general help or specific command help.', inputSchema: { type: 'object', properties: { command: { type: 'string', description: 'Specific command to get help for (e.g., "range", "templates", "user"). If omitted, shows general help.', examples: ['range', 'templates', 'user', 'testing'] }, subcommand: { type: 'string', description: 'Subcommand to get help for (e.g., "deploy", "logs", "list"). Requires command to be specified.', examples: ['deploy', 'logs', 'list', 'add', 'remove'] }, user: { type: 'string', description: 'User ID to get help for (admin only). If omitted, gets help for current user context.' } }, required: [] } }; } export async function handleLudusHelp( args: LudusHelpArgs, logger: Logger, cliWrapper: LudusCliWrapper ): Promise<any> { const { command, subcommand, user } = args; try { // Build help command based on what's requested let result; let fullCommand: string; if (command) { // For specific commands: ludus <command> [subcommand] --help const helpArgs: string[] = []; if (subcommand) { helpArgs.push(subcommand); } helpArgs.push('--help'); // Add user context if provided if (user) { helpArgs.push('--user', user); } fullCommand = subcommand ? `ludus ${command} ${subcommand} --help` : `ludus ${command} --help`; logger.info('Getting Ludus CLI help', { command: fullCommand, user }); result = await cliWrapper.executeArbitraryCommand(command, helpArgs); } else { // For general help: ludus --help fullCommand = 'ludus --help'; logger.info('Getting Ludus CLI help', { command: fullCommand, user }); result = await cliWrapper.executeCommand('--help', []); } if (!result.success) { throw new Error(`Failed to get help: ${result.message}`); } // Format response const targetUser = user || 'current user'; const helpType = command && subcommand ? `${command} ${subcommand}` : command ? command : 'general'; return { success: true, command: fullCommand, user: targetUser, helpType: helpType, content: result.rawOutput || result.message, // Structured for easy parsing sections: parseHelpOutput(result.rawOutput || result.message) }; } catch (error: any) { logger.error('Ludus help failed', { command: command || 'help', subcommand, error: error.message, user }); return { success: false, command: command || 'help', user: user || 'current user', error: error.message, content: error.message }; } } /** * Parse help output into structured sections */ function parseHelpOutput(output: string): any { const sections: any = {}; try { const lines = output.split('\n'); let currentSection = 'description'; let currentContent: string[] = []; for (const line of lines) { const trimmed = line.trim(); // Detect section headers if (trimmed.match(/^(Usage|Available Commands|Flags|Examples|Global Flags|Aliases):/)) { // Save previous section if (currentContent.length > 0) { sections[currentSection] = currentContent.join('\n').trim(); } // Start new section currentSection = trimmed.replace(':', '').toLowerCase().replace(/\s+/g, '_'); currentContent = []; } else if (trimmed.length > 0) { currentContent.push(line); } } // Save last section if (currentContent.length > 0) { sections[currentSection] = currentContent.join('\n').trim(); } return sections; } catch (error) { return { raw: output }; } }