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

307 lines (306 loc) 18.8 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 }); const mcpMapper_1 = require("../../src/mcpMapper"); const openapiProcessor_1 = require("../../src/openapiProcessor"); const test_config_1 = require("../fixtures/test-config"); const path_1 = __importDefault(require("path")); const config_1 = require("../../src/config"); describe('MCP Mapper Integration Tests', () => { const originalEnv = Object.assign({}, process.env); let openApiSpec; const originalConfig = Object.assign({}, config_1.config); beforeAll(() => __awaiter(void 0, void 0, void 0, function* () { // Set up environment for tests process.env.OPENAPI_FILE_PATH = path_1.default.resolve(process.cwd(), test_config_1.testConfig.openApiFile); try { openApiSpec = yield (0, openapiProcessor_1.getProcessedOpenApi)(); } catch (error) { console.error('Error loading OpenAPI spec in beforeAll:', error); throw error; } })); beforeEach(() => { // Reset environment variables before each test process.env = Object.assign({}, originalEnv); process.env.OPENAPI_FILE_PATH = path_1.default.resolve(process.cwd(), test_config_1.testConfig.openApiFile); // Reset config to original state Object.assign(config_1.config, originalConfig); }); afterAll(() => { // Restore original environment after all tests process.env = originalEnv; // Restore original config Object.assign(config_1.config, originalConfig); }); it('should map OpenAPI operations to MCP tools', () => __awaiter(void 0, void 0, void 0, function* () { const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); expect(mappedTools).toBeDefined(); expect(Array.isArray(mappedTools)).toBe(true); expect(mappedTools.length).toBe(3); // Based on our sample OpenAPI with 3 operations // Verify mapped tool structure for (const tool of mappedTools) { expect(tool).toHaveProperty('mcpToolDefinition'); expect(tool).toHaveProperty('apiCallDetails'); const { mcpToolDefinition, apiCallDetails } = tool; // Check MCP tool definition expect(mcpToolDefinition).toHaveProperty('name'); expect(mcpToolDefinition).toHaveProperty('description'); expect(mcpToolDefinition).toHaveProperty('inputSchema'); // Check API call details expect(apiCallDetails).toHaveProperty('method'); expect(apiCallDetails).toHaveProperty('pathTemplate'); expect(apiCallDetails).toHaveProperty('serverUrl'); } })); it('should have consistent parameter mapping', () => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Get the getPetById tool for testing parameter mapping const getPetByIdTool = mappedTools.find(t => t.mcpToolDefinition.name === 'getPetById'); expect(getPetByIdTool).toBeDefined(); if (getPetByIdTool) { const { apiCallDetails, mcpToolDefinition } = getPetByIdTool; // Verify path parameter is correctly mapped expect(apiCallDetails.pathTemplate).toContain('{petId}'); expect(mcpToolDefinition.inputSchema.properties).toHaveProperty('petId'); // Check if the schema shows it as required expect(mcpToolDefinition.inputSchema.required).toContain('petId'); // Verify parameter location metadata is correctly set expect((_a = mcpToolDefinition.inputSchema.properties) === null || _a === void 0 ? void 0 : _a.petId).toHaveProperty('x-parameter-location'); expect(((_b = mcpToolDefinition.inputSchema.properties) === null || _b === void 0 ? void 0 : _b.petId)['x-parameter-location']).toBe('path'); } })); it('should preserve format and type information', () => __awaiter(void 0, void 0, void 0, function* () { var _a; const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Get the tool with parameters that have formats const listPetsTool = mappedTools.find(t => t.mcpToolDefinition.name === 'listPets'); expect(listPetsTool).toBeDefined(); if (listPetsTool) { const { mcpToolDefinition } = listPetsTool; // Assuming the 'limit' parameter has a format in the test fixture // This checks that format information is preserved const limitParam = (_a = mcpToolDefinition.inputSchema.properties) === null || _a === void 0 ? void 0 : _a.limit; expect(limitParam).toBeDefined(); if (limitParam) { // Check type is preserved expect(limitParam).toHaveProperty('type'); // If the parameter has a format in the test data, check it's preserved if (limitParam.format) { expect(limitParam).toHaveProperty('format'); } } } })); it('should include path summary in tool description if available', () => __awaiter(void 0, void 0, void 0, function* () { // First, we create a modified OpenAPI spec with a path summary const modifiedSpec = JSON.parse(JSON.stringify(openApiSpec)); // Deep clone // Add summary to path but not to operation if (modifiedSpec.paths && modifiedSpec.paths['/pets/{petId}']) { const pathItem = modifiedSpec.paths['/pets/{petId}']; const operation = pathItem.get; // Ensure operation doesn't have summary or description delete operation.summary; delete operation.description; // Add summary to the path item pathItem.summary = 'Test path summary'; } // Map the modified spec to MCP tools const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(modifiedSpec); // Find the getPetById tool const getPetByIdTool = mappedTools.find(t => t.mcpToolDefinition.name === 'getPetById'); expect(getPetByIdTool).toBeDefined(); if (getPetByIdTool) { // Verify the description includes the path summary expect(getPetByIdTool.mcpToolDefinition.description).toBe('Test path summary'); } })); it('should map request and response types correctly', () => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Check the createPet tool for request body mapping const createPetTool = mappedTools.find(t => t.mcpToolDefinition.name === 'createPet'); expect(createPetTool).toBeDefined(); if (createPetTool) { const { mcpToolDefinition } = createPetTool; // Verify request body mapping expect(mcpToolDefinition.inputSchema.properties).toBeDefined(); // Check for requestBody property which should contain schema expect((_a = mcpToolDefinition.inputSchema.properties) === null || _a === void 0 ? void 0 : _a.requestBody).toBeDefined(); // Check if content types are included expect(((_b = mcpToolDefinition.inputSchema.properties) === null || _b === void 0 ? void 0 : _b.requestBody)['x-content-types']).toBeDefined(); } })); it('should correctly map GET operation with query parameters', () => __awaiter(void 0, void 0, void 0, function* () { var _a; const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); const listPetsTool = mappedTools.find(tool => tool.mcpToolDefinition.name === 'listPets' || (tool.apiCallDetails.operationId && tool.apiCallDetails.operationId === 'listPets')); expect(listPetsTool).toBeDefined(); expect(listPetsTool === null || listPetsTool === void 0 ? void 0 : listPetsTool.mcpToolDefinition.name).toBe('listPets'); expect(listPetsTool === null || listPetsTool === void 0 ? void 0 : listPetsTool.apiCallDetails.method).toBe('GET'); if (listPetsTool) { // Verify query parameter location metadata const limitParam = (_a = listPetsTool.mcpToolDefinition.inputSchema.properties) === null || _a === void 0 ? void 0 : _a.limit; if (limitParam) { expect(limitParam['x-parameter-location']).toBe('query'); } } })); it('should correctly map GET operation with path parameters', () => __awaiter(void 0, void 0, void 0, function* () { const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); const getPetByIdTool = mappedTools.find(tool => tool.mcpToolDefinition.name === 'getPetById' || (tool.apiCallDetails.operationId && tool.apiCallDetails.operationId === 'getPetById')); expect(getPetByIdTool).toBeDefined(); expect(getPetByIdTool === null || getPetByIdTool === void 0 ? void 0 : getPetByIdTool.mcpToolDefinition.name).toBe('getPetById'); expect(getPetByIdTool === null || getPetByIdTool === void 0 ? void 0 : getPetByIdTool.apiCallDetails.method).toBe('GET'); })); it('should correctly map POST operation with request body', () => __awaiter(void 0, void 0, void 0, function* () { const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); const createPetTool = mappedTools.find(tool => tool.mcpToolDefinition.name === 'createPet' || (tool.apiCallDetails.operationId && tool.apiCallDetails.operationId === 'createPet')); expect(createPetTool).toBeDefined(); expect(createPetTool === null || createPetTool === void 0 ? void 0 : createPetTool.mcpToolDefinition.name).toBe('createPet'); expect(createPetTool === null || createPetTool === void 0 ? void 0 : createPetTool.apiCallDetails.method).toBe('POST'); })); it('should filter operations based on whitelist with exact matches', () => __awaiter(void 0, void 0, void 0, function* () { // Test with whitelist for exact operationId matches config_1.config.filter.whitelist = ['listPets', 'getPetById']; config_1.config.filter.blacklist = []; let filteredTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Check only the expected tools are included const toolNames = filteredTools.map(t => t.mcpToolDefinition.name); expect(toolNames).toContain('listPets'); expect(toolNames).toContain('getPetById'); expect(toolNames).not.toContain('createPet'); // Should be filtered out // Reset config for other tests config_1.config.filter.whitelist = null; config_1.config.filter.blacklist = []; })); it('should filter operations based on blacklist with exact matches', () => __awaiter(void 0, void 0, void 0, function* () { // Test with blacklist config config_1.config.filter.whitelist = null; config_1.config.filter.blacklist = ['createPet']; let filteredTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Check excluded tools are not present const filteredToolNames = filteredTools.map(t => t.mcpToolDefinition.name); expect(filteredToolNames).not.toContain('createPet'); expect(filteredToolNames).toContain('listPets'); expect(filteredToolNames).toContain('getPetById'); // Reset config for other tests config_1.config.filter.whitelist = null; config_1.config.filter.blacklist = []; })); it('should filter operations using glob patterns for operationId', () => __awaiter(void 0, void 0, void 0, function* () { // Test with glob pattern for operationId config_1.config.filter.whitelist = ['get*']; config_1.config.filter.blacklist = []; let filteredTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Check only tools matching the pattern are included const toolNames = filteredTools.map(t => t.mcpToolDefinition.name); expect(toolNames).toContain('getPetById'); // Should match 'get*' expect(toolNames).not.toContain('listPets'); // Shouldn't match 'get*' expect(toolNames).not.toContain('createPet'); // Shouldn't match 'get*' // Reset config for other tests config_1.config.filter.whitelist = null; config_1.config.filter.blacklist = []; })); it('should filter operations using glob patterns for URL paths', () => __awaiter(void 0, void 0, void 0, function* () { // Test with glob pattern for URL paths - use the exact path pattern from fixtures config_1.config.filter.whitelist = ['GET:/pets/{petId}']; config_1.config.filter.blacklist = []; let filteredTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Check only tools matching the URL pattern are included const toolNames = filteredTools.map(t => t.mcpToolDefinition.name); expect(toolNames).toContain('getPetById'); // Should match 'GET:/pets/{petId}' expect(toolNames).not.toContain('createPet'); // POST method, shouldn't match // Test with method pattern config_1.config.filter.whitelist = ['POST:/pets']; filteredTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Check only POST operations are included const postToolNames = filteredTools.map(t => t.mcpToolDefinition.name); expect(postToolNames).toContain('createPet'); // Should match 'POST:/pets' expect(postToolNames).not.toContain('getPetById'); // GET method, shouldn't match expect(postToolNames).not.toContain('listPets'); // GET method, shouldn't match // Reset config for other tests config_1.config.filter.whitelist = null; config_1.config.filter.blacklist = []; })); it('should preserve integer types for parameters', () => __awaiter(void 0, void 0, void 0, function* () { var _a; const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(openApiSpec); // Find the listPets tool which should have a limit parameter of type integer const listPetsTool = mappedTools.find(t => t.mcpToolDefinition.name === 'listPets'); expect(listPetsTool).toBeDefined(); if (listPetsTool) { const { mcpToolDefinition } = listPetsTool; // Check if limit parameter exists expect(mcpToolDefinition.inputSchema.properties).toHaveProperty('limit'); // The crucial test: verify the limit parameter is of type integer, not string const limitParam = (_a = mcpToolDefinition.inputSchema.properties) === null || _a === void 0 ? void 0 : _a.limit; console.log('Limit parameter schema:', JSON.stringify(limitParam, null, 2)); // This should be 'integer', not 'string' expect(limitParam).toHaveProperty('type', 'integer'); // Check format is preserved expect(limitParam).toHaveProperty('format', 'int32'); } })); it('should use custom x-mcp extension properties for tool name and description', () => __awaiter(void 0, void 0, void 0, function* () { // Create a modified OpenAPI spec with x-mcp extensions for testing const modifiedSpec = JSON.parse(JSON.stringify(openApiSpec)); // Deep clone // Add x-mcp extension at the operation level if (modifiedSpec.paths && modifiedSpec.paths['/pets'] && modifiedSpec.paths['/pets']['get']) { modifiedSpec.paths['/pets']['get']['x-mcp'] = { name: 'CustomListPets', description: 'Custom description for list pets endpoint from x-mcp extension' }; } // Add x-mcp extension at the path level for a different path if (modifiedSpec.paths && modifiedSpec.paths['/pets/{petId}']) { modifiedSpec.paths['/pets/{petId}']['x-mcp'] = { name: 'CustomPetByIdAPI', description: 'Custom path-level description for pet by ID endpoint' }; } const mappedTools = (0, mcpMapper_1.mapOpenApiToMcpTools)(modifiedSpec); // 1. Test operation-level extension (has priority) const operationExtensionTool = mappedTools.find(tool => tool.apiCallDetails.pathTemplate === '/pets' && tool.apiCallDetails.method === 'GET'); expect(operationExtensionTool).toBeDefined(); if (operationExtensionTool) { // x-mcp extension should override the operationId expect(operationExtensionTool.mcpToolDefinition.name).toBe('CustomListPets'); expect(operationExtensionTool.mcpToolDefinition.description).toBe('Custom description for list pets endpoint from x-mcp extension'); } // 2. Test path-level extension const pathExtensionTool = mappedTools.find(tool => tool.apiCallDetails.pathTemplate === '/pets/{petId}' && tool.apiCallDetails.method === 'GET'); expect(pathExtensionTool).toBeDefined(); if (pathExtensionTool) { // The path-level x-mcp extension should override both name and description expect(pathExtensionTool.mcpToolDefinition.name).toBe('CustomPetByIdAPI'); expect(pathExtensionTool.mcpToolDefinition.description).toBe('Custom path-level description for pet by ID endpoint'); } // 3. Verify that operations without x-mcp extension use default values const regularTool = mappedTools.find(tool => tool.apiCallDetails.pathTemplate === '/pets' && tool.apiCallDetails.method === 'POST'); expect(regularTool).toBeDefined(); if (regularTool) { // Should use the original operationId and description expect(regularTool.mcpToolDefinition.name).toBe('createPet'); } })); });