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
JavaScript
;
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;
});
}