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