Skip to main content
Glama
index-registry.test.ts10.8 kB
/** * @fileoverview Integration tests for the registry-based MCP server implementation */ import { vi } from 'vitest'; // Mock MCP SDK modules FIRST using unstable_mockModule vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ McpServer: vi.fn().mockImplementation(() => ({ registerTool: vi.fn(), connect: vi.fn().mockResolvedValue(undefined), })), })); vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ StdioServerTransport: vi.fn().mockImplementation(() => ({})), })); // Mock logger vi.mock('../utils/logging/logger.js', () => ({ createLogger: vi.fn(() => ({ info: vi.fn(), debug: vi.fn(), error: vi.fn(), warn: vi.fn(), })), })); // Mock the handlers vi.mock('../handlers/index.js', () => ({ handleProjects: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify([{ key: 'test', name: 'Test Project' }]) }], }), handleDeepsourceQualityMetrics: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ metrics: [] }) }], }), handleDeepsourceUpdateMetricThreshold: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ ok: true }) }], }), handleDeepsourceUpdateMetricSetting: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ success: true }) }], }), handleDeepsourceComplianceReport: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ report: {} }) }], }), handleDeepsourceProjectIssues: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ issues: [] }) }], }), handleDeepsourceProjectRuns: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ runs: [] }) }], }), handleDeepsourceRun: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ run: {} }) }], }), handleDeepsourceRecentRunIssues: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ issues: [] }) }], }), handleDeepsourceDependencyVulnerabilities: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify({ vulnerabilities: [] }) }], }), })); // Mock the server/tool-helpers vi.mock('../server/tool-helpers.js', () => ({ logToolInvocation: vi.fn(), logToolResult: vi.fn(), logAndFormatError: vi.fn().mockReturnValue('Formatted error'), })); // Mock the handler-adapters vi.mock('../adapters/handler-adapters.js', () => ({ adaptQualityMetricsParams: vi.fn((params) => params), adaptUpdateMetricThresholdParams: vi.fn((params) => params), adaptComplianceReportParams: vi.fn((params) => params), adaptProjectIssuesParams: vi.fn((params) => params), })); // Mock zod for schema validation vi.mock('zod', () => ({ z: { object: vi.fn(() => ({ safeParse: vi.fn(() => ({ success: true, data: {} })) })), string: vi.fn(() => ({})), number: vi.fn(() => ({})), boolean: vi.fn(() => ({})), array: vi.fn(() => ({})), optional: vi.fn(() => ({})), }, })); // Mock handler base modules vi.mock('../handlers/base/handler.interface.js', () => ({ HandlerFunction: {}, BaseHandlerDeps: {}, })); vi.mock('../handlers/base/handler.factory.js', () => ({ createDefaultHandlerDeps: vi.fn(), isApiResponse: vi.fn(() => false), createErrorResponse: vi.fn((error) => ({ content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true, })), })); // Mock the ToolRegistry class class MockToolRegistry { private tools: Array<{ name: string; description: string; inputSchema?: Record<string, unknown>; handler: MockedFunction<(...args: unknown[]) => Promise<unknown>>; }> = []; registerTool(tool: { name: string; description: string; inputSchema?: Record<string, unknown>; handler: MockedFunction<(...args: unknown[]) => Promise<unknown>>; }) { if (this.tools.find((t) => t.name === tool.name)) { throw new Error(`Tool ${tool.name} is already registered`); } this.tools.push(tool); } getTools() { return this.tools; } registerWithMcpServer(server: { registerTool: (_config: { name: string; description: string; inputSchema?: unknown; handler: unknown; }) => void; }) { this.tools.forEach((tool) => { server.registerTool({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema, handler: tool.handler, }); }); } } vi.mock('../server/tool-registry.js', () => ({ ToolRegistry: MockToolRegistry, })); // Now import test utilities and modules after mocks are set const { describe, it, expect, beforeEach, afterEach } = await import('vitest'); const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js'); const { ToolRegistry } = await import('../server/tool-registry.js'); const { adaptQualityMetricsParams, adaptUpdateMetricThresholdParams, adaptComplianceReportParams, adaptProjectIssuesParams, } = await import('../adapters/handler-adapters.js'); describe('Registry-based MCP Server Integration', () => { let registry: MockToolRegistry; let mockMcpServer: { registerTool: ReturnType<typeof vi.fn>; connect: MockedFunction<() => Promise<void>>; }; beforeEach(() => { // Create fresh instances for each test mockMcpServer = { registerTool: vi.fn(), connect: vi.fn().mockResolvedValue(undefined), }; const MockedMcpServer = McpServer as ReturnType<typeof vi.fn>; MockedMcpServer.mockImplementation(() => mockMcpServer as ReturnType<typeof McpServer>); registry = new ToolRegistry(); vi.clearAllMocks(); }); afterEach(() => { vi.clearAllMocks(); }); describe('ToolRegistry', () => { it('should register tools successfully', () => { const tool = { name: 'test-tool', description: 'A test tool', inputSchema: { type: 'object', properties: { param: { type: 'string' }, }, }, handler: vi.fn().mockResolvedValue({ success: true }), }; registry.registerTool(tool); const tools = registry.getTools(); expect(tools).toHaveLength(1); expect(tools[0].name).toBe('test-tool'); }); it('should prevent duplicate tool registration', () => { const tool = { name: 'test-tool', description: 'A test tool', handler: vi.fn(), }; registry.registerTool(tool); expect(() => registry.registerTool(tool)).toThrow('Tool test-tool is already registered'); }); it('should register tools with MCP server', () => { const server = new McpServer('test-server', '1.0.0') as ReturnType<typeof McpServer>; const tool = { name: 'test-tool', description: 'A test tool', handler: vi.fn(), }; registry.registerTool(tool); registry.registerWithMcpServer(server); expect(server.registerTool).toHaveBeenCalledWith({ name: 'test-tool', description: 'A test tool', inputSchema: undefined, handler: expect.any(Function), }); }); }); describe('Handler Adapter Integration', () => { it('should correctly adapt quality metrics parameters', () => { const params = { project_key: 'test-project', from_date: '2024-01-01', to_date: '2024-01-31', }; const adapted = adaptQualityMetricsParams(params); expect(adapted).toEqual(params); expect(adaptQualityMetricsParams).toHaveBeenCalledWith(params); }); it('should correctly adapt update metric threshold parameters', () => { const params = { project_key: 'test-project', metric_key: 'coverage', threshold: 80, }; const adapted = adaptUpdateMetricThresholdParams(params); expect(adapted).toEqual(params); expect(adaptUpdateMetricThresholdParams).toHaveBeenCalledWith(params); }); it('should correctly adapt compliance report parameters', () => { const params = { project_key: 'test-project', framework: 'OWASP', }; const adapted = adaptComplianceReportParams(params); expect(adapted).toEqual(params); expect(adaptComplianceReportParams).toHaveBeenCalledWith(params); }); it('should correctly adapt project issues parameters', () => { const params = { project_key: 'test-project', page: 1, limit: 20, category: 'bug', }; const adapted = adaptProjectIssuesParams(params); expect(adapted).toEqual(params); expect(adaptProjectIssuesParams).toHaveBeenCalledWith(params); }); }); describe('End-to-End Tool Registration', () => { it('should handle successful tool execution', async () => { const handler = vi.fn().mockResolvedValue({ content: [{ type: 'text', text: 'Success' }], }); const tool = { name: 'test-tool', description: 'A test tool', inputSchema: { type: 'object', properties: { param: { type: 'string' }, }, required: ['param'], }, handler, }; registry.registerTool(tool); const registeredTool = registry.getTools()[0]; // Call the wrapped handler const result = await registeredTool.handler({ param: 'test' }); expect(handler).toHaveBeenCalledWith({ param: 'test' }); expect(result).toEqual({ content: [{ type: 'text', text: 'Success' }], }); }); it('should handle tool execution errors', async () => { const handler = vi.fn().mockRejectedValue(new Error('Tool error')); const tool = { name: 'test-tool', description: 'A test tool', handler, }; registry.registerTool(tool); const registeredTool = registry.getTools()[0]; // Call the wrapped handler await expect(registeredTool.handler({})).rejects.toThrow('Tool error'); }); }); describe('Schema Validation', () => { it('should validate input schema when provided', async () => { const handler = vi.fn().mockResolvedValue({ success: true }); const tool = { name: 'test-tool', description: 'A test tool', inputSchema: { type: 'object', properties: { required_param: { type: 'string' }, }, required: ['required_param'], }, handler, }; registry.registerTool(tool); const registeredTool = registry.getTools()[0]; // This will be validated by the registry's wrapper await registeredTool.handler({ required_param: 'value' }); expect(handler).toHaveBeenCalledWith({ required_param: 'value' }); }); }); });

Latest Blog Posts

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/sapientpants/deepsource-mcp-server'

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