UNPKG

ludus-mcp

Version:

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

151 lines (130 loc) 4.49 kB
import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; import yaml from 'js-yaml'; import { Logger } from '../utils/logger.js'; export interface LudusConfigOptions { url?: string; apiKey?: string; skipCertVerification?: boolean; proxyUrl?: string; timeout?: number; sshHost?: string; sshUser?: string; } export class LudusConfig { private logger: Logger; private config: LudusConfigOptions; constructor(logger: Logger) { this.logger = logger; this.config = this.loadConfig(); } private loadConfig(): LudusConfigOptions { const config: LudusConfigOptions = {}; // Load from config file first const configPath = join(homedir(), '.config', 'ludus', 'config.yml'); if (existsSync(configPath)) { try { const fileContent = readFileSync(configPath, 'utf8'); const fileConfig = yaml.load(fileContent) as any; // Map config file keys to our interface if (fileConfig.url) config.url = fileConfig.url; if (fileConfig.verify !== undefined) config.skipCertVerification = !fileConfig.verify; if (fileConfig.proxy) config.proxyUrl = fileConfig.proxy; if (fileConfig.ssh_host) config.sshHost = fileConfig.ssh_host; if (fileConfig.ssh_user) config.sshUser = fileConfig.ssh_user; this.logger.debug('Loaded config from file', { path: configPath }); } catch (error) { this.logger.warn('Failed to load config file', { path: configPath, error: error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : String(error) }); } } // Override with environment variables if (process.env.LUDUS_URL) { config.url = process.env.LUDUS_URL; } if (process.env.LUDUS_API_KEY) { config.apiKey = process.env.LUDUS_API_KEY; } if (process.env.LUDUS_VERIFY) { config.skipCertVerification = process.env.LUDUS_VERIFY === 'false'; } if (process.env.LUDUS_PROXY) { config.proxyUrl = process.env.LUDUS_PROXY; } if (process.env.LUDUS_TIMEOUT) { config.timeout = parseInt(process.env.LUDUS_TIMEOUT, 10); } if (process.env.LUDUS_SSH_HOST) { config.sshHost = process.env.LUDUS_SSH_HOST; } if (process.env.LUDUS_SSH_USER) { config.sshUser = process.env.LUDUS_SSH_USER; } // Set defaults if (!config.skipCertVerification) { config.skipCertVerification = false; } if (!config.timeout) { config.timeout = 30000; // 30 seconds } return config; } public getUrl(): string { if (!this.config.url) { throw new Error('Ludus URL not configured. Set LUDUS_URL environment variable or configure in ~/.config/ludusMCP/config.yml'); } return this.config.url; } public getApiKey(): string { if (!this.config.apiKey) { throw new Error('Ludus API key not configured. Set LUDUS_API_KEY environment variable or configure in ~/.config/ludusMCP/config.yml'); } return this.config.apiKey; } public getSkipCertVerification(): boolean { return this.config.skipCertVerification || false; } public getProxyUrl(): string | undefined { return this.config.proxyUrl; } public getTimeout(): number { return this.config.timeout || 30000; } public getSSHHost(): string | undefined { return this.config.sshHost; } public getSSHUser(): string | undefined { return this.config.sshUser; } public validateConfig(): void { const url = this.getUrl(); const apiKey = this.getApiKey(); // Validate URL format try { new URL(url); } catch (error) { throw new Error(`Invalid Ludus URL format: ${url}`); } // Validate API key format (USERID.{40-char-key}) const apiKeyPattern = /^[^.]+\.[a-zA-Z0-9]{40}$/; if (!apiKeyPattern.test(apiKey)) { throw new Error('Invalid API key format. Expected format: USERID.{40-character-key}'); } this.logger.info('Configuration validated successfully', { url, apiKeyUser: apiKey.split('.')[0], skipCertVerification: this.getSkipCertVerification(), timeout: this.getTimeout() }); } public getConfig(): LudusConfigOptions { return { ...this.config }; } }