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

223 lines (222 loc) 10.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getProcessedOpenApi = getProcessedOpenApi; const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser")); // Using our custom implementation instead of the external library const overlay_applier_1 = require("./overlay-applier"); const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const js_yaml_1 = __importDefault(require("js-yaml")); // npm install js-yaml @types/js-yaml const config_1 = require("./config"); const httpClient_1 = require("./utils/httpClient"); /** * Validates an OpenAPI specification for required elements * @param api The OpenAPI specification object * @throws Error if validation fails */ function validateOpenApiSpec(api) { // Check for basic required OpenAPI elements if (!api) { throw new Error('OpenAPI specification is null or undefined'); } if (!api.openapi) { throw new Error('Missing OpenAPI version identifier. This doesn\'t appear to be a valid OpenAPI spec.'); } if (!api.info) { throw new Error('Missing info section in OpenAPI spec'); } if (!api.paths || Object.keys(api.paths).length === 0) { throw new Error('No paths defined in OpenAPI spec. There are no operations to expose as tools.'); } // Validate that at least one path has valid operations let hasValidOperation = false; const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace']; Object.keys(api.paths).forEach(path => { const pathItem = api.paths[path]; if (!pathItem) return; Object.keys(pathItem).forEach(key => { if (httpMethods.includes(key.toLowerCase())) { const operation = pathItem[key]; if (operation) { hasValidOperation = true; } } }); }); if (!hasValidOperation) { throw new Error('No valid operations found in any path. Cannot create tools.'); } console.error('OpenAPI specification validation passed'); } function loadSpec(filePath) { return __awaiter(this, void 0, void 0, function* () { console.error(`Loading OpenAPI spec from: ${filePath}`); try { let api; // Handle HTTP URLs if ((0, httpClient_1.isHttpUrl)(filePath)) { console.error(`Detected HTTP URL for spec: ${filePath}`); // Use our custom HTTP client instead of letting SwaggerParser handle URLs const content = yield (0, httpClient_1.fetchFromUrl)(filePath); // Parse the content based on file extension if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) { api = js_yaml_1.default.load(content); } else { api = JSON.parse(content); } // Manually resolve any references api = yield swagger_parser_1.default.dereference(api); } else { // Use SwaggerParser for local files api = yield swagger_parser_1.default.dereference(filePath); } console.error(`Successfully loaded and dereferenced spec: ${api.info.title} v${api.info.version}`); // Additional validation beyond what SwaggerParser does validateOpenApiSpec(api); return api; } catch (err) { console.error(`Error loading/parsing OpenAPI spec: ${filePath}`, err.message); throw err; } }); } /** * Validates an OpenAPI overlay file structure according to OpenAPI Overlay Specification 1.0.0 * @param overlay The overlay object to validate * @returns True if valid, throws error otherwise */ function validateOverlay(overlay) { if (!overlay) { throw new Error('Overlay is null or undefined'); } // Check if it's a formal OpenAPI Overlay (should have overlay property) if (overlay.overlay) { // Validate formal overlay structure per spec if (typeof overlay.overlay !== 'string') { throw new Error('Overlay version must be a string (e.g., "1.0.0")'); } // Check for required info section if (!overlay.info || typeof overlay.info !== 'object') { throw new Error('Formal overlay is missing info object'); } if (!overlay.info.title || !overlay.info.version) { throw new Error('Overlay info must contain title and version'); } // Check for actions array if (!overlay.actions || !Array.isArray(overlay.actions)) { throw new Error('Formal overlay must have an actions array'); } // Validate each action for (const action of overlay.actions) { if (!action.target) { throw new Error('Each action must have a target JSONPath'); } // An action must have either update or remove property if (action.remove === undefined && action.update === undefined) { throw new Error('Each action must have either update or remove property'); } if (action.remove !== undefined && typeof action.remove !== 'boolean') { throw new Error('Action remove property must be a boolean'); } } } else { // For legacy/simple overlays without the formal structure, // verify that it has some properties that could modify an OpenAPI spec console.error('Warning: Using legacy overlay format, not compliant with OpenAPI Overlay Specification 1.0.0'); const hasValidProperties = overlay.info || overlay.paths || overlay.components || overlay.tags || overlay.servers; if (!hasValidProperties) { throw new Error('Overlay doesn\'t contain any valid OpenAPI modification properties'); } } return true; } function loadOverlay(filePath) { return __awaiter(this, void 0, void 0, function* () { console.error(`Loading overlay file: ${filePath}`); try { let content; if ((0, httpClient_1.isHttpUrl)(filePath)) { // Fetch overlay from HTTP URL content = yield (0, httpClient_1.fetchFromUrl)(filePath); } else { // Load overlay from local file system content = yield promises_1.default.readFile(filePath, 'utf-8'); } const ext = path_1.default.extname(filePath).toLowerCase(); let overlay; if (ext === '.yaml' || ext === '.yml') { overlay = js_yaml_1.default.load(content); } else if (ext === '.json') { overlay = JSON.parse(content); } else { throw new Error(`Unsupported overlay file extension: ${ext}`); } // Validate the overlay structure validateOverlay(overlay); return overlay; } catch (err) { console.error(`Error loading overlay file ${filePath}:`, err.message); throw err; } }); } function getProcessedOpenApi() { return __awaiter(this, void 0, void 0, function* () { let baseApi = yield loadSpec(config_1.config.specPath); if (config_1.config.overlayPaths.length > 0) { console.error(`Applying overlays...`); // Apply each overlay sequentially for (const overlayPath of config_1.config.overlayPaths) { try { // Load the overlay const overlayJson = yield loadOverlay(overlayPath); // Apply the overlay using the OverlayApplier instance const overlayApplier = new overlay_applier_1.OverlayApplier(); baseApi = overlayApplier.apply(baseApi, overlayJson); console.error(`Applied overlay: ${overlayPath}`); } catch (err) { // Decide whether to continue or fail on overlay error console.error(`Failed to apply overlay ${overlayPath}. Continuing without it.`, err); // throw err; // Or re-throw to stop the process } } console.error('Overlays applied successfully.'); } // Ensure servers are present if needed and targetApiBaseUrl isn't set if (!config_1.config.targetApiBaseUrl && (!baseApi.servers || baseApi.servers.length === 0)) { console.error("Warning: No targetApiBaseUrl configured and OpenAPI spec has no servers defined."); // Potentially throw an error if a base URL is absolutely required throw new Error("Cannot determine target API URL. Either configure targetApiBaseUrl or ensure OpenAPI spec includes servers."); } else if (!config_1.config.targetApiBaseUrl) { console.error(`Using server URL from OpenAPI spec: ${baseApi.servers[0].url}`); } return baseApi; }); }