Skip to main content
Glama

OpenAPI to MCP Server

mcpMapper.test.ts15.5 kB
import { mapOpenApiToMcpTools } from '../../src/mcpMapper'; import { getProcessedOpenApi } from '../../src/openapiProcessor'; import { testConfig } from '../fixtures/test-config'; import { TestMappedTool } from '../utils/testTypes'; import path from 'path'; import { config } from '../../src/config'; describe('MCP Mapper Integration Tests', () => { const originalEnv = { ...process.env }; let openApiSpec: any; const originalConfig = { ...config }; beforeAll(async () => { // Set up environment for tests process.env.OPENAPI_FILE_PATH = path.resolve(process.cwd(), testConfig.openApiFile); try { openApiSpec = await getProcessedOpenApi(); } catch (error) { console.error('Error loading OpenAPI spec in beforeAll:', error); throw error; } }); beforeEach(() => { // Reset environment variables before each test process.env = { ...originalEnv }; process.env.OPENAPI_FILE_PATH = path.resolve(process.cwd(), testConfig.openApiFile); // Reset config to original state Object.assign(config, originalConfig); }); afterAll(() => { // Restore original environment after all tests process.env = originalEnv; // Restore original config Object.assign(config, originalConfig); }); it('should map OpenAPI operations to MCP tools', async () => { const mappedTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; 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', async () => { const mappedTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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(mcpToolDefinition.inputSchema.properties?.petId).toHaveProperty('x-parameter-location'); expect((mcpToolDefinition.inputSchema.properties?.petId as any)['x-parameter-location']).toBe('path'); } }); it('should preserve format and type information', async () => { const mappedTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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 = mcpToolDefinition.inputSchema.properties?.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 as any).format) { expect(limitParam).toHaveProperty('format'); } } } }); it('should include path summary in tool description if available', async () => { // 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 = mapOpenApiToMcpTools(modifiedSpec) as TestMappedTool[]; // 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', async () => { const mappedTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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(mcpToolDefinition.inputSchema.properties?.requestBody).toBeDefined(); // Check if content types are included expect((mcpToolDefinition.inputSchema.properties?.requestBody as any)['x-content-types']).toBeDefined(); } }); it('should correctly map GET operation with query parameters', async () => { const mappedTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; const listPetsTool = mappedTools.find(tool => tool.mcpToolDefinition.name === 'listPets' || (tool.apiCallDetails.operationId && tool.apiCallDetails.operationId === 'listPets') ); expect(listPetsTool).toBeDefined(); expect(listPetsTool?.mcpToolDefinition.name).toBe('listPets'); expect(listPetsTool?.apiCallDetails.method).toBe('GET'); if (listPetsTool) { // Verify query parameter location metadata const limitParam = listPetsTool.mcpToolDefinition.inputSchema.properties?.limit; if (limitParam) { expect((limitParam as any)['x-parameter-location']).toBe('query'); } } }); it('should correctly map GET operation with path parameters', async () => { const mappedTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; const getPetByIdTool = mappedTools.find(tool => tool.mcpToolDefinition.name === 'getPetById' || (tool.apiCallDetails.operationId && tool.apiCallDetails.operationId === 'getPetById') ); expect(getPetByIdTool).toBeDefined(); expect(getPetByIdTool?.mcpToolDefinition.name).toBe('getPetById'); expect(getPetByIdTool?.apiCallDetails.method).toBe('GET'); }); it('should correctly map POST operation with request body', async () => { const mappedTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; const createPetTool = mappedTools.find(tool => tool.mcpToolDefinition.name === 'createPet' || (tool.apiCallDetails.operationId && tool.apiCallDetails.operationId === 'createPet') ); expect(createPetTool).toBeDefined(); expect(createPetTool?.mcpToolDefinition.name).toBe('createPet'); expect(createPetTool?.apiCallDetails.method).toBe('POST'); }); it('should filter operations based on whitelist with exact matches', async () => { // Test with whitelist for exact operationId matches config.filter.whitelist = ['listPets', 'getPetById']; config.filter.blacklist = []; let filteredTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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.filter.whitelist = null; config.filter.blacklist = []; }); it('should filter operations based on blacklist with exact matches', async () => { // Test with blacklist config config.filter.whitelist = null; config.filter.blacklist = ['createPet']; let filteredTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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.filter.whitelist = null; config.filter.blacklist = []; }); it('should filter operations using glob patterns for operationId', async () => { // Test with glob pattern for operationId config.filter.whitelist = ['get*']; config.filter.blacklist = []; let filteredTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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.filter.whitelist = null; config.filter.blacklist = []; }); it('should filter operations using glob patterns for URL paths', async () => { // Test with glob pattern for URL paths - use the exact path pattern from fixtures config.filter.whitelist = ['GET:/pets/{petId}']; config.filter.blacklist = []; let filteredTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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.filter.whitelist = ['POST:/pets']; filteredTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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.filter.whitelist = null; config.filter.blacklist = []; }); it('should preserve integer types for parameters', async () => { const mappedTools = mapOpenApiToMcpTools(openApiSpec) as TestMappedTool[]; // 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 = mcpToolDefinition.inputSchema.properties?.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', async () => { // 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 = mapOpenApiToMcpTools(modifiedSpec) as TestMappedTool[]; // 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'); } }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TykTechnologies/api-to-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server