import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { Logger } from '../utils/logger.js';
import { LudusCliWrapper } from '../ludusMCP/cliWrapper.js';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
export function createSetRangeConfigTool(logger: Logger, cliWrapper: LudusCliWrapper): Tool {
return {
name: 'set_range_config',
description: `Set a range configuration in Ludus using an existing configuration file.
**WORKING DIRECTORY**: All file paths are relative to ~/.ludus-mcp/range-config-templates/
**PATH USAGE**:
- Use relative paths only: "base-configs/acme.yml", "user1/config.yaml", "mp/settings.yml"
- DO NOT use: "~/.ludus-mcp/...", "/absolute/paths", or "../traversal"
**EXAMPLES**:
- file: "base-configs/acme.yml" → sets from ~/.ludus-mcp/range-config-templates/base-configs/acme.yml
- file: "user1/config.yaml" → sets from ~/.ludus-mcp/range-config-templates/user1/config.yaml
CRITICAL LLM BEHAVIOR: CREDENTIAL REDACTION REQUIRED
When displaying configuration content in conversation, you MUST replace any non-range-specific credentials such as API keys for external services, passwords not specific to the cyber range environment, or similar credentials with "REDACTED-CREDENTIAL" to prevent credential exposure in chat logs.
IMPORTANT LLM BEHAVIORAL PROMPTS:
- SAFETY FIRST: Ludus operations can be destructive and time-consuming
- CHECK EXISTING STATE: Use list_user_ranges or get_range_status before major operations
- ADMIN vs USER: Admin operations (--user flag) affect other users' ranges - be explicit
- Configuration changes overwrite existing settings - warn users about data loss
- Force flag should only be used when explicitly requested by the user
- REDACT credentials when showing config content to users
CRITICAL WORKFLOW REMINDER:
- This step SETS the active configuration - it's required before deployment
- deploy_range uses the currently SET config, not just any config file
- Always set_range_config first, then deploy_range
- Typical workflow: write_range_config → validate_range_config → set_range_config → deploy_range`,
inputSchema: {
type: 'object',
properties: {
file: {
type: 'string',
description: 'Relative file path (e.g., "base-configs/acme.yml") of the range configuration file. Must be within ~/.ludus-mcp/range-config-templates/',
},
user: {
type: 'string',
description: 'User ID to set the configuration for (admin only)',
},
force: {
type: 'boolean',
description: 'Force the configuration update even with testing enabled',
default: false,
},
verbose: {
type: 'boolean',
description: 'Enable verbose output',
default: false,
},
},
required: ['file'],
},
};
}
export interface SetRangeConfigArgs {
file: string;
user?: string;
force?: boolean;
verbose?: boolean;
}
export async function handleSetRangeConfig(
args: SetRangeConfigArgs,
logger: Logger,
cliWrapper: LudusCliWrapper
): Promise<any> {
const { file, user, force = false, verbose = false } = args;
logger.info('Setting range configuration', { file, user, force, verbose });
try {
// Resolve the file path
const homeDir = os.homedir();
const configTemplatesDir = path.join(homeDir, '.ludus-mcp', 'range-config-templates');
const resolvedFilePath = path.join(configTemplatesDir, file);
// Ensure the base directory exists
if (!fs.existsSync(configTemplatesDir)) {
fs.mkdirSync(configTemplatesDir, { recursive: true });
logger.warn('Created ~/.ludus-mcp/range-config-templates directory', { path: configTemplatesDir });
}
// Security validation: ensure the resolved path is within allowed boundaries
const relativePath = path.relative(configTemplatesDir, resolvedFilePath);
if (relativePath && (relativePath.startsWith('..') || path.isAbsolute(relativePath))) {
const errorMessage = `Access denied: file path '${file}' is outside allowed directory. Files must be within ~/.ludus-mcp/range-config-templates/`;
logger.error('Security validation failed for file access', {
file,
resolvedFilePath,
relativePath,
configTemplatesDir
});
return {
success: false,
file: file,
error: errorMessage,
message: `Failed to set range configuration: ${errorMessage}`
};
}
// Check if the file exists
if (!fs.existsSync(resolvedFilePath)) {
const errorMessage = `Configuration file not found: ${file}`;
logger.error('Configuration file not found', { file, resolvedFilePath });
return {
success: false,
file: file,
error: errorMessage,
message: `Failed to set range configuration: ${errorMessage}`
};
}
// Build the command arguments
const cmdArgs: string[] = ['config', 'set'];
// Add file flag
cmdArgs.push('-f', resolvedFilePath);
// Add optional flags
if (force) {
cmdArgs.push('--force');
}
if (verbose) {
cmdArgs.push('--verbose');
}
if (user) {
cmdArgs.push('--user', user);
}
// Execute the command
const result = await cliWrapper.executeCommand('range', cmdArgs);
const targetUser = user || 'current user';
if (result.success) {
return {
content: [
{
type: 'text',
text: `Range configuration set successfully for ${targetUser}\n\n` +
`Configuration file: ${file}\n` +
`${force ? ' Force mode was enabled\n' : ''}` +
`Output:\n` +
`\`\`\`\n${result.rawOutput || result.message}\n\`\`\`\n\n` +
`Next steps:\n` +
`- Use get_range_status to check current range state\n` +
`- Use deploy_range to deploy this configuration\n` +
`- Use list_user_ranges to see all configured ranges`
}
]
};
} else {
return {
content: [
{
type: 'text',
text: `Failed to set range configuration for ${targetUser}\n\n` +
`Configuration file: ${file}\n` +
`Error Details:\n` +
`\`\`\`\n${result.rawOutput || result.message}\n\`\`\`\n\n` +
`Troubleshooting:\n` +
`- Verify the configuration file exists and is valid\n` +
`- Check file permissions and path accessibility\n` +
`- Use validate_range_config to check the file first\n` +
`- Try with --force flag if testing mode is preventing the update\n` +
`- Ensure you have permissions for the specified user`
}
]
};
}
} catch (error: any) {
logger.error('Set range config failed', { file, user, error: error.message });
return {
content: [
{
type: 'text',
text: `Error setting range configuration\n\n` +
`File: ${file}\n` +
`👤 User: ${user || 'current user'}\n` +
`Error: ${error.message}\n\n` +
`Troubleshooting:\n` +
`- Verify Ludus CLI is properly configured\n` +
`- Check network connectivity to Ludus server\n` +
`- Ensure the configuration file path is correct\n` +
`- Validate the config file format with validate_range_config`
}
]
};
}
}