diff --git a/tests/boundary/mcp/contractPresentation.test.ts b/tests/boundary/mcp/contractPresentation.test.ts index cc7c720df9..bf48d91d9e 100644 --- a/tests/boundary/mcp/contractPresentation.test.ts +++ b/tests/boundary/mcp/contractPresentation.test.ts @@ -2,7 +2,7 @@ import { execFile } from "node:child_process"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { isCallToolResult } from "@modelcontextprotocol/server"; -import { expect, it } from "vitest"; +import { expect, it, onTestFinished } from "vitest"; import { z } from "zod"; import { BROWSER_TOOL_CONTRACTS } from "../../../src/contracts/browserToolContracts.js"; @@ -29,55 +29,62 @@ const resultSchema = z.object({ ), }); -it("preserves full advertised schemas and handler meaning when presentation order changes", async () => { - const { stdout } = await execute(process.execPath, [fixture, "reversed"], { - env: { - ...process.env, - REA_MCP_INPUT_SCHEMA_PROFILE: "full", - REA_MCP_OUTPUT_SCHEMA_PROFILE: "full", - }, - // The child advertises the full catalog and calls every browser, Electron, - // and JavaScript example. A busy runner exceeded 20s and was killed. - timeout: 60_000, - maxBuffer: 16 * 1024 * 1024, - }); - const result = resultSchema.parse(JSON.parse(stdout)); - expect(result.catalog).toHaveLength(GENERATED_MCP_TOOL_CATALOG.length); - expect(new Set(result.catalog.map(({ name }) => name))).toEqual( - new Set(GENERATED_MCP_TOOL_CATALOG.map(({ name }) => name)), - ); - for (const advertised of result.catalog) { - const canonical = GENERATED_MCP_TOOL_CATALOG.find( - ({ name }) => name === advertised.name, +it( + "preserves full advertised schemas and handler meaning when presentation order changes", + { timeout: 70_000 }, + async () => { + const cancellation = new AbortController(); + onTestFinished(() => cancellation.abort()); + const { stdout } = await execute(process.execPath, [fixture, "reversed"], { + env: { + ...process.env, + REA_MCP_INPUT_SCHEMA_PROFILE: "full", + REA_MCP_OUTPUT_SCHEMA_PROFILE: "full", + }, + // The child advertises the full catalog and calls every browser, Electron, + // and JavaScript example. A busy runner exceeded 20s and was killed. + timeout: 60_000, + signal: cancellation.signal, + maxBuffer: 16 * 1024 * 1024, + }); + const result = resultSchema.parse(JSON.parse(stdout)); + expect(result.catalog).toHaveLength(GENERATED_MCP_TOOL_CATALOG.length); + expect(new Set(result.catalog.map(({ name }) => name))).toEqual( + new Set(GENERATED_MCP_TOOL_CATALOG.map(({ name }) => name)), ); - expect(advertised.inputSchema).toEqual(canonical?.inputSchema); - expect(advertised.outputSchema).toEqual(canonical?.outputSchema); - } - expect(result.responses.map(({ name }) => name).sort()).toEqual( - [ - ...BROWSER_TOOL_CONTRACTS, - ...BROWSER_SCENARIO_TOOL_CONTRACTS, - ...ELECTRON_TOOL_CONTRACTS.filter( - ({ name }) => name !== "analyze_javascript_application", - ), - ...JAVASCRIPT_RUNTIME_OBSERVATION_TOOL_CONTRACTS, - ] - .map(({ name }) => name) - .sort(), - ); - for (const { name, response } of result.responses) { - if (!isCallToolResult(response)) - throw new Error(`Invalid tools/call fixture result for ${name}`); - if (name === "reconcile_javascript_runtime") { - expect(response.isError).not.toBe(true); - expect(response.structuredContent).toMatchObject({ - operation: name, - }); - } else { - expect(response.isError, name).toBe(true); - expect(parseMcpToolError(response), name).toMatchObject({ - error: { details: { operation: name } }, - }); + for (const advertised of result.catalog) { + const canonical = GENERATED_MCP_TOOL_CATALOG.find( + ({ name }) => name === advertised.name, + ); + expect(advertised.inputSchema).toEqual(canonical?.inputSchema); + expect(advertised.outputSchema).toEqual(canonical?.outputSchema); } - } -}); + expect(result.responses.map(({ name }) => name).sort()).toEqual( + [ + ...BROWSER_TOOL_CONTRACTS, + ...BROWSER_SCENARIO_TOOL_CONTRACTS, + ...ELECTRON_TOOL_CONTRACTS.filter( + ({ name }) => name !== "analyze_javascript_application", + ), + ...JAVASCRIPT_RUNTIME_OBSERVATION_TOOL_CONTRACTS, + ] + .map(({ name }) => name) + .sort(), + ); + for (const { name, response } of result.responses) { + if (!isCallToolResult(response)) + throw new Error(`Invalid tools/call fixture result for ${name}`); + if (name === "reconcile_javascript_runtime") { + expect(response.isError).not.toBe(true); + expect(response.structuredContent).toMatchObject({ + operation: name, + }); + } else { + expect(response.isError, name).toBe(true); + expect(parseMcpToolError(response), name).toMatchObject({ + error: { details: { operation: name } }, + }); + } + } + }, +);