import { expect, it } from "bun:test"; import type { Tool } from "@modelcontextprotocol/client"; import { boundedQuery, nativeQueryTimeout, queryTimeouts } from "../../src/mcp/query-timeouts.js"; const simulation: Tool = { name: "run_simulation", inputSchema: { type: "object", properties: { timeout_seconds: { type: "integer", minimum: 1, maximum: 240, default: 120 }, } } }; it("keeps HTTP budgets host-owned and separate from calculation duration", () => { expect(queryTimeouts(simulation, { duration: 86400, timeout_seconds: 240 })).toEqual({ serverMs: 120_000, callMs: 180_000, requestMs: 180_000 }); expect(queryTimeouts({ name: "list_elements", inputSchema: { type: "object" }, _meta: { "io.tjwater/request-timeout": { defaultSeconds: 240, maxSeconds: 240 }, } }).requestMs).toBe(270_000); expect(nativeQueryTimeout([simulation])).toBe(300_000); expect(nativeQueryTimeout([])).toBe(300_000); }); it("falls back safely for malformed advertised budgets", () => { for (const metadata of [null, [], { defaultSeconds: 999, maxSeconds: 999 }, { defaultSeconds: 240, maxSeconds: 120 }, { defaultSeconds: "120", maxSeconds: 240 }]) { expect(queryTimeouts({ ...simulation, _meta: { "io.tjwater/request-timeout": metadata } }).requestMs).toBe(180_000); } expect(queryTimeouts({ ...simulation, inputSchema: { type: "object", properties: { timeout_seconds: { type: "integer", maximum: 3600, default: 1800 }, } } }).requestMs).toBe(180_000); }); it("returns on deadline even when the underlying adapter ignores cancellation", async () => { await expect(boundedQuery(() => new Promise(() => {}), AbortSignal.timeout(10))).rejects.toThrow(); });