Skip to main content
Glama

1MCP Server

multi-transport.test.ts16.4 kB
import { ConfigBuilder, ProtocolValidator, TestProcessManager } from '@test/e2e/utils/index.js'; import { join } from 'path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; describe('Multi-Transport Infrastructure Integration E2E', () => { let processManager: TestProcessManager; let configBuilder: ConfigBuilder; let configPath: string; beforeEach(async () => { processManager = new TestProcessManager(); configBuilder = new ConfigBuilder(); const fixturesPath = join(__dirname, '../fixtures'); configPath = configBuilder .enableStdioTransport() .enableHttpTransport(3000) .enableAuth('test-client-id', 'test-client-secret') .addStdioServer('stdio-echo', 'node', [join(fixturesPath, 'echo-server.js')], ['stdio', 'echo']) .addStdioServer('stdio-capability', 'node', [join(fixturesPath, 'capability-server.js')], ['stdio', 'capability']) .addStdioServer( 'stdio-slow', 'node', [join(fixturesPath, 'slow-server.js'), '--defaultDelay=100'], ['stdio', 'slow'], ) .writeToFile(); }); afterEach(async () => { await processManager.cleanup(); configBuilder.cleanup(); }); it('should create valid multi-transport configuration', async () => { // Test that configuration builds correctly with multiple transports expect(configPath).toBeDefined(); expect(configPath.endsWith('.json')).toBe(true); const config = configBuilder.build(); expect(config).toBeDefined(); expect(config.servers).toBeDefined(); expect(Array.isArray(config.servers)).toBe(true); expect(config.transport).toBeDefined(); }); it('should validate transport protocol configurations', async () => { // Test different transport configurations const stdioConfig = new ConfigBuilder() .enableStdioTransport() .addStdioServer('stdio-1', 'echo', ['hello'], ['stdio']) .build(); const httpConfig = new ConfigBuilder() .enableHttpTransport(3001) .enableAuth('client1', 'secret1') .addStdioServer('http-1', 'echo', ['world'], ['http']) .build(); const multiConfig = new ConfigBuilder() .enableStdioTransport() .enableHttpTransport(3002) .enableAuth('client2', 'secret2') .addStdioServer('multi-1', 'echo', ['test'], ['both']) .build(); expect(stdioConfig.transport?.stdio).toBe(true); expect(stdioConfig.transport?.http).toBeFalsy(); expect(httpConfig.transport?.http?.port).toBe(3001); expect(httpConfig.auth?.enabled).toBe(true); expect(multiConfig.transport?.stdio).toBe(true); expect(multiConfig.transport?.http?.port).toBe(3002); }); it('should handle tag-based server filtering across transports', async () => { // Test server filtering by tags const servers = [ { name: 'stdio-echo', tags: ['stdio', 'echo'] }, { name: 'stdio-capability', tags: ['stdio', 'capability'] }, { name: 'stdio-slow', tags: ['stdio', 'slow'] }, ]; // Filter by transport type const stdioServers = servers.filter((s) => s.tags.includes('stdio')); expect(stdioServers).toHaveLength(3); // Filter by functionality const echoServers = servers.filter((s) => s.tags.includes('echo')); const capabilityServers = servers.filter((s) => s.tags.includes('capability')); const slowServers = servers.filter((s) => s.tags.includes('slow')); expect(echoServers).toHaveLength(1); expect(capabilityServers).toHaveLength(1); expect(slowServers).toHaveLength(1); expect(echoServers[0].name).toBe('stdio-echo'); expect(capabilityServers[0].name).toBe('stdio-capability'); expect(slowServers[0].name).toBe('stdio-slow'); }); it('should validate concurrent transport operations', async () => { // Test concurrent operation patterns const operations = [ { transport: 'stdio', server: 'stdio-echo', method: 'ping' }, { transport: 'stdio', server: 'stdio-capability', method: 'tools/list' }, { transport: 'stdio', server: 'stdio-slow', method: 'ping' }, { transport: 'http', server: 'any', method: 'ping' }, ]; // Validate operation structure operations.forEach((op) => { expect(['stdio', 'http', 'sse']).toContain(op.transport); expect(op.server).toBeDefined(); expect(op.method).toBeDefined(); // Validate method format const validation = ProtocolValidator.validateMcpMethod(op.method); expect(validation.valid).toBe(true); }); }); it('should handle process management across multiple transports', async () => { // Test starting different types of processes const processes = [ { name: 'stdio-process', command: 'sleep', args: ['1'] }, { name: 'http-process', command: 'sleep', args: ['1'] }, { name: 'slow-process', command: 'sleep', args: ['1'] }, ]; const processInfos = []; for (const proc of processes) { const info = await processManager.startProcess(proc.name, { command: proc.command, args: proc.args, timeout: 5000, startupTimeout: 2000, // Reasonable startup timeout }); processInfos.push(info); expect(processManager.isProcessRunning(proc.name)).toBe(true); } // All processes should be running expect(processInfos).toHaveLength(3); processInfos.forEach((info) => { expect(info.pid).toBeGreaterThan(0); }); // Clean up for (const proc of processes) { await processManager.stopProcess(proc.name); expect(processManager.isProcessRunning(proc.name)).toBe(false); } }); it('should validate configuration reload with multiple transports', async () => { // Test configuration changes const originalConfig = configBuilder.build(); expect(originalConfig.servers).toHaveLength(3); // Create new configuration with additional servers const newConfigBuilder = new ConfigBuilder() .enableStdioTransport() .enableHttpTransport(3001) .enableAuth('new-client-id', 'new-client-secret'); // Add original servers const fixturesPath = join(__dirname, '../fixtures'); newConfigBuilder .addStdioServer('stdio-echo', 'node', [join(fixturesPath, 'echo-server.js')], ['stdio', 'echo']) .addStdioServer('stdio-capability', 'node', [join(fixturesPath, 'capability-server.js')], ['stdio', 'capability']) // Add new server .addStdioServer('stdio-error', 'node', [join(fixturesPath, 'error-server.js')], ['stdio', 'error']); const newConfig = newConfigBuilder.build(); expect(newConfig.servers).toHaveLength(3); // echo, capability, error (slow removed) expect(newConfig.transport?.http?.port).toBe(3001); expect(newConfig.auth?.clientId).toBe('new-client-id'); const serverNames = newConfig.servers.map((s) => s.name); expect(serverNames).toContain('stdio-echo'); expect(serverNames).toContain('stdio-capability'); expect(serverNames).toContain('stdio-error'); }); it('should handle transport failure scenarios', async () => { // Test handling of different failure scenarios const failureScenarios = [ { type: 'server_crash', server: 'stdio-crash', expected_status: 'failed', }, { type: 'invalid_command', server: 'invalid-server', expected_status: 'failed', }, { type: 'timeout', server: 'slow-server', expected_status: 'timeout', }, ]; failureScenarios.forEach((scenario) => { expect(['server_crash', 'invalid_command', 'timeout', 'network_error']).toContain(scenario.type); expect(['failed', 'timeout', 'disconnected']).toContain(scenario.expected_status); }); }); it('should validate performance patterns across transports', async () => { // Test performance measurement structures const performanceMetrics = { stdio: { requests_per_second: 150, avg_response_time: 25, error_rate: 0.02, concurrent_connections: 10, }, http: { requests_per_second: 75, avg_response_time: 45, error_rate: 0.01, concurrent_connections: 25, }, }; Object.values(performanceMetrics).forEach((metrics) => { expect(metrics.requests_per_second).toBeGreaterThan(0); expect(metrics.avg_response_time).toBeGreaterThan(0); expect(metrics.error_rate).toBeGreaterThanOrEqual(0); expect(metrics.error_rate).toBeLessThan(1); expect(metrics.concurrent_connections).toBeGreaterThan(0); }); // Stdio should generally be faster than HTTP expect(performanceMetrics.stdio.requests_per_second).toBeGreaterThan(performanceMetrics.http.requests_per_second); expect(performanceMetrics.stdio.avg_response_time).toBeLessThan(performanceMetrics.http.avg_response_time); }); it('should handle graceful shutdown across transports', async () => { // Test shutdown patterns const shutdownSequence = [ { phase: 'stop_accepting_requests', duration: 100 }, { phase: 'complete_active_requests', duration: 2000 }, { phase: 'close_connections', duration: 500 }, { phase: 'cleanup_resources', duration: 300 }, ]; let totalDuration = 0; shutdownSequence.forEach((phase) => { expect([ 'stop_accepting_requests', 'complete_active_requests', 'close_connections', 'cleanup_resources', ]).toContain(phase.phase); expect(phase.duration).toBeGreaterThan(0); totalDuration += phase.duration; }); // Total shutdown should be reasonable expect(totalDuration).toBeLessThan(10000); // Less than 10 seconds }); it('should validate resource management across transports', async () => { // Test resource creation and management const resources = [ { uri: 'test://stdio/resource1', name: 'Stdio Resource 1', transport: 'stdio', content: 'Created via stdio transport', }, { uri: 'test://http/resource1', name: 'HTTP Resource 1', transport: 'http', content: 'Created via HTTP transport', }, { uri: 'test://multi/resource1', name: 'Multi Resource 1', transport: 'both', content: 'Accessible via both transports', }, ]; resources.forEach((resource) => { expect(resource.uri).toMatch(/^test:\/\/[a-z]+\/[a-zA-Z0-9]+$/); expect(resource.name).toBeDefined(); expect(['stdio', 'http', 'both']).toContain(resource.transport); expect(resource.content).toBeDefined(); }); // Test resource listing structure const resourceList = { resources: resources, total_count: resources.length, by_transport: { stdio: resources.filter((r) => r.transport === 'stdio' || r.transport === 'both').length, http: resources.filter((r) => r.transport === 'http' || r.transport === 'both').length, }, }; expect(resourceList.total_count).toBe(3); expect(resourceList.by_transport.stdio).toBe(2); // stdio + both expect(resourceList.by_transport.http).toBe(2); // http + both }); it('should handle error propagation across transports', async () => { // Test error handling structures const errorTypes = [ { transport: 'stdio', error_code: 'STDIO_CONNECTION_FAILED', message: 'Failed to establish stdio connection', recoverable: true, }, { transport: 'http', error_code: 'HTTP_TIMEOUT', message: 'HTTP request timed out', recoverable: true, }, { transport: 'both', error_code: 'PROTOCOL_ERROR', message: 'Invalid JSON-RPC message format', recoverable: false, }, ]; errorTypes.forEach((error) => { expect(['stdio', 'http', 'sse', 'both']).toContain(error.transport); expect(error.error_code).toMatch(/^[A-Z_]+$/); expect(error.message).toBeDefined(); expect(typeof error.recoverable).toBe('boolean'); }); }); it('should validate authentication across transports', async () => { // Test authentication configurations const authConfigs = [ { transport: 'stdio', auth_required: false, reason: 'Local process communication', }, { transport: 'http', auth_required: true, methods: ['oauth2', 'bearer_token'], scopes: ['mcp:read', 'mcp:write', 'mcp:admin'], }, ]; authConfigs.forEach((config) => { expect(['stdio', 'http', 'sse']).toContain(config.transport); expect(typeof config.auth_required).toBe('boolean'); if (config.auth_required) { expect(config.methods).toBeDefined(); expect(Array.isArray(config.methods)).toBe(true); expect(config.scopes).toBeDefined(); expect(Array.isArray(config.scopes)).toBe(true); } }); }); it('should handle transport-specific message formatting', async () => { // Test message format validation for different transports const messages = [ { transport: 'stdio', format: 'json_rpc', message: { jsonrpc: '2.0', id: 1, method: 'ping', }, }, { transport: 'http', format: 'http_json_rpc', message: { method: 'POST', url: '/mcp', headers: { 'Content-Type': 'application/json' }, body: { jsonrpc: '2.0', id: 1, method: 'ping', }, }, }, ]; messages.forEach((msg) => { expect(['stdio', 'http', 'sse']).toContain(msg.transport); expect(['json_rpc', 'http_json_rpc', 'sse_json_rpc']).toContain(msg.format); expect(msg.message).toBeDefined(); if (msg.format.includes('json_rpc')) { const jsonRpcMsg = msg.format === 'json_rpc' ? msg.message : msg.message.body; const validation = ProtocolValidator.validateRequest(jsonRpcMsg); expect(validation.valid).toBe(true); } }); }); it('should validate transport monitoring and metrics', async () => { // Test monitoring data structures const transportMetrics = { stdio: { active_connections: 3, total_requests: 1250, failed_requests: 15, avg_request_duration: 25, uptime: 3600000, }, http: { active_connections: 12, total_requests: 890, failed_requests: 8, avg_request_duration: 45, uptime: 3600000, port: 3000, ssl_enabled: false, }, }; Object.entries(transportMetrics).forEach(([transport, metrics]) => { expect(['stdio', 'http']).toContain(transport); expect(metrics.active_connections).toBeGreaterThanOrEqual(0); expect(metrics.total_requests).toBeGreaterThan(0); expect(metrics.failed_requests).toBeGreaterThanOrEqual(0); expect(metrics.avg_request_duration).toBeGreaterThan(0); expect(metrics.uptime).toBeGreaterThan(0); if (transport === 'http') { expect((metrics as any).port).toBeGreaterThan(0); expect(typeof (metrics as any).ssl_enabled).toBe('boolean'); } }); }); it('should handle configuration validation for all transport types', async () => { // Test comprehensive transport configuration const fullConfig = new ConfigBuilder() .enableStdioTransport() .enableHttpTransport(3005) .enableAuth('comprehensive-client', 'comprehensive-secret') .addStdioServer('server1', 'echo', ['arg1'], ['tag1']) .addStdioServer('server2', 'echo', ['arg2'], ['tag2']) .addStdioServer('server3', 'echo', ['arg3'], ['tag3']) .build(); // Validate transport configuration expect(fullConfig.transport?.stdio).toBe(true); expect(fullConfig.transport?.http?.port).toBe(3005); // Validate auth configuration expect(fullConfig.auth?.enabled).toBe(true); expect(fullConfig.auth?.clientId).toBe('comprehensive-client'); // Validate servers expect(fullConfig.servers).toHaveLength(3); fullConfig.servers.forEach((server, index) => { expect(server.name).toBe(`server${index + 1}`); expect(server.transport).toBe('stdio'); expect(server.tags).toContain(`tag${index + 1}`); }); }); });

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/1mcp-app/agent'

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