import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { Ajv2020 } from "ajv/dist/2020.js"; import { expect, it, onTestFinished } from "vitest"; import { toolContract } from "../../../src/contracts/toolContracts.js"; import { parseEvidence } from "../../../src/domain/evidence.js"; import { createServer } from "../../../src/server/createServer.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { assertRecoveryCleanup, recoveryFixture, } from "../../fixtures/javascript-recovery/provider.js"; it .skipIf(process.platform !== "linux" || process.arch !== "x64") .each([false, true])( "advertises and executes recovery through the named SDK contract (multiple inputs: %s)", async (multiple) => { const fixture = await recoveryFixture(); const session = createTestBinarySession(() => { throw new Error("no binary provider should start"); }); const server = createServer( { kind: "session", session }, { javascriptRecovery: fixture.provider, }, ); const client = new Client({ name: "recovery-contract", version: "1" }); onTestFinished(async () => { await client.close(); await server.close(); await session.close(); }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); await server.connect(serverTransport); await client.connect(clientTransport); const tools = await client.listTools(); const advertised = tools.tools.find( (tool) => tool.name === "recover_javascript_sources", ); if (advertised === undefined || advertised.outputSchema === undefined) throw new Error("recovery tool must advertise both schemas"); const ajv = new Ajv2020({ strict: false, validateFormats: false }); const inputSchema: Record = advertised.inputSchema; const outputSchema: Record = advertised.outputSchema; expect(ajv.validateSchema(inputSchema)).toBe(true); expect(ajv.validateSchema(outputSchema)).toBe(true); const additional = join(fixture.root, "chunk.js"); if (multiple) await writeFile(additional, "export const chunk = 1;"); const args = { path: fixture.path, output_directory: fixture.output, ...(multiple ? { additional_paths: [additional] } : {}), }; expect(ajv.validate(inputSchema, args)).toBe(true); const response = await client.callTool({ name: "recover_javascript_sources", arguments: args, }); expect(response.isError, JSON.stringify(response)).not.toBe(true); expect( ajv.validate(outputSchema, response.structuredContent), JSON.stringify(ajv.errors), ).toBe(true); const parsed = toolContract( "recover_javascript_sources", ).outputSchema.parse(response.structuredContent); const evidence = parseEvidence(parsed); expect(evidence).toMatchObject({ operation: "recover_javascript_sources", confidence: "derived", authority: "shipped-artifact", parameters: { ...args, extraction_mode: "structural", rewrite_level: "standard", }, }); expect(session.evidenceById(evidence.evidence_id)).toEqual(evidence); await assertRecoveryCleanup(fixture.launches); }, );