Skip to main content
Glama
mcpService.test.ts4.96 kB
/** * Tests for MCP service functionality including SSE heartbeat. */ import Fastify from "fastify"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { IPipeline } from "../pipeline/trpc/interfaces"; import type { IDocumentManagement } from "../store/trpc/interfaces"; import { cleanupMcpService, registerMcpService } from "./mcpService"; // Mock the dependencies vi.mock("../mcp/tools", () => ({ initializeTools: vi.fn().mockResolvedValue({ listLibraries: { execute: vi.fn() }, findVersion: { execute: vi.fn() }, search: { execute: vi.fn() }, fetchUrl: { execute: vi.fn() }, scrape: { execute: vi.fn() }, refresh: { execute: vi.fn() }, listJobs: { execute: vi.fn() }, getJobInfo: { execute: vi.fn() }, cancelJob: { execute: vi.fn() }, remove: { execute: vi.fn() }, }), })); vi.mock("../mcp/mcpServer", () => ({ createMcpServerInstance: vi.fn().mockReturnValue({ connect: vi.fn().mockResolvedValue(undefined), close: vi.fn().mockResolvedValue(undefined), }), })); vi.mock("../telemetry", () => ({ telemetry: { isEnabled: () => false, }, })); vi.mock("../utils/logger", () => ({ logger: { info: vi.fn(), debug: vi.fn(), error: vi.fn(), }, })); describe("MCP Service", () => { let server: ReturnType<typeof Fastify>; let mockDocService: IDocumentManagement; let mockPipeline: IPipeline; beforeEach(() => { vi.useFakeTimers(); server = Fastify({ logger: false }); mockDocService = {} as IDocumentManagement; mockPipeline = {} as IPipeline; }); afterEach(async () => { vi.useRealTimers(); await server.close(); vi.clearAllMocks(); }); describe("SSE Heartbeat", () => { // Note: Actual heartbeat message verification is done in the E2E test (test/mcp-http-e2e.test.ts) // which can observe raw SSE stream data. Unit tests here focus on setup and cleanup. it("should cleanup heartbeat intervals on service cleanup", async () => { // Register the MCP service const mcpServer = await registerMcpService( server, mockDocService, mockPipeline, false, ); // Verify the heartbeat intervals map is attached const mcpServerWithInternals = mcpServer as unknown as { _heartbeatIntervals: Record<string, NodeJS.Timeout>; }; expect(mcpServerWithInternals._heartbeatIntervals).toBeDefined(); // Cleanup should not throw await expect(cleanupMcpService(mcpServer)).resolves.not.toThrow(); }); it("should store transport references for cleanup", async () => { // Register the MCP service const mcpServer = await registerMcpService( server, mockDocService, mockPipeline, false, ); // Verify the transports map is attached const mcpServerWithInternals = mcpServer as unknown as { _sseTransports: Record<string, unknown>; _heartbeatIntervals: Record<string, NodeJS.Timeout>; }; expect(mcpServerWithInternals._sseTransports).toBeDefined(); expect(mcpServerWithInternals._heartbeatIntervals).toBeDefined(); // Cleanup await cleanupMcpService(mcpServer); }); }); describe("Route Registration", () => { it("should register /sse endpoint", async () => { const mcpServer = await registerMcpService( server, mockDocService, mockPipeline, false, ); // Check that routes are registered (printRoutes uses a tree format) const routes = server.printRoutes(); expect(routes).toContain("sse"); await cleanupMcpService(mcpServer); }); it("should register /messages endpoint", async () => { const mcpServer = await registerMcpService( server, mockDocService, mockPipeline, false, ); // Fastify's printRoutes() uses a radix tree format where common prefixes are shared. // Routes /messages and /mcp share the "m" prefix, so they appear as: // └── m // ├── essages (POST) // └── cp (POST) // We check for "essages" which is the unique suffix for /messages. const routes = server.printRoutes(); expect(routes).toContain("essages"); await cleanupMcpService(mcpServer); }); it("should register /mcp endpoint", async () => { const mcpServer = await registerMcpService( server, mockDocService, mockPipeline, false, ); // Fastify's printRoutes() uses a radix tree format where common prefixes are shared. // Routes /messages and /mcp share the "m" prefix, so /mcp appears as "cp" in the tree. // We check for "cp (POST)" which uniquely identifies the /mcp route. const routes = server.printRoutes(); expect(routes).toContain("cp (POST)"); await cleanupMcpService(mcpServer); }); }); });

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/arabold/docs-mcp-server'

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