Skip to main content
Glama

1MCP Server

streamableHttpRoutes.test.ts18.9 kB
import { ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { STREAMABLE_HTTP_ENDPOINT } from '@src/constants.js'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { setupStreamableHttpRoutes } from './streamableHttpRoutes.js'; // Mock all external dependencies vi.mock('node:crypto', () => ({ randomUUID: vi.fn(() => 'mock-uuid-123'), })); vi.mock('@modelcontextprotocol/sdk/server/streamableHttp.js', () => ({ StreamableHTTPServerTransport: vi.fn().mockImplementation((options) => { const transport = { sessionId: options?.sessionIdGenerator?.() || 'mock-session-id', onclose: null, onerror: null, handleRequest: vi.fn().mockResolvedValue(undefined), }; return transport; }), })); vi.mock('@src/logger/logger.js', () => ({ default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), }, })); vi.mock('../middlewares/tagsExtractor.js', () => ({ default: vi.fn((req: any, res: any, next: any) => { req.tags = ['test']; next(); }), })); vi.mock('@src/transport/http/middlewares/scopeAuthMiddleware.js', () => ({ createScopeAuthMiddleware: vi.fn(() => (req: any, res: any, next: any) => { res.locals = res.locals || {}; res.locals.validatedTags = ['test']; next(); }), getValidatedTags: vi.fn((res: any) => { return res.locals?.validatedTags || []; }), getTagExpression: vi.fn((res: any) => res?.locals?.tagExpression), getTagFilterMode: vi.fn((res: any) => res?.locals?.tagFilterMode || 'none'), getTagQuery: vi.fn((res: any) => res?.locals?.tagQuery), getPresetName: vi.fn((res: any) => res?.locals?.presetName), })); vi.mock('@src/utils/validation/sanitization.js', () => ({ sanitizeHeaders: vi.fn((_headers: any) => ({ 'content-type': 'application/json' })), })); vi.mock('../../../core/server/serverManager.js', () => ({ ServerManager: vi.fn(), })); vi.mock('../../../auth/sdkOAuthServerProvider.js', () => ({ SDKOAuthServerProvider: vi.fn(), })); describe('Streamable HTTP Routes', () => { let mockRouter: any; let mockServerManager: any; let _mockOAuthProvider: any; let mockRequest: any; let mockResponse: any; let postHandler: any; let getHandler: any; let deleteHandler: any; beforeEach(async () => { vi.resetAllMocks(); // Mock router mockRouter = { post: vi.fn(), get: vi.fn(), delete: vi.fn(), }; // Mock server manager mockServerManager = { connectTransport: vi.fn().mockResolvedValue(undefined), disconnectTransport: vi.fn(), getTransport: vi.fn(), }; // Mock OAuth provider _mockOAuthProvider = { validateScope: vi.fn().mockReturnValue(true), }; // Mock request/response mockRequest = { query: {}, headers: { 'content-type': 'application/json' }, body: {}, }; mockResponse = { status: vi.fn().mockReturnThis(), json: vi.fn().mockReturnThis(), end: vi.fn().mockReturnThis(), locals: {}, }; }); afterEach(() => { vi.resetAllMocks(); }); describe('setupStreamableHttpRoutes', () => { it('should setup POST route', () => { const mockAuthMiddleware = vi.fn(); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); expect(mockRouter.post).toHaveBeenCalledWith( STREAMABLE_HTTP_ENDPOINT, expect.any(Function), // tagsExtractor mockAuthMiddleware, // authMiddleware expect.any(Function), // handler ); }); it('should setup GET route', () => { const mockAuthMiddleware = vi.fn(); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); expect(mockRouter.get).toHaveBeenCalledWith( STREAMABLE_HTTP_ENDPOINT, expect.any(Function), // tagsExtractor mockAuthMiddleware, // authMiddleware expect.any(Function), // handler ); }); it('should setup DELETE route', () => { const mockAuthMiddleware = vi.fn(); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); expect(mockRouter.delete).toHaveBeenCalledWith( STREAMABLE_HTTP_ENDPOINT, expect.any(Function), // tagsExtractor mockAuthMiddleware, // authMiddleware expect.any(Function), // handler ); }); it('should setup routes without OAuth provider', () => { const mockAuthMiddleware = vi.fn((req, res, next) => next()); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); expect(mockRouter.post).toHaveBeenCalled(); expect(mockRouter.get).toHaveBeenCalled(); expect(mockRouter.delete).toHaveBeenCalled(); }); }); describe('POST Handler - New Session', () => { beforeEach(() => { const mockAuthMiddleware = vi.fn((req, res, next) => next()); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); postHandler = mockRouter.post.mock.calls[0][3]; // Get the actual handler function (4th arg after endpoint, tagsExtractor, authMiddleware) // Reset the serverManager mock after getting the handler but keep it available vi.mocked(mockServerManager.connectTransport).mockClear(); }); it('should create new session when no sessionId header', async () => { const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { getValidatedTags, getTagExpression, getTagFilterMode } = await import( '@src/transport/http/middlewares/scopeAuthMiddleware.js' ); const { randomUUID } = await import('node:crypto'); vi.mocked(getValidatedTags).mockReturnValue(['test-tag']); vi.mocked(getTagExpression).mockReturnValue(undefined); vi.mocked(getTagFilterMode).mockReturnValue('none'); vi.mocked(randomUUID).mockReturnValue('550e8400-e29b-41d4-a716-446655440000'); const mockTransport = { sessionId: '550e8400-e29b-41d4-a716-446655440000', onclose: null, handleRequest: vi.fn().mockResolvedValue(undefined), }; vi.mocked(StreamableHTTPServerTransport).mockReturnValue(mockTransport as any); mockRequest.query = { pagination: 'true' }; mockRequest.body = { method: 'test', params: {} }; mockResponse.locals = { validatedTags: ['test-tag'] }; await postHandler(mockRequest, mockResponse); expect(StreamableHTTPServerTransport).toHaveBeenCalledWith({ sessionIdGenerator: expect.any(Function), }); expect(mockServerManager.connectTransport).toHaveBeenCalledWith( mockTransport, '550e8400-e29b-41d4-a716-446655440000', { tags: ['test-tag'], tagExpression: undefined, tagFilterMode: 'none', enablePagination: true, }, ); expect(mockTransport.handleRequest).toHaveBeenCalledWith(mockRequest, mockResponse, mockRequest.body); }); it('should setup onclose handler for new transport', async () => { const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { getValidatedTags } = await import('../middlewares/scopeAuthMiddleware.js'); const { randomUUID } = await import('node:crypto'); vi.mocked(getValidatedTags).mockReturnValue([]); vi.mocked(randomUUID).mockReturnValue('550e8400-e29b-41d4-a716-446655440001'); const mockTransport = { sessionId: '550e8400-e29b-41d4-a716-446655440001', onclose: null, handleRequest: vi.fn().mockResolvedValue(undefined), }; vi.mocked(StreamableHTTPServerTransport).mockReturnValue(mockTransport as any); await postHandler(mockRequest, mockResponse); expect(mockTransport.onclose).toBeTypeOf('function'); // Test the onclose handler if (mockTransport.onclose) { (mockTransport.onclose as Function)(); expect(mockServerManager.disconnectTransport).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440001'); } }); it('should handle pagination disabled', async () => { const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { getValidatedTags, getTagExpression, getTagFilterMode } = await import( '../middlewares/scopeAuthMiddleware.js' ); const { randomUUID } = await import('node:crypto'); vi.mocked(getValidatedTags).mockReturnValue(['tag1', 'tag2']); vi.mocked(getTagExpression).mockReturnValue(undefined); vi.mocked(getTagFilterMode).mockReturnValue('none'); vi.mocked(randomUUID).mockReturnValue('550e8400-e29b-41d4-a716-446655440002'); const mockTransport = { sessionId: '550e8400-e29b-41d4-a716-446655440002', onclose: null, handleRequest: vi.fn().mockResolvedValue(undefined), }; vi.mocked(StreamableHTTPServerTransport).mockReturnValue(mockTransport as any); mockRequest.query = { pagination: 'false' }; await postHandler(mockRequest, mockResponse); expect(mockServerManager.connectTransport).toHaveBeenCalledWith( mockTransport, '550e8400-e29b-41d4-a716-446655440002', { tags: ['tag1', 'tag2'], tagExpression: undefined, tagFilterMode: 'none', enablePagination: false, }, ); }); }); describe('POST Handler - Existing Session', () => { beforeEach(() => { const mockAuthMiddleware = vi.fn((req, res, next) => next()); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); postHandler = mockRouter.post.mock.calls[0][3]; }); it('should use existing transport when sessionId provided', async () => { const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js'); const mockTransport = { handleRequest: vi.fn().mockResolvedValue(undefined), }; mockRequest.headers = { 'mcp-session-id': 'existing-session' }; mockRequest.body = { method: 'test' }; mockServerManager.getTransport.mockReturnValue(mockTransport); // Mock instanceof check Object.setPrototypeOf(mockTransport, StreamableHTTPServerTransport.prototype); await postHandler(mockRequest, mockResponse); expect(mockServerManager.getTransport).toHaveBeenCalledWith('existing-session'); expect(mockTransport.handleRequest).toHaveBeenCalledWith(mockRequest, mockResponse, mockRequest.body); }); it('should return 404 when session not found', async () => { mockRequest.headers = { 'mcp-session-id': 'non-existent' }; mockServerManager.getTransport.mockReturnValue(null); await postHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(404); expect(mockResponse.json).toHaveBeenCalledWith({ error: { code: ErrorCode.InvalidParams, message: 'No active streamable HTTP session found for the provided sessionId', }, }); }); it('should return 400 when session uses different transport', async () => { const mockTransport = { type: 'different-transport', }; mockRequest.headers = { 'mcp-session-id': 'wrong-transport' }; mockServerManager.getTransport.mockReturnValue(mockTransport); await postHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(400); expect(mockResponse.json).toHaveBeenCalledWith({ error: { code: ErrorCode.InvalidParams, message: 'Session already exists but uses a different transport protocol', }, }); }); }); describe('POST Handler - Error Handling', () => { beforeEach(() => { const mockAuthMiddleware = vi.fn((req, res, next) => next()); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); postHandler = mockRouter.post.mock.calls[0][3]; }); it('should handle transport creation error', async () => { const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js'); vi.mocked(StreamableHTTPServerTransport).mockImplementation(() => { throw new Error('Transport creation failed'); }); await postHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(500); expect(mockResponse.end).toHaveBeenCalled(); }); it('should handle server manager connection error', async () => { const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { getValidatedTags } = await import('../middlewares/scopeAuthMiddleware.js'); vi.mocked(getValidatedTags).mockReturnValue([]); const mockTransport = { sessionId: 'error-test', onclose: null, handleRequest: vi.fn(), }; vi.mocked(StreamableHTTPServerTransport).mockReturnValue(mockTransport as any); mockServerManager.connectTransport.mockRejectedValue(new Error('Connection failed')); await postHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(500); expect(mockResponse.end).toHaveBeenCalled(); }); it('should handle request processing error', async () => { const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js'); const mockTransport = { handleRequest: vi.fn().mockRejectedValue(new Error('Request processing failed')), }; mockRequest.headers = { 'mcp-session-id': 'error-session' }; mockServerManager.getTransport.mockReturnValue(mockTransport); // Mock instanceof check Object.setPrototypeOf(mockTransport, StreamableHTTPServerTransport.prototype); await postHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(500); expect(mockResponse.end).toHaveBeenCalled(); }); }); describe('GET Handler', () => { beforeEach(() => { const mockAuthMiddleware = vi.fn((req, res, next) => next()); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); getHandler = mockRouter.get.mock.calls[0][3]; // Get the actual handler function }); it('should handle GET request with valid sessionId', async () => { const mockTransport = { handleRequest: vi.fn().mockResolvedValue(undefined), }; mockRequest.headers = { 'mcp-session-id': 'valid-session' }; mockRequest.body = {}; mockServerManager.getTransport.mockReturnValue(mockTransport); await getHandler(mockRequest, mockResponse); expect(mockServerManager.getTransport).toHaveBeenCalledWith('valid-session'); expect(mockTransport.handleRequest).toHaveBeenCalledWith(mockRequest, mockResponse, mockRequest.body); }); it('should return 400 when sessionId header missing', async () => { mockRequest.headers = {}; // No sessionId await getHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(400); expect(mockResponse.json).toHaveBeenCalledWith({ error: { code: ErrorCode.InvalidParams, message: 'Invalid params: sessionId is required', }, }); }); it('should return 404 when transport not found', async () => { mockRequest.headers = { 'mcp-session-id': 'non-existent' }; mockServerManager.getTransport.mockReturnValue(null); await getHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(404); expect(mockResponse.json).toHaveBeenCalledWith({ error: { code: ErrorCode.InvalidParams, message: 'No active streamable HTTP session found for the provided sessionId', }, }); }); it('should handle request processing error', async () => { const mockTransport = { handleRequest: vi.fn().mockRejectedValue(new Error('GET processing failed')), }; mockRequest.headers = { 'mcp-session-id': 'error-get' }; mockServerManager.getTransport.mockReturnValue(mockTransport); await getHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(500); expect(mockResponse.end).toHaveBeenCalled(); }); }); describe('DELETE Handler', () => { beforeEach(() => { const mockAuthMiddleware = vi.fn((req, res, next) => next()); setupStreamableHttpRoutes(mockRouter, mockServerManager, mockAuthMiddleware); deleteHandler = mockRouter.delete.mock.calls[0][3]; // Get the actual handler function }); it('should handle DELETE request with valid sessionId', async () => { const mockTransport = { handleRequest: vi.fn().mockResolvedValue(undefined), }; mockRequest.headers = { 'mcp-session-id': 'delete-session' }; mockServerManager.getTransport.mockReturnValue(mockTransport); await deleteHandler(mockRequest, mockResponse); expect(mockServerManager.getTransport).toHaveBeenCalledWith('delete-session'); expect(mockTransport.handleRequest).toHaveBeenCalledWith(mockRequest, mockResponse); }); it('should return 400 when sessionId header missing for DELETE', async () => { mockRequest.headers = {}; // No sessionId await deleteHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(400); expect(mockResponse.json).toHaveBeenCalledWith({ error: { code: ErrorCode.InvalidParams, message: 'Invalid params: sessionId is required', }, }); }); it('should return 404 when transport not found for DELETE', async () => { mockRequest.headers = { 'mcp-session-id': 'non-existent-delete' }; mockServerManager.getTransport.mockReturnValue(null); await deleteHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(404); expect(mockResponse.json).toHaveBeenCalledWith({ error: { code: ErrorCode.InvalidParams, message: 'No active streamable HTTP session found for the provided sessionId', }, }); }); it('should handle DELETE processing error', async () => { const mockTransport = { handleRequest: vi.fn().mockRejectedValue(new Error('DELETE processing failed')), }; mockRequest.headers = { 'mcp-session-id': 'error-delete' }; mockServerManager.getTransport.mockReturnValue(mockTransport); await deleteHandler(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalledWith(500); expect(mockResponse.end).toHaveBeenCalled(); }); }); });

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