import { describe, expect, test } from "bun:test"; import { MCPConnectionPool, MCPPoolAcquireAbortError, MCPPoolLeaseInvalidatedError } from "./pool"; import { MCPPoolConfigError } from "./pool-key"; import { legacyEraObservation } from "./protocol"; import type { MCPRequestOptions, MCPServerConfig, MCPServerConnection, MCPTransport } from "./types"; import { MCPExpectedFailure, MCPNotificationMethods } from "./types"; class FakeTransport implements MCPTransport { connected = true; closeCount = 0; requests: string[] = []; notifications: string[] = []; onClose?: () => void; failSubscribe = false; failUnsubscribe = false; failSubscribeUri?: string; failUnsubscribeUri?: string; onError?: (error: Error) => void; onNotification?: (method: string, params: unknown) => void; onRequest?: (method: string, params: unknown) => Promise; async request( method: string, params?: Record, _options?: MCPRequestOptions, ): Promise { this.requests.push(method); const uri = typeof params?.uri === "string" ? params.uri : undefined; if (method === "resources/subscribe") { if (this.failSubscribe || uri === this.failSubscribeUri) throw new Error("subscribe failed"); return {} as T; } if (method === "resources/unsubscribe") { if (this.failUnsubscribe || uri === this.failUnsubscribeUri) throw new Error("unsubscribe failed"); return {} as T; } return {} as T; } async notify(method: string): Promise { if (!this.connected) throw new MCPExpectedFailure(); this.notifications.push(method); } async close(): Promise { this.closeCount += 1; this.connected = false; } } class DelayedSubscriptionTransport extends FakeTransport { readonly subscribeStarted = Promise.withResolvers(); readonly allowSubscribe = Promise.withResolvers(); override async request( method: string, params?: Record, options?: MCPRequestOptions, ): Promise { if (method === "resources/subscribe") { this.subscribeStarted.resolve(); await this.allowSubscribe.promise; } return super.request(method, params, options); } } class CrashCallTransport extends FakeTransport { callCount = 0; readonly callStarted = Promise.withResolvers(); #rejectCall?: (error: Error) => void; override request( method: string, params?: Record, options?: MCPRequestOptions, ): Promise { if (method !== "tools/call") return super.request(method, params, options); this.callCount += 1; this.callStarted.resolve(); return new Promise((_resolve, reject) => { this.#rejectCall = reject; }); } crash(): void { this.connected = false; const failure = new MCPExpectedFailure(new Error("shared transport crashed")); this.#rejectCall?.(failure); this.#rejectCall = undefined; this.onError?.(failure); this.onClose?.(); } } function config(sharing?: "per-session" | "shared"): MCPServerConfig { return { type: "stdio", command: "fake-mcp", args: ["--test"], sharing }; } function connection(name: string, configValue: MCPServerConfig, transport: FakeTransport): MCPServerConnection { return { name, config: configValue, transport, serverInfo: { name: "fake", version: "1" }, capabilities: { tools: {}, resources: { subscribe: true } }, protocol: legacyEraObservation({ preference: "auto", effectiveVersion: "2025-03-26", negotiation: "legacy-forced", downgradeReason: "stdio-transport", serverInfo: { name: "fake", version: "1" }, capabilities: { tools: true, resources: true }, }), }; } test("shared leases broadcast catalog notifications, union roots, and reject unknown notifications", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const first = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); const second = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); const firstEvents: string[] = []; const secondEvents: string[] = []; first.onEvent(event => event.type === "notification" && firstEvents.push(event.method)); second.onEvent(event => event.type === "notification" && secondEvents.push(event.method)); first.updateRoots([{ uri: "file:///one", name: "one" }]); second.updateRoots([{ uri: "file:///two", name: "two" }]); expect(await transport.onRequest?.("roots/list", {})).toEqual({ roots: [ { uri: "file:///one", name: "one" }, { uri: "file:///two", name: "two" }, ], }); await Bun.sleep(0); expect(transport.notifications).toEqual(["notifications/roots/list_changed"]); transport.onNotification?.(MCPNotificationMethods.TOOLS_LIST_CHANGED, {}); transport.onNotification?.("notifications/unknown", {}); expect(firstEvents).toEqual([MCPNotificationMethods.TOOLS_LIST_CHANGED]); expect(secondEvents).toEqual([MCPNotificationMethods.TOOLS_LIST_CHANGED]); expect(pool.getHealth()[0]?.events.some(event => event.message?.includes("Unsupported MCP notification"))).toBe( true, ); await first.release(); expect(await transport.onRequest?.("roots/list", {})).toEqual({ roots: [{ uri: "file:///two", name: "two" }] }); await second.release(); await Bun.sleep(0); expect(transport.notifications).toEqual([ "notifications/roots/list_changed", "notifications/roots/list_changed", "notifications/roots/list_changed", ]); }); test("a shared crash rejects only the in-flight calling lease with a typed error", async () => { const transport = new CrashCallTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const first = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); const second = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); const call = first.request("tools/call", { name: "work" }); await transport.callStarted.promise; transport.crash(); await expect(call).rejects.toBeInstanceOf(MCPExpectedFailure); expect(transport.callCount).toBe(1); await first.release(); await second.release(); }); test("shared tools-only entries reject sampling and elicitation requests and gate restart ownership", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("server", config("shared"), { sharingMode: "shared", capabilityProfile: "tools-only", }); await expect(transport.onRequest?.("sampling/createMessage", {})).rejects.toMatchObject({ code: -32601 }); await expect(transport.onRequest?.("elicitation/create", {})).rejects.toMatchObject({ code: -32601 }); expect(pool.claimRestart(lease.key)).toBe(true); expect(pool.claimRestart(lease.key)).toBe(false); pool.releaseRestart(lease.key); expect(pool.claimRestart(lease.key)).toBe(true); pool.releaseRestart(lease.key); await lease.release(); }); test("shared SSE leases retain one physical callback transport until the last release", async () => { let opens = 0; const transport = new FakeTransport(); const pool = new MCPConnectionPool({ sharedPoolIdleMs: 0, connect: async (name, cfg) => { opens += 1; return connection(name, cfg, transport); }, }); const configValue: MCPServerConfig = { type: "sse", url: "https://example.test/events", sharing: "shared" }; const first = await pool.acquire("remote", configValue, { sharingMode: "shared" }); const second = await pool.acquire("remote", configValue, { sharingMode: "shared" }); expect(opens).toBe(1); await first.release(); expect(transport.closeCount).toBe(0); await second.release(); expect(transport.closeCount).toBe(1); }); test("releasing subscribed leases after shared transport close detaches locally without dead RPC", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const first = await pool.acquire("remote", config("shared"), { sharingMode: "shared" }); const second = await pool.acquire("remote", config("shared"), { sharingMode: "shared" }); await first.setResourceSubscriptions(["file:///resource"]); await second.setResourceSubscriptions(["file:///resource"]); transport.connected = false; transport.onClose?.(); await first.release(); await second.release(); expect(transport.requests.filter(method => method === "resources/unsubscribe")).toHaveLength(0); expect(pool.size).toBe(0); }); test("repeated shared crash cleanup does not retain retired entries", async () => { let opens = 0; const transports: FakeTransport[] = []; const pool = new MCPConnectionPool({ connect: async (name, cfg) => { opens += 1; const transport = new FakeTransport(); transports.push(transport); return connection(name, cfg, transport); }, }); for (let index = 0; index < 5; index += 1) { const lease = await pool.acquire("remote", config("shared"), { sharingMode: "shared" }); transports[index]?.onClose?.(); await lease.release(); expect(pool.size).toBe(0); } expect(opens).toBe(5); }); test("retired lease already closed is removed during release cleanup", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("remote", config("shared"), { sharingMode: "shared" }); pool.retireLease(lease); transport.connected = false; transport.onClose?.(); await lease.release(); expect(pool.size).toBe(0); await pool.shutdown(); expect(transport.closeCount).toBe(0); }); describe("MCPConnectionPool", () => { test("ref-counts shared leases and closes on final release", async () => { let opens = 0; const transports: FakeTransport[] = []; const pool = new MCPConnectionPool({ sharedPoolIdleMs: 0, connect: async (name, cfg) => { opens += 1; const transport = new FakeTransport(); transports.push(transport); return connection(name, cfg, transport); }, }); const first = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); const second = await pool.acquire("server", config("shared"), { sharingMode: "shared", sessionId: "ignored" }); expect(first.key).toBe(second.key); expect(opens).toBe(1); expect(pool.getHealth()[0]?.refCount).toBe(2); await first.release(); expect(pool.getHealth()[0]?.refCount).toBe(1); await second.release(); await Bun.sleep(0); expect(transports[0]?.closeCount).toBe(1); expect(pool.size).toBe(0); }); test("keeps distinct physical entries for per-session leases", async () => { let opens = 0; const pool = new MCPConnectionPool({ connect: async (name, cfg) => { opens += 1; return connection(name, cfg, new FakeTransport()); }, }); const first = await pool.acquire("server", config(), { sessionId: "one" }); const second = await pool.acquire("server", config(), { sessionId: "two" }); expect(first.key).not.toBe(second.key); expect(opens).toBe(2); expect(pool.size).toBe(2); await pool.shutdown(); expect(pool.size).toBe(0); }); test("rejects per-session acquire without a non-empty session id", async () => { const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, new FakeTransport()) }); await expect(pool.acquire("server", config())).rejects.toBeInstanceOf(MCPPoolConfigError); await expect(pool.acquire("server", config(), { sessionId: "" })).rejects.toMatchObject({ name: "MCPPoolConfigError", code: "MCP_SESSION_ID_REQUIRED", }); }); test("aggregates resource subscriptions across shared leases", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ sharedPoolIdleMs: 0, connect: async (name, cfg) => connection(name, cfg, transport), }); const first = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); const second = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); await first.setResourceSubscriptions(["file:///same"]); await second.setResourceSubscriptions(["file:///same"]); expect(transport.requests.filter(method => method === "resources/subscribe")).toHaveLength(1); await first.release(); expect(transport.requests.filter(method => method === "resources/unsubscribe")).toHaveLength(0); await second.release(); expect(transport.requests.filter(method => method === "resources/unsubscribe")).toHaveLength(1); }); test("release waits for an in-flight shared subscription update before removing its counts", async () => { const transport = new DelayedSubscriptionTransport(); const pool = new MCPConnectionPool({ sharedPoolIdleMs: 0, connect: async (name, cfg) => connection(name, cfg, transport), }); const lease = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); const setPromise = lease.setResourceSubscriptions(["file:///race"]); await transport.subscribeStarted.promise; const releasePromise = lease.release(); await expect(lease.setResourceSubscriptions(["file:///late"])).resolves.toBeUndefined(); let releaseSettled = false; void releasePromise.finally(() => { releaseSettled = true; }); await Bun.sleep(0); expect(releaseSettled).toBe(false); transport.allowSubscribe.resolve(); await setPromise; await releasePromise; expect(transport.requests.filter(method => method === "resources/subscribe")).toHaveLength(1); expect(transport.requests.filter(method => method === "resources/unsubscribe")).toHaveLength(1); const replacementLease = await pool.acquire("server", config("shared"), { sharingMode: "shared" }); await replacementLease.setResourceSubscriptions(["file:///race"]); expect(transport.requests.filter(method => method === "resources/subscribe")).toHaveLength(2); await replacementLease.release(); expect(transport.requests.filter(method => method === "resources/unsubscribe")).toHaveLength(2); await pool.shutdown(); }); test("aborted acquire aborts the opener and closes a late transport", async () => { let resolveOpen: ((value: MCPServerConnection) => void) | undefined; let openSignal: AbortSignal | undefined; const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (_name, _cfg, options) => { openSignal = options.signal; return new Promise(resolve => { resolveOpen = resolve; }); }, }); const controller = new AbortController(); const acquire = pool.acquire("server", config(), { sessionId: "aborted", signal: controller.signal }); await Bun.sleep(0); controller.abort(new Error("caller aborted")); await expect(acquire).rejects.toThrow("caller aborted"); expect(openSignal?.aborted).toBe(true); resolveOpen?.(connection("server", config(), transport)); await Bun.sleep(0); expect(transport.closeCount).toBe(1); await pool.shutdown(); }); test("shutdown aborts hanging opens, settles waiters, and closes late transports", async () => { let resolveOpen: ((value: MCPServerConnection) => void) | undefined; let openSignal: AbortSignal | undefined; const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (_name, _cfg, options) => { openSignal = options.signal; return new Promise(resolve => { resolveOpen = resolve; }); }, }); const acquire = pool.acquire("server", config(), { sessionId: "shutdown" }); await Bun.sleep(0); const shutdown = pool.shutdown(); await expect(acquire).rejects.toThrow("MCP connection pool shut down"); expect(openSignal?.aborted).toBe(true); await shutdown; resolveOpen?.(connection("server", config(), transport)); await Bun.sleep(0); expect(transport.closeCount).toBe(1); }); test("transport close is recorded once and release does not close it again", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("server", config(), { sessionId: "closed" }); transport.onClose?.(); await lease.release(); expect(transport.closeCount).toBe(1); expect(pool.size).toBe(0); }); test("forwards requests, notifications, roots and resource subscriptions through a lease", async () => { let transport: FakeTransport | undefined; const pool = new MCPConnectionPool({ connect: async (name, cfg) => { transport = new FakeTransport(); return connection(name, cfg, transport); }, }); const lease = await pool.acquire("server", config(), { sessionId: "one" }); const events: string[] = []; lease.onEvent(event => { if (event.type === "notification") events.push(event.method); }); lease.updateRoots([{ uri: "file:///workspace", name: "workspace" }]); await lease.setResourceSubscriptions(["file:///resource"]); await lease.request("ping"); transport?.onNotification?.("notifications/tools/list_changed", {}); expect(events).toEqual(["notifications/tools/list_changed"]); expect(await transport?.onRequest?.("roots/list", {})).toEqual({ roots: [{ uri: "file:///workspace", name: "workspace" }], }); await lease.release(); }); test("aborting one pending waiter leaves the other waiter interested", async () => { let resolveOpen: ((value: MCPServerConnection) => void) | undefined; const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (_name, _cfg) => new Promise(resolve => { resolveOpen = resolve; }), }); const firstController = new AbortController(); const secondController = new AbortController(); const options = { sessionId: "waiters" }; const first = pool.acquire("server", config(), { ...options, signal: firstController.signal }); const second = pool.acquire("server", config(), { ...options, signal: secondController.signal }); await Bun.sleep(0); firstController.abort(new Error("first waiter aborted")); await expect(first).rejects.toBeInstanceOf(MCPPoolAcquireAbortError); resolveOpen?.(connection("server", config(), transport)); const lease = await second; expect(lease.connection.transport).toBe(transport); await lease.release(); expect(transport.closeCount).toBe(1); await pool.shutdown(); }); test("aborting the later pending waiter leaves the first waiter interested", async () => { let resolveOpen: ((value: MCPServerConnection) => void) | undefined; const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (_name, _cfg) => new Promise(resolve => { resolveOpen = resolve; }), }); const firstController = new AbortController(); const secondController = new AbortController(); const options = { sessionId: "waiters-later" }; const first = pool.acquire("server", config(), { ...options, signal: firstController.signal }); const second = pool.acquire("server", config(), { ...options, signal: secondController.signal }); await Bun.sleep(0); secondController.abort(new Error("second waiter aborted")); await expect(second).rejects.toBeInstanceOf(MCPPoolAcquireAbortError); resolveOpen?.(connection("server", config(), transport)); const lease = await first; expect(lease.connection.transport).toBe(transport); await lease.release(); expect(transport.closeCount).toBe(1); await pool.shutdown(); }); test("shutdown invalidates all leases before closing their transport", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("server", config(), { sessionId: "invalidated" }); await pool.shutdown(); expect(() => lease.request("ping")).toThrow(MCPPoolLeaseInvalidatedError); expect(() => lease.notify("notifications/test")).toThrow(MCPPoolLeaseInvalidatedError); expect(() => lease.updateRoots([])).toThrow(MCPPoolLeaseInvalidatedError); await expect(lease.setResourceSubscriptions(["file:///resource"])).rejects.toBeInstanceOf( MCPPoolLeaseInvalidatedError, ); expect(transport.requests).toEqual([]); }); test("failed subscription accounting can retry and failed unsubscription remains retryable", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("server", config(), { sessionId: "subscription-failures" }); transport.failSubscribe = true; await expect(lease.setResourceSubscriptions(["file:///resource"])).rejects.toBeInstanceOf(AggregateError); transport.failSubscribe = false; await lease.setResourceSubscriptions(["file:///resource"]); expect(transport.requests.filter(method => method === "resources/subscribe")).toHaveLength(2); transport.failUnsubscribe = true; await expect(lease.setResourceSubscriptions([])).rejects.toBeInstanceOf(AggregateError); transport.failUnsubscribe = false; await lease.setResourceSubscriptions([]); expect(transport.requests.filter(method => method === "resources/unsubscribe")).toHaveLength(2); await lease.release(); await pool.shutdown(); }); test("resolve-then-abort-all closes a zero-claim handoff entry", async () => { let resolveOpen: ((value: MCPServerConnection) => void) | undefined; const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async () => new Promise(resolve => { resolveOpen = resolve; }), }); const firstController = new AbortController(); const secondController = new AbortController(); const first = pool.acquire("server", config(), { sessionId: "handoff", signal: firstController.signal }); const second = pool.acquire("server", config(), { sessionId: "handoff", signal: secondController.signal }); first.catch(() => {}); second.catch(() => {}); await Bun.sleep(0); resolveOpen?.(connection("server", config(), transport)); firstController.abort(new Error("first aborted at handoff")); secondController.abort(new Error("second aborted at handoff")); await expect(first).rejects.toBeInstanceOf(MCPPoolAcquireAbortError); await expect(second).rejects.toBeInstanceOf(MCPPoolAcquireAbortError); await Bun.sleep(0); await Bun.sleep(0); expect(transport.closeCount).toBe(1); expect(pool.size).toBe(0); await pool.shutdown(); }); test("onEvent rejects after pool shutdown invalidates the lease", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("server", config(), { sessionId: "event-invalidated" }); await pool.shutdown(); expect(() => lease.onEvent(() => {})).toThrow(MCPPoolLeaseInvalidatedError); }); test("shutdown invalidates leases whose transport already removed the pool entry", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("server", config(), { sessionId: "removed-entry" }); transport.onClose?.(); await pool.shutdown(); expect(() => lease.onEvent(() => {})).toThrow(MCPPoolLeaseInvalidatedError); }); test("mixed subscription batch compensates partial success before retry", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("server", config(), { sessionId: "mixed-subscriptions" }); await lease.setResourceSubscriptions(["file:///old"]); transport.failSubscribeUri = "file:///new"; await expect(lease.setResourceSubscriptions(["file:///new"])).rejects.toBeInstanceOf(AggregateError); transport.failSubscribeUri = undefined; await lease.setResourceSubscriptions(["file:///new"]); expect(transport.requests.filter(method => method === "resources/subscribe")).toHaveLength(4); expect(transport.requests.filter(method => method === "resources/unsubscribe")).toHaveLength(2); await lease.release(); }); test("failed final release keeps subscriptions retryable", async () => { const transport = new FakeTransport(); const pool = new MCPConnectionPool({ connect: async (name, cfg) => connection(name, cfg, transport) }); const lease = await pool.acquire("server", config(), { sessionId: "release-retry" }); await lease.setResourceSubscriptions(["file:///old"]); transport.failUnsubscribe = true; await expect(lease.release()).rejects.toBeInstanceOf(AggregateError); transport.failUnsubscribe = false; await lease.release(); expect(transport.requests.filter(method => method === "resources/unsubscribe")).toHaveLength(2); expect(pool.size).toBe(0); await pool.shutdown(); }); test("health output is bounded and redacted", async () => { let transport: FakeTransport | undefined; const pool = new MCPConnectionPool({ connect: async (name, cfg) => { transport = new FakeTransport(); return connection(name, cfg, transport); }, }); const lease = await pool .acquire("secret-server", { type: "http", url: "https://user:secret@example.test/mcp" }, { sessionId: "s" }) .catch(() => undefined); if (lease) await lease.release(); // Userinfo rejection happens before opening; use a valid endpoint for event health. const validLease = await pool.acquire( "secret-server", { type: "http", url: "https://example.test/mcp", headers: { Authorization: "Bearer top-secret" } }, { sessionId: "s" }, ); for (let index = 0; index < 30; index += 1) transport?.onError?.(new Error(`https://example.test/mcp?token=top-secret ${"x".repeat(600)}`)); const health = pool.getHealth()[0]; expect(health?.events.length).toBeLessThanOrEqual(20); expect(JSON.stringify(health)).not.toContain("top-secret"); expect(health?.events.every(event => !event.message || event.message.length <= 512)).toBe(true); await validLease.release(); }); });