Skip to main content
Glama

Context Pods

by conorluddy
protocol-compliance.test.tsโ€ข22 kB
/** * Unit tests for MCP Protocol Compliance Testing * Tests the functionality of the MCP compliance test suite */ import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest'; import { MCPComplianceTestSuite } from '../../src/protocol/compliance.js'; import { MCPMessageTestHarness } from '../../src/protocol/messages.js'; import { MCPProtocolValidator } from '../../src/protocol/validator.js'; import { TestStatus } from '../../src/types.js'; // Mock dependencies vi.mock('../../src/protocol/messages.js', () => ({ MCPMessageTestHarness: vi.fn(), })); vi.mock('../../src/protocol/validator.js', () => ({ MCPProtocolValidator: vi.fn(), })); vi.mock('@context-pods/core', () => ({ logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), }, })); describe('MCPComplianceTestSuite', () => { let complianceSuite: MCPComplianceTestSuite; let mockHarness: { startServer: Mock; stopServer: Mock; initialize: Mock; listTools: Mock; callTool: Mock; listResources: Mock; readResource: Mock; listPrompts: Mock; getPrompt: Mock; sendInvalidMessage: Mock; callNonExistentMethod: Mock; sendInvalidParams: Mock; sendNotification: Mock; sendBatchRequest: Mock; sendMessage: Mock; }; let mockValidator: { validateMessage: Mock; validateToolDeclaration: Mock; validateResourceDeclaration: Mock; validatePromptDeclaration: Mock; }; beforeEach(() => { vi.clearAllMocks(); // Create mock harness mockHarness = { startServer: vi.fn(), stopServer: vi.fn(), initialize: vi.fn(), listTools: vi.fn(), callTool: vi.fn(), listResources: vi.fn(), readResource: vi.fn(), listPrompts: vi.fn(), getPrompt: vi.fn(), sendInvalidMessage: vi.fn(), callNonExistentMethod: vi.fn(), sendInvalidParams: vi.fn(), sendNotification: vi.fn(), sendBatchRequest: vi.fn(), sendMessage: vi.fn(), }; // Create mock validator mockValidator = { validateMessage: vi.fn(), validateToolDeclaration: vi.fn(), validateResourceDeclaration: vi.fn(), validatePromptDeclaration: vi.fn(), }; // Mock constructors vi.mocked(MCPMessageTestHarness).mockImplementation(() => mockHarness as any); vi.mocked(MCPProtocolValidator).mockImplementation(() => mockValidator as any); // Set up default successful responses mockValidator.validateMessage.mockReturnValue({ valid: true }); mockValidator.validateToolDeclaration.mockReturnValue(true); mockValidator.validateResourceDeclaration.mockReturnValue(true); mockValidator.validatePromptDeclaration.mockReturnValue(true); // Create compliance suite instance complianceSuite = new MCPComplianceTestSuite('/path/to/server', false); }); describe('Constructor', () => { it('should create harness with correct configuration', () => { expect(MCPMessageTestHarness).toHaveBeenCalledWith({ serverPath: '/path/to/server', transport: 'stdio', timeout: 5000, debug: false, }); }); it('should create harness with debug enabled', () => { new MCPComplianceTestSuite('/path/to/debug-server', true); expect(MCPMessageTestHarness).toHaveBeenCalledWith({ serverPath: '/path/to/debug-server', transport: 'stdio', timeout: 5000, debug: true, }); }); it('should create protocol validator', () => { expect(MCPProtocolValidator).toHaveBeenCalled(); }); }); describe('Full Test Suite', () => { it('should run complete test suite successfully', async () => { // Set up successful initialization mockHarness.initialize.mockResolvedValue({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server', version: '1.0.0' }, capabilities: { tools: {}, resources: {}, prompts: {} }, }, }); // Set up successful tool listing mockHarness.listTools.mockResolvedValue({ result: { tools: [ { name: 'test-tool', description: 'A test tool', inputSchema: {} }, ], }, }); // Set up successful tool call mockHarness.callTool.mockResolvedValue({ result: { content: [{ type: 'text', text: 'Tool result' }], }, }); // Set up successful resource listing mockHarness.listResources.mockResolvedValue({ result: { resources: [ { uri: 'test://resource', name: 'Test Resource', mimeType: 'text/plain' }, ], }, }); // Set up successful resource reading mockHarness.readResource.mockResolvedValue({ result: { contents: [{ uri: 'test://resource', mimeType: 'text/plain', text: 'Resource content' }], }, }); // Set up successful prompt listing mockHarness.listPrompts.mockResolvedValue({ result: { prompts: [ { name: 'test-prompt', description: 'A test prompt' }, ], }, }); // Set up successful prompt getting mockHarness.getPrompt.mockResolvedValue({ result: { messages: [{ role: 'user', content: { type: 'text', text: 'Test prompt' } }], }, }); // Set up error responses for error handling tests mockHarness.callTool.mockResolvedValueOnce({ result: { content: [{ type: 'text', text: 'Tool result' }] }, }).mockResolvedValueOnce({ error: { code: -32601, message: 'Method not found' }, }); mockHarness.readResource.mockResolvedValueOnce({ result: { contents: [{ uri: 'test://resource', mimeType: 'text/plain', text: 'Content' }] }, }).mockResolvedValueOnce({ error: { code: -32000, message: 'Resource not found' }, }); mockHarness.sendInvalidMessage.mockResolvedValue({ error: { code: -32700, message: 'Parse error' }, }); mockHarness.callNonExistentMethod.mockResolvedValue({ error: { code: -32601, message: 'Method not found' }, }); mockHarness.sendInvalidParams.mockResolvedValue({ error: { code: -32602, message: 'Invalid params' }, }); mockHarness.sendBatchRequest.mockResolvedValue([ { id: 1, result: { tools: [] } }, { id: 2, result: { resources: [] } }, ]); mockHarness.sendMessage.mockResolvedValue({ id: 1, result: { tools: [] }, }); const result = await complianceSuite.runFullSuite(); expect(result.name).toBe('MCP Compliance Test Suite'); expect(result.tests.length).toBeGreaterThan(0); expect(result.passed).toBeGreaterThan(0); expect(result.duration).toBeGreaterThan(0); // Verify server lifecycle expect(mockHarness.startServer).toHaveBeenCalled(); expect(mockHarness.stopServer).toHaveBeenCalled(); }); it('should handle server startup failure', async () => { mockHarness.startServer.mockRejectedValue(new Error('Server failed to start')); const result = await complianceSuite.runFullSuite(); expect(result.failed).toBeGreaterThan(0); expect(result.tests.some(t => t.error === 'Server failed to start')).toBe(true); expect(mockHarness.stopServer).toHaveBeenCalled(); }); it('should handle test execution errors gracefully', async () => { mockHarness.initialize.mockRejectedValue(new Error('Initialization failed')); const result = await complianceSuite.runFullSuite(); expect(result.tests.length).toBeGreaterThan(0); expect(result.failed).toBeGreaterThan(0); }); }); describe('Initialization Tests', () => { beforeEach(() => { mockHarness.startServer.mockResolvedValue(undefined); }); it('should test basic initialization successfully', async () => { mockHarness.initialize.mockResolvedValue({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server', version: '1.0.0' }, capabilities: {}, }, }); const result = await complianceSuite.runFullSuite(); const initTest = result.tests.find(t => t.name === 'Basic Initialization'); expect(initTest?.status).toBe(TestStatus.PASSED); }); it('should fail when protocol version is missing', async () => { mockHarness.initialize.mockResolvedValue({ result: { serverInfo: { name: 'Test Server' }, }, }); const result = await complianceSuite.runFullSuite(); const initTest = result.tests.find(t => t.name === 'Basic Initialization'); expect(initTest?.status).toBe(TestStatus.FAILED); expect(initTest?.error).toContain('Missing protocol version'); }); it('should test capability negotiation', async () => { mockHarness.initialize.mockResolvedValue({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server' }, capabilities: { tools: {} }, }, }); const result = await complianceSuite.runFullSuite(); const capTest = result.tests.find(t => t.name === 'Capability Negotiation'); expect(capTest?.status).toBe(TestStatus.PASSED); expect(mockHarness.initialize).toHaveBeenCalledWith({ tools: false, resources: true, prompts: false, }); }); it('should test double initialization prevention', async () => { mockHarness.initialize .mockResolvedValueOnce({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test' } }, }) .mockResolvedValueOnce({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test' } }, }) .mockResolvedValueOnce({ error: { code: -32000, message: 'Already initialized' }, }); const result = await complianceSuite.runFullSuite(); const doubleInitTest = result.tests.find(t => t.name === 'Double Initialization Prevention'); expect(doubleInitTest?.status).toBe(TestStatus.PASSED); }); }); describe('Tools Capability Tests', () => { beforeEach(() => { mockHarness.startServer.mockResolvedValue(undefined); mockHarness.initialize.mockResolvedValue({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server' }, capabilities: { tools: {} }, }, }); }); it('should test tool listing successfully', async () => { mockHarness.listTools.mockResolvedValue({ result: { tools: [ { name: 'test-tool', description: 'A test tool', inputSchema: {} }, ], }, }); const result = await complianceSuite.runFullSuite(); const listTest = result.tests.find(t => t.name === 'List Tools'); expect(listTest?.status).toBe(TestStatus.PASSED); expect(mockValidator.validateToolDeclaration).toHaveBeenCalled(); }); it('should fail when tools response is invalid', async () => { mockHarness.listTools.mockResolvedValue({ result: {}, // Missing tools array }); const result = await complianceSuite.runFullSuite(); const listTest = result.tests.find(t => t.name === 'List Tools'); expect(listTest?.status).toBe(TestStatus.FAILED); expect(listTest?.error).toContain('Missing tools'); }); it('should test tool calling with valid tool', async () => { mockHarness.listTools.mockResolvedValue({ result: { tools: [{ name: 'test-tool', description: 'Test', inputSchema: {} }], }, }); mockHarness.callTool.mockResolvedValue({ result: { content: [{ type: 'text', text: 'Tool executed' }], }, }); const result = await complianceSuite.runFullSuite(); const callTest = result.tests.find(t => t.name === 'Call Tool'); expect(callTest?.status).toBe(TestStatus.PASSED); expect(mockHarness.callTool).toHaveBeenCalledWith('test-tool'); }); it('should test non-existent tool call error handling', async () => { mockHarness.listTools.mockResolvedValue({ result: { tools: [] } }); mockHarness.callTool.mockResolvedValue({ error: { code: -32601, message: 'Method not found' }, }); const result = await complianceSuite.runFullSuite(); const errorTest = result.tests.find(t => t.name === 'Call Non-Existent Tool'); expect(errorTest?.status).toBe(TestStatus.PASSED); }); }); describe('Resources Capability Tests', () => { beforeEach(() => { mockHarness.startServer.mockResolvedValue(undefined); mockHarness.initialize.mockResolvedValue({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server' }, capabilities: { resources: {} }, }, }); }); it('should test resource listing successfully', async () => { mockHarness.listResources.mockResolvedValue({ result: { resources: [ { uri: 'test://resource', name: 'Test Resource', mimeType: 'text/plain' }, ], }, }); const result = await complianceSuite.runFullSuite(); const listTest = result.tests.find(t => t.name === 'List Resources'); expect(listTest?.status).toBe(TestStatus.PASSED); expect(mockValidator.validateResourceDeclaration).toHaveBeenCalled(); }); it('should test resource reading with valid resource', async () => { mockHarness.listResources.mockResolvedValue({ result: { resources: [{ uri: 'test://resource', name: 'Test', mimeType: 'text/plain' }], }, }); mockHarness.readResource.mockResolvedValue({ result: { contents: [{ uri: 'test://resource', mimeType: 'text/plain', text: 'Content' }], }, }); const result = await complianceSuite.runFullSuite(); const readTest = result.tests.find(t => t.name === 'Read Resource'); expect(readTest?.status).toBe(TestStatus.PASSED); expect(mockHarness.readResource).toHaveBeenCalledWith('test://resource'); }); it('should test non-existent resource error handling', async () => { mockHarness.listResources.mockResolvedValue({ result: { resources: [] } }); mockHarness.readResource.mockResolvedValue({ error: { code: -32000, message: 'Resource not found' }, }); const result = await complianceSuite.runFullSuite(); const errorTest = result.tests.find(t => t.name === 'Read Non-Existent Resource'); expect(errorTest?.status).toBe(TestStatus.PASSED); }); }); describe('Prompts Capability Tests', () => { beforeEach(() => { mockHarness.startServer.mockResolvedValue(undefined); mockHarness.initialize.mockResolvedValue({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server' }, capabilities: { prompts: {} }, }, }); }); it('should test prompt listing successfully', async () => { mockHarness.listPrompts.mockResolvedValue({ result: { prompts: [ { name: 'test-prompt', description: 'A test prompt' }, ], }, }); const result = await complianceSuite.runFullSuite(); const listTest = result.tests.find(t => t.name === 'List Prompts'); expect(listTest?.status).toBe(TestStatus.PASSED); expect(mockValidator.validatePromptDeclaration).toHaveBeenCalled(); }); it('should test prompt getting with valid prompt', async () => { mockHarness.listPrompts.mockResolvedValue({ result: { prompts: [{ name: 'test-prompt', description: 'Test' }], }, }); mockHarness.getPrompt.mockResolvedValue({ result: { messages: [{ role: 'user', content: { type: 'text', text: 'Prompt content' } }], }, }); const result = await complianceSuite.runFullSuite(); const getTest = result.tests.find(t => t.name === 'Get Prompt'); expect(getTest?.status).toBe(TestStatus.PASSED); expect(mockHarness.getPrompt).toHaveBeenCalledWith('test-prompt'); }); }); describe('Error Handling Tests', () => { beforeEach(() => { mockHarness.startServer.mockResolvedValue(undefined); mockHarness.initialize.mockResolvedValue({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server' }, capabilities: {}, }, }); }); it('should test invalid JSON handling', async () => { mockHarness.sendInvalidMessage.mockRejectedValue(new Error('Parse error')); const result = await complianceSuite.runFullSuite(); const jsonTest = result.tests.find(t => t.name === 'Invalid JSON Handling'); expect(jsonTest?.status).toBe(TestStatus.PASSED); }); it('should test missing required fields', async () => { mockHarness.sendInvalidMessage.mockResolvedValue({ error: { code: -32600, message: 'Invalid request' }, }); const result = await complianceSuite.runFullSuite(); const fieldsTest = result.tests.find(t => t.name === 'Missing Required Fields'); expect(fieldsTest?.status).toBe(TestStatus.PASSED); }); it('should test invalid method error', async () => { mockHarness.callNonExistentMethod.mockResolvedValue({ error: { code: -32601, message: 'Method not found' }, }); const result = await complianceSuite.runFullSuite(); const methodTest = result.tests.find(t => t.name === 'Invalid Method'); expect(methodTest?.status).toBe(TestStatus.PASSED); }); it('should test invalid parameters error', async () => { mockHarness.sendInvalidParams.mockResolvedValue({ error: { code: -32602, message: 'Invalid params' }, }); const result = await complianceSuite.runFullSuite(); const paramsTest = result.tests.find(t => t.name === 'Invalid Parameters'); expect(paramsTest?.status).toBe(TestStatus.PASSED); }); }); describe('JSON-RPC Compliance Tests', () => { beforeEach(() => { mockHarness.startServer.mockResolvedValue(undefined); mockHarness.initialize.mockResolvedValue({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server' }, capabilities: {}, }, }); }); it('should test notification handling', async () => { mockHarness.sendNotification.mockResolvedValue(undefined); const result = await complianceSuite.runFullSuite(); const notificationTest = result.tests.find(t => t.name === 'JSON-RPC Notification'); expect(notificationTest?.status).toBe(TestStatus.PASSED); expect(mockHarness.sendNotification).toHaveBeenCalledWith('test/notification', { data: 'test' }); }); it('should test batch request handling', async () => { mockHarness.sendBatchRequest.mockResolvedValue([ { id: 1, result: { tools: [] } }, { id: 2, result: { resources: [] } }, ]); const result = await complianceSuite.runFullSuite(); const batchTest = result.tests.find(t => t.name === 'JSON-RPC Batch Request'); expect(batchTest?.status).toBe(TestStatus.PASSED); }); it('should test ID matching for different ID types', async () => { mockHarness.sendMessage .mockResolvedValueOnce({ id: 1, result: { tools: [] } }) .mockResolvedValueOnce({ id: 'string-id', result: { tools: [] } }) .mockResolvedValueOnce({ id: null, result: { tools: [] } }); const result = await complianceSuite.runFullSuite(); const idTest = result.tests.find(t => t.name === 'JSON-RPC ID Matching'); expect(idTest?.status).toBe(TestStatus.PASSED); expect(mockHarness.sendMessage).toHaveBeenCalledTimes(3 + 2); // +2 for other tests }); it('should fail ID matching when IDs dont match', async () => { mockHarness.sendMessage.mockResolvedValue({ id: 999, result: { tools: [] } }); const result = await complianceSuite.runFullSuite(); const idTest = result.tests.find(t => t.name === 'JSON-RPC ID Matching'); expect(idTest?.status).toBe(TestStatus.FAILED); expect(idTest?.error).toContain('ID mismatch'); }); }); describe('Test Suite Statistics', () => { it('should calculate test statistics correctly', async () => { // Set up mixed results mockHarness.startServer.mockResolvedValue(undefined); mockHarness.initialize .mockResolvedValueOnce({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server' }, capabilities: {}, }, }) .mockRejectedValueOnce(new Error('Capability test failed')) .mockResolvedValueOnce({ result: { protocolVersion: '2024-11-05', serverInfo: { name: 'Test Server' }, capabilities: {}, }, }); mockHarness.listTools.mockResolvedValue({ result: {} }); // Will fail mockHarness.listResources.mockResolvedValue({ result: { resources: [] } }); mockHarness.listPrompts.mockResolvedValue({ result: { prompts: [] } }); const result = await complianceSuite.runFullSuite(); expect(result.passed + result.failed + result.skipped).toBe(result.tests.length); expect(result.duration).toBeGreaterThan(0); }); it('should handle all tests failing', async () => { mockHarness.startServer.mockRejectedValue(new Error('Complete failure')); const result = await complianceSuite.runFullSuite(); expect(result.failed).toBeGreaterThan(0); expect(result.passed).toBe(0); expect(result.tests.some(t => t.status === TestStatus.FAILED)).toBe(true); }); }); });

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/conorluddy/ContextPods'

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