Skip to main content
Glama
streamable-http.unit.test.ts8.22 kB
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" import { StreamableHttpServer } from "../../src/transports/streamable-http" import express from "express" import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" // Mock the Server class vi.mock("@modelcontextprotocol/sdk/server/index.js", () => { return { Server: vi.fn().mockImplementation(() => { return { connect: vi.fn().mockImplementation(async (transport) => { // Manually set callbacks for testing if (transport.start) { await transport.start() } }), setRequestHandler: vi.fn(), } }), } }) // Mock the StreamableHTTPServerTransport vi.mock("@modelcontextprotocol/sdk/server/streamableHttp.js", () => { return { StreamableHTTPServerTransport: vi .fn() .mockImplementation(({ sessionIdGenerator, onsessioninitialized }) => { const sessionId = sessionIdGenerator() onsessioninitialized(sessionId) return { sessionId, handleRequest: vi.fn().mockResolvedValue(undefined), onclose: undefined, start: vi.fn().mockResolvedValue(undefined), close: vi.fn().mockResolvedValue(undefined), } }), } }) describe("StreamableHTTP Server", () => { let app: express.Application beforeEach(() => { // Create a test app app = express() vi.clearAllMocks() }) it("should set up correct routes", () => { // Create StreamableHTTP server instance that uses the test app const httpServer = new StreamableHttpServer({ port: 0, // Use any available port for testing }) // @ts-expect-error - Replace the app with our test app httpServer.app = app // Add spy on route configuration methods const appAllSpy = vi.spyOn(app, "all") const appGetSpy = vi.spyOn(app, "get") // Setup routes manually // @ts-expect-error - Access private method for testing httpServer.setupRoutes() // Verify that the expected routes were set up expect(appAllSpy).toHaveBeenCalledTimes(1) // /mcp // Express internally calls app.get with a function as the first argument for query parsing // We only care about the route paths, so filter to only include string paths const actualRoutes = appGetSpy.mock.calls.filter( (call) => typeof call[0] === "string" && call[0].startsWith("/"), ) expect(actualRoutes.length).toBe(1) // Only /health is a real route // Check specific routes const mcpCallArgs = appAllSpy.mock.calls.find((call) => call[0] === "/mcp") const healthCallArgs = appGetSpy.mock.calls.find((call) => call[0] === "/health") expect(mcpCallArgs).toBeDefined() expect(healthCallArgs).toBeDefined() }) it("should handle POST initialization requests correctly", async () => { // Create StreamableHTTP server const httpServer = new StreamableHttpServer({ port: 0, // Use any available port for testing }) // @ts-expect-error - Replace the app with our test app httpServer.app = app // @ts-expect-error - Access private method for testing httpServer.setupRoutes() // Mock request and response const req = { method: "POST", headers: {}, body: { jsonrpc: "2.0", method: "initialize", id: 1, params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "test-client", version: "1.0.0", }, }, }, } as any const res = { status: vi.fn().mockReturnThis(), json: vi.fn(), send: vi.fn(), writeHead: vi.fn(), headersSent: false, } as any // Get the route handler const routeHandler = app._router.stack.find( (layer: any) => layer.route && layer.route.path === "/mcp", )?.route.stack[0].handle // Call the route handler await routeHandler(req, res) // Check that StreamableHTTPServerTransport was created expect(StreamableHTTPServerTransport).toHaveBeenCalledTimes(1) // Check that handleRequest was called with the initialization request const transportInstance = (StreamableHTTPServerTransport as any).mock.results[0].value expect(transportInstance.handleRequest).toHaveBeenCalledWith(req, res, req.body) }) it("should reject POST requests without session ID or initialization", async () => { // Create StreamableHTTP server const httpServer = new StreamableHttpServer({ port: 0, // Use any available port for testing }) // @ts-expect-error - Replace the app with our test app httpServer.app = app // @ts-expect-error - Access private method for testing httpServer.setupRoutes() // Mock request and response const req = { method: "POST", headers: {}, body: { jsonrpc: "2.0", method: "test", id: 1, }, } as any const res = { status: vi.fn().mockReturnThis(), json: vi.fn(), headersSent: false, } as any // Get the route handler const routeHandler = app._router.stack.find( (layer: any) => layer.route && layer.route.path === "/mcp", )?.route.stack[0].handle // Call the route handler await routeHandler(req, res) // Check that it rejected the request expect(res.status).toHaveBeenCalledWith(400) expect(res.json).toHaveBeenCalledWith({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request: No valid session ID provided for non-initialize request", }, id: null, }) }) it("should set up server handlers correctly", () => { // Create StreamableHTTP server const httpServer = new StreamableHttpServer({ port: 0, // Use any available port for testing }) // Create a mock server const mockServer = { setRequestHandler: vi.fn(), } // Call setupServerHandlers // @ts-expect-error - Access private method for testing httpServer.setupServerHandlers(mockServer as any) // Verify that setRequestHandler was called for all expected handlers expect(mockServer.setRequestHandler).toHaveBeenCalledTimes(4) }) it("should start and stop the server", async () => { // Create StreamableHTTP server const httpServer = new StreamableHttpServer({ port: 0, // Use any available port for testing }) // Mock the app.listen method const listenMock = vi.fn().mockImplementation((port, callback) => { callback() return { close: vi.fn().mockImplementation((cb) => cb()) } }) // @ts-expect-error - Replace the app.listen with our mock httpServer.app.listen = listenMock // Start the server await httpServer.start() // Verify that listen was called expect(listenMock).toHaveBeenCalledTimes(1) // Stop the server (with no active transports) await httpServer.stop() // Now test with an active transport // @ts-expect-error - Access private property for testing httpServer.transports = { "test-session": { close: vi.fn().mockResolvedValue(undefined), } as any, } // Stop the server (with an active transport) await httpServer.stop() // Verify that transport.close was called // @ts-expect-error - Access private property for testing expect(httpServer.transports["test-session"].close).toHaveBeenCalledTimes(1) }) it("should get handler for valid tool names", () => { // Create StreamableHTTP server const httpServer = new StreamableHttpServer({ port: 0, // Use any available port for testing }) // Test getting a handler for a known GraphQL tool // @ts-expect-error - Access private method for testing const handler = httpServer.getHandler("graphql_query") // Verify that a handler was returned expect(handler).toBeDefined() // Test getting a handler for an unknown tool // @ts-expect-error - Access private method for testing const unknownHandler = httpServer.getHandler("unknown_tool") // Verify that no handler was returned expect(unknownHandler).toBeUndefined() }) })

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/ivo-toby/contentful-mcp-graphql'

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