UNPKG

combined-memory-mcp

Version:

MCP server for Combined Memory API - AI-powered chat with unlimited context, memory management, voice agents, and 500+ tool integrations

322 lines (321 loc) 12.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.config = void 0; const dotenv_1 = __importDefault(require("dotenv")); const path_1 = __importDefault(require("path")); const yargs_1 = __importDefault(require("yargs/yargs")); const helpers_1 = require("yargs/helpers"); const fs_1 = __importDefault(require("fs")); const httpClient_1 = require("./utils/httpClient"); dotenv_1.default.config(); // Process command-line arguments first - before any code that uses argv const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)) .option('config', { alias: 'c', type: 'string', description: 'Path to JSON configuration file' }) .option('spec', { alias: 's', type: 'string', description: 'Path to the OpenAPI specification file' }) .option('overlays', { alias: 'o', type: 'string', // Comma-separated paths description: 'Comma-separated paths to OpenAPI overlay files' }) .option('port', { alias: 'p', type: 'number', description: 'Port for the MCP server' }) .option('targetUrl', { alias: 'u', type: 'string', description: 'Target API base URL (overrides OpenAPI servers)' }) .option('whitelist', { alias: 'w', type: 'string', description: 'Comma-separated operationIds or URL paths to include (supports glob patterns)' }) .option('blacklist', { alias: 'b', type: 'string', description: 'Comma-separated operationIds or URL paths to exclude (supports glob patterns, ignored if whitelist used)' }) // Add options for credentials as needed .option('apiKey', { type: 'string', description: 'API Key for the target API' }) .option('securitySchemeName', { type: 'string', description: 'Name of the security scheme requiring the API Key' }) .option('securityCredentials', { type: 'string', description: 'JSON string containing security credentials for multiple schemes' }) .option('headers', { type: 'string', description: 'JSON string containing custom headers to include in all API requests' }) .option('disableXMcp', { type: 'boolean', description: 'Disable adding X-MCP: 1 header to all API requests' }) .help() .parseSync(); // Use parseSync or handle async parsing // Parse custom headers from environment variables const customHeadersFromEnv = {}; Object.keys(process.env).forEach(key => { if (key.startsWith('HEADER_')) { const headerName = key.substring(7); // Remove 'HEADER_' prefix customHeadersFromEnv[headerName] = process.env[key] || ''; } }); // Function to load JSON configuration file function loadJsonConfig(configPath) { try { if (fs_1.default.existsSync(configPath)) { const configContent = fs_1.default.readFileSync(configPath, 'utf8'); const jsonConfig = JSON.parse(configContent); console.error(`Loaded configuration from ${configPath}`); return jsonConfig; } } catch (error) { console.error(`Error loading JSON config from ${configPath}:`, error); } return {}; } // Get the package directory when running via npx function getPackageDirectory() { var _a; try { // When running via npx, __dirname will point to the package's bin directory // We need to go up to the package root (from dist/src/ to package root) const mainModulePath = ((_a = require.main) === null || _a === void 0 ? void 0 : _a.filename) || ''; // For a typical package structure, we need to go up several levels // From <package>/dist/src/server.js to <package> let packageDir = path_1.default.dirname(mainModulePath); // Go up to package root (typically 2 levels) if (packageDir.includes('dist/src')) { packageDir = path_1.default.resolve(packageDir, '../..'); } else if (packageDir.includes('dist')) { packageDir = path_1.default.resolve(packageDir, '..'); } // Verify this looks like a package directory by checking for package.json if (fs_1.default.existsSync(path_1.default.join(packageDir, 'package.json'))) { return packageDir; } } catch (error) { console.error('Error determining package directory:', error); } return null; } // Get config paths to check function getConfigPaths() { // Check if running as a package (via npx) const packageDir = getPackageDirectory(); if (packageDir) { const packageConfigPath = path_1.default.join(packageDir, 'config.json'); console.error(`Checking for package config at: ${packageConfigPath}`); return [packageConfigPath]; } else { // Fallback to current working directory if not running as a package return [ path_1.default.resolve(process.cwd(), 'config.json'), path_1.default.resolve(process.cwd(), 'openapi-mcp.json'), path_1.default.resolve(process.cwd(), '.openapi-mcp.json') ]; } } // Load configuration with proper priority handling for --config flag let jsonConfig = {}; // First check if config is specified via CLI option if (argv.config) { const configPath = path_1.default.resolve(process.cwd(), argv.config); jsonConfig = loadJsonConfig(configPath); } // Then check environment variable else if (process.env.CONFIG_FILE) { jsonConfig = loadJsonConfig(process.env.CONFIG_FILE); } // Finally fall back to default paths else { const configPaths = getConfigPaths(); for (const configPath of configPaths) { const config = loadJsonConfig(configPath); if (Object.keys(config).length > 0) { jsonConfig = config; break; } } } // Apply priority order: CLI arguments > Environment variables > Config file const getValueWithPriority = (cliValue, envValue, configValue, defaultValue) => { if (cliValue !== undefined) return cliValue; if (envValue !== undefined) return envValue; if (configValue !== undefined) return configValue; return defaultValue; }; // Parse environment variables const envValues = { specPath: process.env.OPENAPI_SPEC_PATH, overlays: process.env.OPENAPI_OVERLAY_PATHS, port: process.env.MCP_SERVER_PORT ? parseInt(process.env.MCP_SERVER_PORT, 10) : undefined, targetUrl: process.env.TARGET_API_BASE_URL, whitelist: process.env.MCP_WHITELIST_OPERATIONS, blacklist: process.env.MCP_BLACKLIST_OPERATIONS, apiKey: process.env.API_KEY, securitySchemeName: process.env.SECURITY_SCHEME_NAME, securityCredentials: process.env.SECURITY_CREDENTIALS, headers: process.env.CUSTOM_HEADERS, disableXMcp: process.env.DISABLE_X_MCP === 'true' }; // Apply priority to key configuration values const specPath = getValueWithPriority(argv.spec, envValues.specPath, jsonConfig.spec, ''); const overlays = getValueWithPriority(argv.overlays, envValues.overlays, jsonConfig.overlays, ''); const port = getValueWithPriority(argv.port, envValues.port, jsonConfig.port, 8080); const targetUrl = getValueWithPriority(argv.targetUrl, envValues.targetUrl, jsonConfig.targetUrl, ''); const whitelist = getValueWithPriority(argv.whitelist, envValues.whitelist, jsonConfig.whitelist, ''); const blacklist = getValueWithPriority(argv.blacklist, envValues.blacklist, jsonConfig.blacklist, ''); const apiKey = getValueWithPriority(argv.apiKey, envValues.apiKey, jsonConfig.apiKey, ''); const securitySchemeName = getValueWithPriority(argv.securitySchemeName, envValues.securitySchemeName, jsonConfig.securitySchemeName, ''); if (!specPath) { console.error("Error: OpenAPI specification path is required. Set OPENAPI_SPEC_PATH environment variable, use --spec option, or specify in config file."); process.exit(1); } // Parse security credentials if present let securityCredentials = {}; // CLI has highest priority if (argv.securityCredentials) { try { securityCredentials = JSON.parse(argv.securityCredentials); } catch (e) { console.error('Failed to parse security credentials JSON from CLI:', e); } } else if (envValues.securityCredentials) { // Then environment variables try { securityCredentials = JSON.parse(envValues.securityCredentials); } catch (e) { console.error('Failed to parse security credentials JSON from ENV:', e); } } else if (jsonConfig.securityCredentials) { // Then config file if (typeof jsonConfig.securityCredentials === 'string') { try { securityCredentials = JSON.parse(jsonConfig.securityCredentials); } catch (e) { console.error('Failed to parse security credentials JSON from config file:', e); } } else if (typeof jsonConfig.securityCredentials === 'object') { securityCredentials = jsonConfig.securityCredentials; } } // Parse custom headers with same priority let customHeaders = Object.assign({}, customHeadersFromEnv); // CLI has highest priority if (argv.headers) { try { const headersFromArg = JSON.parse(argv.headers); customHeaders = Object.assign(Object.assign({}, customHeaders), headersFromArg); } catch (e) { console.error('Failed to parse custom headers JSON from CLI:', e); } } else if (envValues.headers) { // Then environment variables try { const headersFromEnv = JSON.parse(envValues.headers); customHeaders = Object.assign(Object.assign({}, customHeaders), headersFromEnv); } catch (e) { console.error('Failed to parse custom headers JSON from ENV:', e); } } else if (jsonConfig.headers) { // Then config file if (typeof jsonConfig.headers === 'string') { try { const headersFromConfig = JSON.parse(jsonConfig.headers); customHeaders = Object.assign(Object.assign({}, customHeaders), headersFromConfig); } catch (e) { console.error('Failed to parse custom headers JSON from config file:', e); } } else if (typeof jsonConfig.headers === 'object') { customHeaders = Object.assign(Object.assign({}, customHeaders), jsonConfig.headers); } } // Determine disableXMcp value with correct priority const disableXMcp = argv.disableXMcp !== undefined ? argv.disableXMcp : envValues.disableXMcp !== undefined ? envValues.disableXMcp : jsonConfig.disableXMcp !== undefined ? jsonConfig.disableXMcp : false; // Generate the final configuration object with correct priorities applied exports.config = { specPath: (0, httpClient_1.isHttpUrl)(specPath) ? specPath : path_1.default.resolve(specPath), overlayPaths: overlays ? overlays.split(',').map((p) => (0, httpClient_1.isHttpUrl)(p.trim()) ? p.trim() : path_1.default.resolve(p.trim())) : [], mcpPort: port, targetApiBaseUrl: targetUrl, // Now properly respects priority apiKey, securitySchemeName, securityCredentials, customHeaders, disableXMcp, filter: { whitelist: whitelist ? whitelist.split(',').map((pattern) => pattern.trim()) : null, blacklist: blacklist ? blacklist.split(',').map((pattern) => pattern.trim()) : [], }, }; console.error('Configuration loaded:'); console.error(`- OpenAPI Spec: ${exports.config.specPath}`); if (exports.config.overlayPaths.length > 0) { console.error(`- Overlays: ${exports.config.overlayPaths.join(', ')}`); } console.error(`- MCP Server Port: ${exports.config.mcpPort}`); if (exports.config.targetApiBaseUrl) { console.error(`- Target API Base URL: ${exports.config.targetApiBaseUrl}`); } else { console.error(`- Target API Base URL: Will use 'servers' from OpenAPI spec.`); } if (exports.config.filter.whitelist) { console.error(`- Whitelist Patterns: ${exports.config.filter.whitelist.join(', ')} (supports glob patterns for operationId and URL paths)`); } else if (exports.config.filter.blacklist.length > 0) { console.error(`- Blacklist Patterns: ${exports.config.filter.blacklist.join(', ')} (supports glob patterns for operationId and URL paths)`); } if (Object.keys(exports.config.securityCredentials).length > 0) { console.error(`- Security Credentials: ${Object.keys(exports.config.securityCredentials).join(', ')}`); } if (exports.config.apiKey) { console.error('- API Key: [REDACTED]'); } if (Object.keys(exports.config.customHeaders).length > 0) { console.error(`- Custom Headers: ${Object.keys(exports.config.customHeaders).join(', ')}`); } console.error(`- X-MCP Header: ${exports.config.disableXMcp ? 'Disabled' : 'Enabled'}`);