import { afterAll, expect, test } from "bun:test"; import { join } from "node:path"; import { connectMcp, mcpServersFor, mcpToolName } from "../src/mcp/client"; import { handle } from "./fixtures/fake-mcp"; const FAKE = join(import.meta.dir, "fixtures/fake-mcp.ts"); test("stdio: lists and calls tools", async () => { const c = await connectMcp({ id: "fake", name: "Fake", transport: "stdio", command: process.execPath, args: [FAKE] }); try { expect((await c.tools()).map((t) => t.name)).toEqual(["echo"]); expect(await c.call("echo", { text: "hi" })).toBe("echo: hi"); await expect(c.call("nope", {})).rejects.toThrow(/Unknown tool/); } finally { await c.close(); } }); test("stdio: a server that exits rejects instead of hanging", async () => { await expect(connectMcp({ id: "x", name: "X", transport: "stdio", command: process.execPath, args: [FAKE, "--exit-immediately"] }, { timeoutMs: 5000 })).rejects.toThrow(); }); const sessions: (string | null)[] = []; const server = Bun.serve({ port: 0, async fetch(req) { if (req.method === "DELETE") return new Response(null, { status: 204 }); const msg = await req.json(); sessions.push(req.headers.get("mcp-session-id")); const reply = handle(msg); if (!reply) return new Response(null, { status: 202 }); const headers: Record = msg.method === "initialize" ? { "mcp-session-id": "sess-42" } : {}; if (msg.method === "tools/call") { return new Response(`event: message\ndata: ${JSON.stringify(reply)}\n\n`, { headers: { ...headers, "content-type": "text/event-stream" } }); } return Response.json(reply, { headers }); }, }); afterAll(() => server.stop(true)); test("streamable HTTP: JSON and SSE replies, session id echoed", async () => { const c = await connectMcp({ id: "web", name: "Web", transport: "http", url: `http://127.0.0.1:${server.port}/mcp` }); expect((await c.tools()).map((t) => t.name)).toEqual(["echo"]); expect(await c.call("echo", { text: "over http" })).toBe("echo: over http"); await c.close(); expect(sessions[0]).toBeNull(); expect(sessions.slice(1).every((s) => s === "sess-42")).toBe(true); }); test("server selection and tool names", () => { const servers = [ { id: "g", name: "G", transport: "http" as const, url: "http://x" }, { id: "s", name: "S", transport: "http" as const, url: "http://y", courseIds: ["sample"] }, { id: "o", name: "O", transport: "http" as const, url: "http://z", courseIds: ["other"] }, ]; expect(mcpServersFor("sample", servers).map((s) => s.id)).toEqual(["g", "s"]); expect(mcpToolName("my server", "read file!")).toBe("mcp_my_server__read_file_"); expect(mcpToolName("a".repeat(40), "b".repeat(40)).length).toBeLessThanOrEqual(64); });