ludus-mcp
Version:
MCP server for managing Ludus cybersecurity training environments through natural language commands
142 lines (138 loc) • 6.16 kB
JavaScript
export function createDeployRangeTool(logger, cliWrapper) {
return {
name: 'deploy_range',
description: `Deploy a Ludus range from a configuration file. This creates a new virtualized training environment based on the specified configuration.
CREDENTIAL SECURITY REMINDER
Ensure range configurations use credential placeholders: {{LudusCredName-<targetUser>-<credName>}}
DO NOT deploy ranges with non-range-specific credentials such as API keys for external services, passwords not specific to the cyber range environment, or similar credentials embedded in config files!
IMPORTANT LLM BEHAVIORAL PROMPTS:
- SAFETY FIRST: Ludus operations can be destructive and time-consuming
- VERIFY DESTRUCTIVE ACTIONS: Always confirm with user before destroy/delete operations
- CHECK EXISTING STATE: Use list_user_ranges or get_range_status before major operations
- DESTRUCTION IS PERMANENT: Destroying ranges deletes all VMs and data irreversibly
- ADMIN vs USER: Admin operations (--user flag) affect other users' ranges - be explicit
DEPLOYMENT CONSIDERATIONS:
- Deployments take 10-45 minutes depending on complexity
- Windows domains take longer than simple Linux deployments
- Users should monitor progress with get_range_status
- Failed deployments can be debugged with range logs
CRITICAL WORKFLOW REMINDER:
- deploy_range uses the currently SET configuration, not any specific file
- If deploying with a NEW config, you must first use set_range_config to make it active
- Typical workflow: write_range_config → validate_range_config → set_range_config → deploy_range`,
inputSchema: {
type: 'object',
properties: {
user: {
type: 'string',
description: 'User ID to deploy range for (admin only). If omitted, deploys for current user.'
},
configPath: {
type: 'string',
description: 'Path to range configuration YAML file. If omitted, uses existing configuration. ENSURE: Config must use credential placeholders {{LudusCredName-<user>-<cred>}}, NOT actual credentials!'
},
force: {
type: 'boolean',
description: 'Force deployment even if range already exists',
default: false
},
tags: {
type: 'string',
description: 'Ansible tags to run for this deploy (comma-separated, e.g. "dns,custom-groups"). Default: all tags'
},
limit: {
type: 'string',
description: 'Limit deployment to VMs matching the specified pattern (must include localhost or no plays will run)'
},
onlyRoles: {
type: 'string',
description: 'Limit user-defined roles to this comma-separated list (e.g. "role1,role2")'
},
verboseAnsible: {
type: 'boolean',
description: 'Enable verbose output from Ansible during deployment',
default: false
},
help: {
type: 'boolean',
description: 'Show help information for the deploy_range command',
default: false
}
},
required: []
}
};
}
export async function handleDeployRange(args, logger, cliWrapper) {
const { user, configPath, force = false, tags, limit, onlyRoles, verboseAnsible = false, help = false } = args;
// Handle help request
if (help) {
logger.info('Getting help for deploy_range command', { user });
const result = await cliWrapper.executeArbitraryCommand('range', ['deploy', '--help']);
if (result.success) {
return {
success: true,
message: 'Help information for deploy_range command',
help: true,
content: result.rawOutput || result.message
};
}
else {
throw new Error(`Failed to get help: ${result.message}`);
}
}
try {
logger.info('Starting range deployment', {
user,
configPath,
force,
tags,
limit,
onlyRoles,
verboseAnsible
});
// Deploy the range with all options (deployRange handles config setting internally)
logger.info('Deploying range');
// Build options object, filtering out undefined values
const deployOptions = { force, verboseAnsible };
if (user !== undefined)
deployOptions.user = user;
if (configPath !== undefined)
deployOptions.configPath = configPath;
if (tags !== undefined)
deployOptions.tags = tags;
if (limit !== undefined)
deployOptions.limit = limit;
if (onlyRoles !== undefined)
deployOptions.onlyRoles = onlyRoles;
const deployResult = await cliWrapper.deployRange(deployOptions);
if (!deployResult.success) {
throw new Error(`Deployment failed: ${deployResult.message}`);
}
const successMessage = user
? `Range deployment initiated for user ${user}`
: 'Range deployment initiated for current user';
return {
success: true,
message: successMessage,
details: deployResult.data,
user: user || 'current',
configPath: configPath || 'existing configuration',
rawOutput: deployResult.rawOutput
};
}
catch (error) {
logger.error('Range deployment failed', {
user,
configPath,
error: error.message
});
return {
success: false,
message: error.message,
user: user || 'current',
configPath: configPath || 'existing configuration'
};
}
}
//# sourceMappingURL=deployRange.js.map