import { readFile } from "node:fs/promises"; import type { Server } from "node:http"; import type { AddressInfo } from "node:net"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { afterEach, describe, expect, it } from "vitest"; import { EXAMPLE_ECHO_TOOL_NAME } from "../src/capabilities/index.js"; import { startHttpServer } from "../src/http.js"; const testsDir = dirname(fileURLToPath(import.meta.url)); const projectRoot = resolve(testsDir, ".."); const packageJsonPath = resolve(projectRoot, "package.json"); let activeServer: Server | undefined; afterEach(async () => { if (activeServer !== undefined) { await new Promise((resolveClose, rejectClose) => { activeServer?.close((error) => { if (error) { rejectClose(error); return; } resolveClose(); }); }); activeServer = undefined; } }); async function startTestServer(): Promise { activeServer = await startHttpServer({ host: "127.0.0.1", port: 0 }); const address = activeServer.address() as AddressInfo; return new URL(`http://127.0.0.1:${address.port}/mcp`); } describe("HTTP entrypoint", () => { it("supports streamable HTTP initialize flow and shared capabilities", async () => { const endpoint = await startTestServer(); const transport = new StreamableHTTPClientTransport(endpoint); const client = new Client({ name: "http-test-client", version: "0.0.0" }, { capabilities: {} }); await client.connect(transport); const tools = await client.listTools(); expect(tools.tools.map((tool) => tool.name)).toContain(EXAMPLE_ECHO_TOOL_NAME); await client.close(); await transport.close(); }); it("returns 404 on unknown routes", async () => { await startTestServer(); const address = activeServer?.address() as AddressInfo; const response = await fetch(`http://127.0.0.1:${address.port}/not-mcp`); expect(response.status).toBe(404); await expect(response.json()).resolves.toMatchObject({ error: "Not Found", }); }); it("returns explicit invalid-session errors", async () => { await startTestServer(); const address = activeServer?.address() as AddressInfo; const response = await fetch(`http://127.0.0.1:${address.port}/mcp`, { method: "POST", headers: { "content-type": "application/json", "mcp-session-id": "missing-session", }, body: JSON.stringify({ jsonrpc: "2.0", id: "invalid-session", method: "ping", params: {}, }), }); expect(response.status).toBe(404); await expect(response.json()).resolves.toMatchObject({ jsonrpc: "2.0", error: { message: "Session not found", }, id: null, }); }); it("publishes an HTTP entry command", async () => { const packageJsonRaw = await readFile(packageJsonPath, "utf8"); const packageJson = JSON.parse(packageJsonRaw) as { scripts?: Record; }; expect(packageJson.scripts?.["dev:http"]).toBe("tsx src/http.ts"); }); });