mcp-cve-intelligence-server-lite
Version:
Lite Model Context Protocol server for comprehensive CVE intelligence gathering with multi-source exploit discovery, designed for security professionals and cybersecurity researchers
236 lines • 12 kB
JavaScript
/* eslint-disable no-console */
import { Command } from 'commander';
import { createContextLogger } from '../utils/logger.js';
import { VERSION } from '../version.js';
import { setEnvironmentFromCLI, getAppConfiguration, setEnvironmentVariable, setEnvironmentIfNotSet, configManager, } from '../config/index.js';
const logger = createContextLogger('CLIService');
export class CLIService {
program;
version;
constructor() {
// Use auto-generated version from version.ts
this.version = VERSION;
logger.debug('CLIService initialized', { version: this.version });
this.program = new Command();
this.setupCommands();
}
setupCommands() {
this.program
.name('mcp-cve-intelligence-server-lite')
.description('MCP CVE Intelligence Server Lite - Search and analyze CVE vulnerabilities with in-memory storage')
.version(this.version)
.option('-t, --transport <type>', 'transport type (stdio|http)')
.option('-p, --port <number>', 'HTTP port (when using http transport)', '3001')
.option('-H, --host <host>', 'HTTP host (when using http transport)', '0.0.0.0')
.option('-l, --log-level <level>', 'log level (error|warn|info|debug)', 'info')
.option('-c, --sources-config <path>', 'path to custom cve-sources.json file')
.helpOption('-?, --help', 'display help for command');
// Quick start command - no options, just uses optimal defaults
this.program
.command('quick-start [port]')
.description('Start server with optimal defaults for quick testing (optional port as argument)')
.action(async (port) => {
const quickStartOptions = {
port: port || '3001',
};
await this.handleQuickStart(quickStartOptions);
});
// Config command
this.program
.command('config')
.description('Show current configuration and environment')
.option('--json', 'show only the complete JSON configuration')
.option('--full', 'show only the complete JSON configuration (alias for --json)')
.action((options) => {
this.handleConfigCommand(options);
});
// Default action for main command
this.program.action(async (options) => {
await this.handleDefaultCommand(options);
});
}
async handleQuickStart(options) {
logger.debug('Quick start command initiated', { options });
// Use default port if not provided
const port = options.port || '3001';
// Validate port
this.validatePort(port, 'quick-start');
// Set environment variables for quick start using centralized config
setEnvironmentVariable('MCP_TRANSPORT_TYPE', 'http'); // Force HTTP for quick-start
setEnvironmentVariable('MCP_HTTP_PORT', port);
setEnvironmentIfNotSet('MCP_HTTP_HOST', '0.0.0.0');
setEnvironmentIfNotSet('MCP_HTTP_CORS', 'true');
// Reload configuration to pick up the new environment variables
const config = configManager.reload();
logger.debug('Quick start configuration applied', {
port: config.transport.http.port,
host: config.transport.http.host,
cors: config.transport.http.enableCors,
});
this.displayMessage('info', '');
this.displayMessage('info', 'Starting MCP CVE Intelligence Server Lite (Quick Start Mode)');
this.displayMessage('info', 'Transport: HTTP');
this.displayMessage('info', `Port: ${config.transport.http.port}`);
this.displayMessage('info', '');
// Import and start server dynamically to avoid circular dependencies
const { startServer } = await import('./server-launcher.js');
await startServer(config.transport.type);
}
handleConfigCommand(options) {
logger.debug('Config command initiated', { options });
const config = getAppConfiguration();
logger.debug('Current configuration', { config });
// If --json or --full flag is used, show only the JSON configuration
if (options?.json || options?.full) {
try {
// Create a sanitized version of the config for display (only mask sensitive API keys)
const sanitizedConfig = JSON.parse(JSON.stringify(config));
// Only mask API keys in security section - sources config is not sensitive
if (sanitizedConfig.security?.apiKeys) {
for (const [key, value] of Object.entries(sanitizedConfig.security.apiKeys)) {
if (value && typeof value === 'string') {
sanitizedConfig.security.apiKeys[key] = '***configured***';
}
}
}
this.displayMessage('info', JSON.stringify(sanitizedConfig, null, 2));
}
catch (error) {
this.displayMessage('info', `Error serializing configuration: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
process.exit(0);
return;
}
// Default behavior: show formatted configuration summary
this.displayMessage('info', 'MCP CVE Intelligence Server Lite Configuration:');
this.displayMessage('info', '');
// Display CVE Sources Configuration
this.displayMessage('info', 'CVE Sources Configuration:');
try {
if (config.sources && Object.keys(config.sources).length > 0) {
for (const [sourceKey, sourceConfig] of Object.entries(config.sources)) {
const status = sourceConfig.enabled ? '✓ enabled' : '✗ disabled';
const apiKeyStatus = sourceConfig.apiKeyRequired
? (process.env[sourceConfig.apiKeyEnvVar || ''] ? 'with API key' : 'missing API key')
: 'no API key required';
// Display basic info
this.displayMessage('info', ` ${sourceKey}: ${sourceConfig.name}`);
this.displayMessage('info', ` Status: ${status}`);
this.displayMessage('info', ` API Key: ${apiKeyStatus}`);
// Display additional details if available
if ('priority' in sourceConfig) {
this.displayMessage('info', ` Priority: ${sourceConfig.priority}`);
}
if ('features' in sourceConfig && Array.isArray(sourceConfig.features)) {
this.displayMessage('info', ` Features: ${sourceConfig.features.join(', ')}`);
}
this.displayMessage('info', '');
}
}
else {
this.displayMessage('info', ' No sources configured');
this.displayMessage('info', '');
}
}
catch (error) {
this.displayMessage('info', ` Error loading sources: ${error instanceof Error ? error.message : 'Unknown error'}`);
this.displayMessage('info', '');
}
this.displayMessage('info', 'Complete Configuration (JSON):');
this.displayMessage('info', '');
try {
// Create a sanitized version of the config for display (only mask sensitive API keys)
const sanitizedConfig = JSON.parse(JSON.stringify(config));
// Only mask API keys in security section - sources config is not sensitive
if (sanitizedConfig.security?.apiKeys) {
for (const [key, value] of Object.entries(sanitizedConfig.security.apiKeys)) {
if (value && typeof value === 'string') {
sanitizedConfig.security.apiKeys[key] = '***configured***';
}
}
}
this.displayMessage('info', JSON.stringify(sanitizedConfig, null, 2));
}
catch (error) {
this.displayMessage('info', `Error serializing configuration: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
this.displayMessage('info', '');
this.displayMessage('info', 'Available Commands:');
this.displayMessage('info', ' npx mcp-cve-intelligence-server-lite # Start with default settings');
this.displayMessage('info', ' npx mcp-cve-intelligence-server-lite quick-start # HTTP mode for testing');
this.displayMessage('info', ' npx mcp-cve-intelligence-server-lite --help # Show all options');
this.displayMessage('info', '');
this.displayMessage('info', 'MCP Client Configuration Examples:');
this.displayMessage('info', ' See README.md for VS Code and Claude Desktop setup');
// Exit the process after displaying configuration
process.exit(0);
}
async handleDefaultCommand(options) {
logger.debug('Default command initiated', { options });
// Validate CLI options
this.validateTransport(options.transport);
this.validatePort(options.port, 'main command');
this.validateLogLevel(options.logLevel);
// Set environment variables from CLI options - ONLY if not already set by environment
this.setEnvironmentFromOptions(options);
// Reload configuration to pick up CLI options (especially custom sources config)
const config = configManager.reload();
logger.info('Starting server with configuration', {
transport: config.transport.type,
logLevel: config.logging.level,
});
// Import and start server dynamically to avoid circular dependencies
const { startServer } = await import('./server-launcher.js');
await startServer(config.transport.type);
}
setEnvironmentFromOptions(options) {
logger.debug('Setting environment from CLI options', { options });
// Use centralized configuration management
setEnvironmentFromCLI(options);
logger.debug('Environment variables set from CLI options');
}
displayMessage(level, ...args) {
if (level === 'debug') {
console.log('DEBUG -', ...args);
}
else {
console.log(...args);
}
}
validatePort(port, commandName) {
if (port) {
const portNum = parseInt(port, 10);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
logger.error(`Invalid port number for ${commandName}: ${port}`);
this.displayMessage('info', `Error: Invalid port number '${port}'. Port must be between 1 and 65535.`);
process.exit(1);
}
if (portNum < 1024) {
logger.warn(`Port ${portNum} requires elevated privileges`);
this.displayMessage('info', `Warning: Port ${portNum} may require elevated privileges (sudo/root).`);
}
}
}
validateTransport(transport) {
if (transport && !['stdio', 'http'].includes(transport)) {
logger.error(`Invalid transport type: ${transport}`);
this.displayMessage('info', `Error: Invalid transport type '${transport}'. Must be 'stdio' or 'http'.`);
process.exit(1);
}
}
validateLogLevel(logLevel) {
if (logLevel && !['error', 'warn', 'info', 'debug'].includes(logLevel)) {
logger.error(`Invalid log level: ${logLevel}`);
this.displayMessage('info', `Error: Invalid log level '${logLevel}'. Must be 'error', 'warn', 'info', or 'debug'.`);
process.exit(1);
}
}
parse(argv) {
logger.debug('Parsing CLI arguments', { argv });
this.program.parse(argv);
}
getProgram() {
return this.program;
}
}
//# sourceMappingURL=cli-service.js.map