Skip to main content
Glama
http-server.test.ts8.07 kB
import { spawn } from "node:child_process"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { createMockBuckets } from "../mocks/s3Client.mock"; // Mock the S3Client for HTTP server tests vi.mock("@aws-sdk/client-s3", async () => { const actual = await vi.importActual("@aws-sdk/client-s3"); const mockSend = vi.fn().mockResolvedValue({ Buckets: createMockBuckets(3), }); return { ...actual, S3Client: vi.fn().mockImplementation(() => ({ send: mockSend, config: {}, middlewareStack: { clone: () => ({ use: () => {} }) }, destroy: () => {}, })), }; }); describe("HTTP MCP Server", () => { const TEST_PORT = 3001; let serverProcess: any; let baseUrl: string; beforeAll(async () => { baseUrl = `http://localhost:${TEST_PORT}`; // Set environment for HTTP mode process.env.MCP_TRANSPORT = "http"; process.env.PORT = TEST_PORT.toString(); process.env.S3_BUCKETS = "test-bucket-1,test-bucket-2"; // Start the HTTP server using new architecture const serverPath = path.resolve(process.cwd(), "dist/index.js"); serverProcess = spawn("node", [serverPath, "--http"], { env: { ...process.env, PORT: TEST_PORT.toString(), S3_BUCKETS: "test-bucket-1,test-bucket-2", }, stdio: ["pipe", "pipe", "pipe"], }); // Wait for server to start await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Server failed to start within timeout")); }, 10000); const checkServer = async () => { try { const response = await fetch(`${baseUrl}/health`); if (response.ok) { clearTimeout(timeout); resolve(true); } else { setTimeout(checkServer, 100); } } catch { setTimeout(checkServer, 100); } }; checkServer(); }); }, 15000); afterAll(async () => { if (serverProcess) { serverProcess.kill("SIGTERM"); // Wait for process to exit await new Promise((resolve) => { serverProcess.on("exit", resolve); setTimeout(() => { serverProcess.kill("SIGKILL"); resolve(true); }, 5000); }); } process.env.MCP_TRANSPORT = undefined; process.env.PORT = undefined; process.env.S3_BUCKETS = undefined; }); it("should respond to health check endpoint", async () => { const response = await fetch(`${baseUrl}/health`); expect(response.status).toBe(200); const data = await response.json(); expect(data).toHaveProperty("status", "ok"); expect(data).toHaveProperty("timestamp"); }); it("should handle MCP initialization request", async () => { const initRequest = { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "test-client", version: "1.0.0", }, }, }; const response = await fetch(`${baseUrl}/mcp`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(initRequest), }); expect(response.status).toBe(200); const data = await response.json(); expect(data).toHaveProperty("jsonrpc", "2.0"); expect(data).toHaveProperty("id", 1); expect(data).toHaveProperty("result"); }); it("should handle tools/list request", async () => { // First initialize the session const initRequest = { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "test-client", version: "1.0.0", }, }, }; const sessionId = "test-session-2"; const initResponse = await fetch(`${baseUrl}/mcp?sessionId=${sessionId}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(initRequest), }); expect(initResponse.status).toBe(200); // Now make the tools/list request const toolsListRequest = { jsonrpc: "2.0", id: 2, method: "tools/list", }; const response = await fetch(`${baseUrl}/mcp?sessionId=${sessionId}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(toolsListRequest), }); expect(response.status).toBe(200); const data = await response.json(); expect(data).toHaveProperty("jsonrpc", "2.0"); expect(data).toHaveProperty("id", 2); expect(data).toHaveProperty("result"); expect(data.result).toHaveProperty("tools"); const tools = data.result.tools; expect(tools).toBeInstanceOf(Array); expect(tools.length).toBeGreaterThan(0); // Check that expected S3 tools are present const toolNames = tools.map((tool: any) => tool.name); expect(toolNames).toContain("list-buckets"); expect(toolNames).toContain("list-objects"); expect(toolNames).toContain("get-object"); }); it("should handle list-buckets tool call", async () => { // First initialize the session const initRequest = { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "test-client", version: "1.0.0", }, }, }; const sessionId = "test-session-3"; const initResponse = await fetch(`${baseUrl}/mcp?sessionId=${sessionId}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(initRequest), }); expect(initResponse.status).toBe(200); // Now make the tool call const toolCallRequest = { jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "list-buckets", arguments: {}, }, }; const response = await fetch(`${baseUrl}/mcp?sessionId=${sessionId}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(toolCallRequest), }); expect(response.status).toBe(200); const data = await response.json(); expect(data).toHaveProperty("jsonrpc", "2.0"); expect(data).toHaveProperty("id", 3); expect(data).toHaveProperty("result"); expect(data.result).toHaveProperty("content"); }); it("should handle CORS preflight requests", async () => { const response = await fetch(`${baseUrl}/mcp`, { method: "OPTIONS", headers: { Origin: "http://localhost:3000", "Access-Control-Request-Method": "POST", "Access-Control-Request-Headers": "Content-Type", }, }); expect(response.status).toBe(204); expect(response.headers.get("access-control-allow-origin")).toBe("*"); expect(response.headers.get("access-control-allow-methods")).toContain("POST"); }); it("should handle invalid JSON-RPC requests", async () => { const invalidRequest = { invalid: "request", }; const response = await fetch(`${baseUrl}/mcp`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(invalidRequest), }); // StreamableHTTPServerTransport returns 400 for parse errors expect(response.status).toBe(400); const data = await response.json(); expect(data).toHaveProperty("jsonrpc", "2.0"); expect(data).toHaveProperty("error"); }); it("should handle malformed JSON", async () => { const response = await fetch(`${baseUrl}/mcp`, { method: "POST", headers: { "Content-Type": "application/json", }, body: "invalid json", }); expect(response.status).toBe(500); const data = await response.json(); expect(data).toHaveProperty("error", "Internal server error"); }); });

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/samuraikun/aws-s3-mcp'

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