import assert from "node:assert/strict"; import test from "node:test"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { createProxyServer } from "../src/bridge.js"; test("stdio proxy forwards tools, resources, templates, reads, errors, and cursors", async () => { const calls: Array<{ method: string; params: unknown }> = []; const upstream = { listTools: async (params?: { cursor?: string }) => { calls.push({ method: "tools/list", params }); return { tools: [{ name: "list_spaces", description: "List spaces", inputSchema: { type: "object" } }], nextCursor: "tools-next" }; }, callTool: async (params: { name: string; arguments?: Record }) => { calls.push({ method: "tools/call", params }); if (params.name === "explode") throw new Error("upstream failed"); return { content: [{ type: "text", text: JSON.stringify(params.arguments) }] }; }, listResources: async (params?: { cursor?: string }) => { calls.push({ method: "resources/list", params }); return { resources: [{ uri: "gamecraft://spaces", name: "Spaces" }], nextCursor: "resources-next" }; }, listResourceTemplates: async (params?: { cursor?: string }) => { calls.push({ method: "resources/templates/list", params }); return { resourceTemplates: [{ uriTemplate: "gamecraft://space/{spaceId}", name: "Space" }] }; }, readResource: async (params: { uri: string }) => { calls.push({ method: "resources/read", params }); return { contents: [{ uri: params.uri, text: "resource-value" }] }; }, }; const server = createProxyServer(upstream); const client = new Client({ name: "bridge-test", version: "1.0.0" }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); try { await server.connect(serverTransport); await client.connect(clientTransport); assert.equal((await client.listTools({ cursor: "tool-cursor" })).nextCursor, "tools-next"); assert.equal((await client.listResources({ cursor: "resource-cursor" })).nextCursor, "resources-next"); assert.equal((await client.listResourceTemplates({ cursor: "template-cursor" })).resourceTemplates.length, 1); const resource = await client.readResource({ uri: "gamecraft://spaces" }); assert.equal("text" in resource.contents[0]! ? resource.contents[0].text : undefined, "resource-value"); const called = await client.callTool({ name: "list_spaces", arguments: { page: 2 } }); const content = called.content as Array<{ type: string; text?: string }>; assert.match(String(content[0]?.type === "text" ? content[0].text : ""), /\"page\":2/); await assert.rejects(client.callTool({ name: "explode", arguments: {} }), /upstream failed/); assert.deepEqual(calls.slice(0, 3), [ { method: "tools/list", params: { cursor: "tool-cursor" } }, { method: "resources/list", params: { cursor: "resource-cursor" } }, { method: "resources/templates/list", params: { cursor: "template-cursor" } }, ]); } finally { await client.close(); await server.close(); } });