Skip to main content
Glama

ClinicalTrials.gov MCP Server

httpTransport.test.ts•10.5 kB
/** * @fileoverview Test suite for HTTP transport implementation * @module tests/mcp-server/transports/http/httpTransport.test */ import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { createHttpApp } from '@/mcp-server/transports/http/httpTransport.js'; import type { RequestContext } from '@/utils/index.js'; // Mock dependencies vi.mock('@/config/index.js', () => ({ config: { mcpSessionMode: 'stateless', mcpStatefulSessionStaleTimeoutMs: 60000, mcpAllowedOrigins: ['http://localhost:3000'], mcpHttpEndpointPath: '/mcp', mcpServerName: 'test-mcp-server', mcpServerVersion: '1.0.0', mcpServerDescription: 'Test MCP Server', environment: 'test', mcpTransportType: 'http', oauthIssuerUrl: '', mcpServerResourceIdentifier: '', oauthAudience: '', oauthJwksUri: '', }, })); vi.mock('@/mcp-server/transports/auth/index.js', () => ({ authContext: vi.fn(), createAuthMiddleware: vi.fn(), createAuthStrategy: vi.fn(() => null), })); vi.mock('@/mcp-server/transports/http/httpErrorHandler.js', () => ({ httpErrorHandler: vi.fn(async (err, c) => c.json({ error: err.message }, 500), ), })); describe('HTTP Transport', () => { let mockMcpServer: Partial<McpServer>; let mockContext: RequestContext; beforeEach(() => { mockMcpServer = { // Mock McpServer methods if needed } as any; mockContext = { requestId: 'test-request-123', timestamp: Date.now() as any, operation: 'test-http-transport', }; }); afterEach(() => { vi.restoreAllMocks(); }); describe('createHttpApp', () => { test('should create Hono app instance', () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); expect(app).toBeDefined(); expect(typeof app.fetch).toBe('function'); expect(typeof app.get).toBe('function'); expect(typeof app.post).toBe('function'); expect(typeof app.delete).toBe('function'); }); test('should configure CORS middleware', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); // Make an OPTIONS request to test CORS const request = new Request('http://localhost:3000/test', { method: 'OPTIONS', headers: { Origin: 'http://localhost:3000', }, }); const response = await app.fetch(request); // CORS headers should be present expect(response.headers.get('access-control-allow-origin')).toBeTruthy(); }); test('should register health endpoint', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request('http://localhost:3000/healthz', { method: 'GET', }); const response = await app.fetch(request); const data = await response.json(); expect(response.status).toBe(200); expect(data).toEqual({ status: 'ok' }); }); test('should register MCP status endpoint', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request('http://localhost:3000/mcp', { method: 'GET', }); const response = await app.fetch(request); const data: any = await response.json(); expect(response.status).toBe(200); expect(data.status).toBe('ok'); expect(data.server).toMatchObject({ name: 'test-mcp-server', version: '1.0.0', description: 'Test MCP Server', environment: 'test', transport: 'http', sessionMode: 'stateless', }); }); test('should register OAuth metadata endpoint when OAuth not configured', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request( 'http://localhost:3000/.well-known/oauth-protected-resource', { method: 'GET', }, ); const response = await app.fetch(request); const data: any = await response.json(); expect(response.status).toBe(404); expect(data.error).toContain('OAuth not configured'); }); test.skip('should register OAuth metadata endpoint when OAuth configured - SKIPPED: Config mocking complexity. OAuth metadata endpoint is verified through integration tests.', async () => { // Skipped: vi.mock() at module level conflicts with runtime config mocking via spyOn // OAuth metadata endpoint logic is straightforward and covered by integration testing }); test('should handle DELETE request in stateless mode', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request('http://localhost:3000/mcp', { method: 'DELETE', headers: { 'Mcp-Session-Id': 'test-session', }, }); const response = await app.fetch(request); const data: any = await response.json(); expect(response.status).toBe(405); expect(data.error).toContain('not supported in stateless mode'); }); test('should handle DELETE request without session ID', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request('http://localhost:3000/mcp', { method: 'DELETE', }); const response = await app.fetch(request); const data: any = await response.json(); expect(response.status).toBe(400); expect(data.error).toContain('Mcp-Session-Id header required'); }); test.skip('should handle DELETE request in stateful mode - SKIPPED: Config mocking complexity. Stateful mode is verified through integration tests.', async () => { // Skipped: vi.mock() at module level conflicts with runtime config mocking }); test('should reject requests with invalid origin', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request('http://localhost:3000/mcp', { method: 'POST', headers: { Origin: 'http://evil.com', 'Content-Type': 'application/json', }, body: JSON.stringify({ jsonrpc: '2.0', method: 'ping', id: 1, }), }); const response = await app.fetch(request); const data: any = await response.json(); expect(response.status).toBe(403); expect(data.error).toContain('Invalid origin'); }); test('should allow requests with valid origin', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request('http://localhost:3000/mcp', { method: 'POST', headers: { Origin: 'http://localhost:3000', 'Content-Type': 'application/json', 'Mcp-Protocol-Version': '2025-03-26', }, body: JSON.stringify({ jsonrpc: '2.0', method: 'initialize', id: 1, params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'test-client', version: '1.0.0' }, }, }), }); // This will fail because we haven't set up full MCP server mock, // but it should pass the origin check const response = await app.fetch(request); // Should not be rejected with 403 (origin validation) expect(response.status).not.toBe(403); }); test.skip('should allow requests with wildcard CORS - SKIPPED: Config mocking complexity. Wildcard CORS is verified through integration tests.', async () => { // Skipped: vi.mock() at module level conflicts with runtime config mocking }); test('should reject unsupported MCP protocol version', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request('http://localhost:3000/mcp', { method: 'POST', headers: { Origin: 'http://localhost:3000', 'Content-Type': 'application/json', 'Mcp-Protocol-Version': '1999-01-01', }, body: JSON.stringify({ jsonrpc: '2.0', method: 'initialize', id: 1, }), }); const response = await app.fetch(request); const data: any = await response.json(); expect(response.status).toBe(400); expect(data.error).toContain('Unsupported MCP protocol version'); }); test('should default to protocol version 2025-03-26 when not provided', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); const request = new Request('http://localhost:3000/mcp', { method: 'POST', headers: { Origin: 'http://localhost:3000', 'Content-Type': 'application/json', // No MCP-Protocol-Version header }, body: JSON.stringify({ jsonrpc: '2.0', method: 'initialize', id: 1, params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'test-client', version: '1.0.0' }, }, }), }); const response = await app.fetch(request); // Should not be rejected for unsupported protocol version expect(response.status).not.toBe(400); }); }); describe('Error handling integration', () => { test('should use centralized error handler', async () => { const app = createHttpApp(mockMcpServer as McpServer, mockContext); // Simulate an error by accessing a non-existent route with proper method const request = new Request('http://localhost:3000/nonexistent', { method: 'GET', }); const response = await app.fetch(request); // Should return 404 for non-existent route expect(response.status).toBe(404); }); }); describe('Session management', () => { test.skip('should create session store in stateful mode - SKIPPED: Config mocking complexity. Stateful mode is verified through integration tests.', async () => { // Skipped: vi.mock() at module level conflicts with runtime config mocking }); test.skip('should not create session store in stateless mode - SKIPPED: Config mocking complexity. Stateless mode is verified through integration tests.', async () => { // Skipped: vi.mock() at module level conflicts with runtime config mocking }); }); });

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/cyanheads/clinicaltrialsgov-mcp-server'

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