Skip to main content
Glama

MCP Gemini Server

by bsmi021
streamableHttpTransport.test.vitest.ts8.52 kB
// Using vitest globals - see vitest.config.ts globals: true import { MCPTestClient } from "./clients/mcp-test-client.js"; import { spawn, ChildProcess } from "node:child_process"; interface Tool { name: string; description?: string; inputSchema?: unknown; } describe("Streamable HTTP Transport E2E Tests", () => { let serverProcess: ChildProcess | null = null; let client: MCPTestClient; const testPort = 3002; const baseUrl = `http://localhost:${testPort}`; // Store original environment variables const originalEnv = process.env; beforeEach(async () => { // Set environment variables for the test process.env = { ...originalEnv, MCP_TRANSPORT: "streamable", MCP_SERVER_PORT: testPort.toString(), MCP_ENABLE_STREAMING: "true", MCP_SESSION_TIMEOUT: "60", GOOGLE_GEMINI_API_KEY: process.env.GOOGLE_GEMINI_API_KEY || "test-api-key", GOOGLE_GEMINI_MODEL: "gemini-1.5-flash", NODE_ENV: "test", }; // Start the server await startServerProcess(); // Create test client client = new MCPTestClient(baseUrl); }); afterEach(async () => { // Close client if it has cleanup if (client && typeof client.close === "function") { await client.close(); } // Stop the server process if (serverProcess) { await stopServerProcess(); } // Restore environment process.env = originalEnv; vi.restoreAllMocks(); }); async function startServerProcess(): Promise<void> { return new Promise((resolve, reject) => { serverProcess = spawn("node", ["dist/server.js"], { env: process.env, stdio: "pipe", }); let serverReady = false; const timeout = setTimeout(() => { if (!serverReady) { reject(new Error("Server startup timeout")); } }, 15000); serverProcess!.stdout?.on("data", (data: Buffer) => { const output = data.toString(); console.log(`Server output: ${output}`); if ( output.includes("HTTP server listening") || output.includes(`port ${testPort}`) || output.includes("MCP Server connected and listening") ) { serverReady = true; clearTimeout(timeout); // Give server a moment to fully initialize setTimeout(() => resolve(), 500); } }); serverProcess!.stderr?.on("data", (data: Buffer) => { console.error(`Server error: ${data.toString()}`); }); serverProcess!.on("error", (error) => { clearTimeout(timeout); reject(new Error(`Failed to start server: ${error.message}`)); }); serverProcess!.on("exit", (code, signal) => { clearTimeout(timeout); if (!serverReady) { reject( new Error(`Server exited early: code ${code}, signal ${signal}`) ); } }); }); } async function stopServerProcess(): Promise<void> { if (!serverProcess) return; return new Promise((resolve) => { serverProcess!.on("exit", () => { serverProcess = null; resolve(); }); serverProcess!.kill("SIGTERM"); // Force kill after timeout setTimeout(() => { if (serverProcess) { serverProcess.kill("SIGKILL"); } }, 5000); }); } describe("Session Management", () => { it("should initialize a session and return session ID", async () => { const result = await client.initialize(); expect(result).toBeDefined(); expect(result.protocolVersion).toBe("2024-11-05"); expect(result.capabilities).toBeDefined(); expect(client.sessionId).toBeTruthy(); expect(client.sessionId).toMatch(/^[a-f0-9-]{36}$/); // UUID format }); it("should maintain session across multiple requests", async () => { // Initialize session await client.initialize(); const firstSessionId = client.sessionId; // Make another request with same session const tools = await client.listTools(); expect(tools).toBeDefined(); expect(client.sessionId).toBe(firstSessionId); }); it("should reject requests without valid session", async () => { // Don't initialize, just try to list tools await expect(client.listTools()).rejects.toThrow(); }); it("should handle session expiration gracefully", async () => { // This test would require waiting for session timeout or mocking time // For now, we'll just verify the session exists await client.initialize(); expect(client.sessionId).toBeTruthy(); }); }); describe("Tool Operations", () => { beforeEach(async () => { await client.initialize(); }); it("should list available tools", async () => { const result = await client.listTools(); expect(result).toBeDefined(); expect(result.tools).toBeInstanceOf(Array); expect(result.tools.length).toBeGreaterThan(0); // Check for some expected tools const toolNames = (result.tools as Tool[]).map((t) => t.name); expect(toolNames).toContain("gemini_generate_content"); expect(toolNames).toContain("gemini_start_chat"); }); it("should call a tool successfully", async () => { const result = await client.callTool("gemini_generate_content", { prompt: "Say hello in one word", modelName: "gemini-1.5-flash", }); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(result.content?.[0]).toBeDefined(); expect(result.content?.[0].text).toBeTruthy(); }); it("should handle tool errors gracefully", async () => { await expect(client.callTool("non_existent_tool", {})).rejects.toThrow(); }); }); describe("SSE Streaming", () => { beforeEach(async () => { await client.initialize(); }); it("should stream content using SSE", async () => { const chunks: string[] = []; const stream = await client.streamTool("gemini_generate_content_stream", { prompt: "Count from 1 to 3", modelName: "gemini-1.5-flash", }); // Collect chunks from the async iterable for await (const chunk of stream) { chunks.push(String(chunk)); } expect(chunks.length).toBeGreaterThan(0); expect(chunks.join("")).toContain("1"); expect(chunks.join("")).toContain("2"); expect(chunks.join("")).toContain("3"); }); it("should handle SSE connection errors", async () => { // Test with invalid session client.sessionId = "invalid-session-id"; await expect( client.streamTool("gemini_generate_content_stream", { prompt: "Test", modelName: "gemini-1.5-flash", }) ).rejects.toThrow(); }); }); describe("Transport Selection", () => { it("should use streamable transport when configured", async () => { // The server logs should indicate streamable transport is selected // This is more of a server configuration test await client.initialize(); // If we got here, the streamable transport is working expect(client.sessionId).toBeTruthy(); }); }); describe("CORS and Headers", () => { it("should handle CORS preflight requests", async () => { const response = await fetch(`${baseUrl}/mcp`, { method: "OPTIONS", headers: { Origin: "http://example.com", "Access-Control-Request-Method": "POST", "Access-Control-Request-Headers": "Content-Type, Mcp-Session-Id", }, }); expect(response.status).toBe(204); expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*"); expect(response.headers.get("Access-Control-Allow-Methods")).toContain( "POST" ); expect(response.headers.get("Access-Control-Allow-Headers")).toContain( "Mcp-Session-Id" ); }); it("should include proper headers in responses", async () => { await client.initialize(); const response = await fetch(`${baseUrl}/mcp`, { method: "POST", headers: { "Content-Type": "application/json", "Mcp-Session-Id": client.sessionId!, }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {}, }), }); expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*"); }); }); });

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/bsmi021/mcp-gemini-server'

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