From 99062feaa7f48bacf1d53485b4869ecea4333555 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Mon, 26 Oct 2026 23:06:21 +0800 Subject: [PATCH] test: bind MCP validation and cleanup faults to selected inputs --- scripts/lib/artifact-e2e.d.mts | 11 ++++++ .../acceptance/harmonyArtifactJourney.test.ts | 13 +++---- .../acceptance/managedMemberRecovery.test.ts | 11 +++--- tests/acceptance/setup/dshClientSetup.test.ts | 3 +- .../setup/launcherAliasesCli.test.ts | 3 +- .../wasmInterfaceComparison.test.ts | 36 ++++++++++++------- .../kuna/kunaHashOwnerPropagation.test.ts | 2 +- 7 files changed, 53 insertions(+), 26 deletions(-) create mode 100644 scripts/lib/artifact-e2e.d.mts diff --git a/scripts/lib/artifact-e2e.d.mts b/scripts/lib/artifact-e2e.d.mts new file mode 100644 index 0000000000..ee8f8503e9 --- /dev/null +++ b/scripts/lib/artifact-e2e.d.mts @@ -0,0 +1,11 @@ +import type { + Client, + CallToolRequestOptions, +} from "@modelcontextprotocol/client"; + +/** Call with the server's advertised schema; callers cannot replace it. */ +export function artifactMcpCall( + client: Client, + parameters: Parameters[0], + options?: Omit, +): ReturnType; diff --git a/tests/acceptance/harmonyArtifactJourney.test.ts b/tests/acceptance/harmonyArtifactJourney.test.ts index 59fa30b5ff..896e28d712 100644 --- a/tests/acceptance/harmonyArtifactJourney.test.ts +++ b/tests/acceptance/harmonyArtifactJourney.test.ts @@ -1,3 +1,4 @@ +import { artifactMcpCall } from "../../scripts/lib/artifact-e2e.mjs"; import { createHash } from "node:crypto"; import { readFile, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; @@ -105,7 +106,7 @@ test("preserves Harmony archive formats through the public CLI/MCP lifecycle", a root_format: format, root_sha256: sha256, }); - const opened = await client.callTool({ + const opened = await artifactMcpCall(client, { name: "open_binary", arguments: { path }, }); @@ -113,7 +114,7 @@ test("preserves Harmony archive formats through the public CLI/MCP lifecycle", a expect(opened.structuredContent).toMatchObject({ result: { kind: "archive", format, sha256 }, }); - const status = await client.callTool({ + const status = await artifactMcpCall(client, { name: "binary_session", arguments: {}, }); @@ -121,7 +122,7 @@ test("preserves Harmony archive formats through the public CLI/MCP lifecycle", a expect(status.structuredContent).toMatchObject({ result: { open: true, kind: "archive", format, sha256 }, }); - const inspected = await client.callTool({ + const inspected = await artifactMcpCall(client, { name: "inspect_artifact", arguments: {}, }); @@ -133,7 +134,7 @@ test("preserves Harmony archive formats through the public CLI/MCP lifecycle", a ), ).toEqual(cliInventory); - const projected = await client.callTool({ + const projected = await artifactMcpCall(client, { name: "project_harmony_application_graph", arguments: { inventory_evidence: [inventoryEvidence] }, }); @@ -175,12 +176,12 @@ test("preserves Harmony archive formats through the public CLI/MCP lifecycle", a mcp: inventoryEvidence.evidence_id, cli: cliEvidence.evidence_id, }); - const closed = await client.callTool({ + const closed = await artifactMcpCall(client, { name: "close_binary", arguments: {}, }); expect(closed.isError, JSON.stringify(closed)).not.toBe(true); - const empty = await client.callTool({ + const empty = await artifactMcpCall(client, { name: "binary_session", arguments: {}, }); diff --git a/tests/acceptance/managedMemberRecovery.test.ts b/tests/acceptance/managedMemberRecovery.test.ts index 7ad6944577..ab9e3db2ca 100644 --- a/tests/acceptance/managedMemberRecovery.test.ts +++ b/tests/acceptance/managedMemberRecovery.test.ts @@ -1,3 +1,4 @@ +import { artifactMcpCall } from "../../scripts/lib/artifact-e2e.mjs"; import { createHash } from "node:crypto"; import { unlink, writeFile } from "node:fs/promises"; import { join, resolve } from "node:path"; @@ -63,7 +64,7 @@ cliTest( }); expect(output.exitCode, output.stderr).toBe(0); const cliEvidence = parseEvidence(output.json); - const response = await client.callTool({ + const response = await artifactMcpCall(client, { name: "inspect_managed_members", arguments: { path }, }); @@ -96,7 +97,7 @@ cliTest( ], }); expect(selectedCli.exitCode, selectedCli.stderr).toBe(0); - const selected = await client.callTool({ + const selected = await artifactMcpCall(client, { name: "inspect_managed_members", arguments: { path, type_names: [empty!.full_name] }, }); @@ -109,7 +110,7 @@ cliTest( coverage: { state: "complete", issues: [] }, }); - const summaryReply = await client.callTool({ + const summaryReply = await artifactMcpCall(client, { name: "inspect_managed_members", arguments: { path, detail: "summary" }, }); @@ -128,7 +129,7 @@ cliTest( }); await unlink(path); for (const collection of ["types", "methods"] as const) { - const reply = await client.callTool({ + const reply = await artifactMcpCall(client, { name: "inspect_analysis_view", arguments: { source: { @@ -149,7 +150,7 @@ cliTest( collection === "types" ? report.types : [], ); } - const unknown = await client.callTool({ + const unknown = await artifactMcpCall(client, { name: "inspect_analysis_view", arguments: { source: { diff --git a/tests/acceptance/setup/dshClientSetup.test.ts b/tests/acceptance/setup/dshClientSetup.test.ts index b544c5816a..0a44a30b43 100644 --- a/tests/acceptance/setup/dshClientSetup.test.ts +++ b/tests/acceptance/setup/dshClientSetup.test.ts @@ -1,3 +1,4 @@ +import { artifactMcpCall } from "../../../scripts/lib/artifact-e2e.mjs"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { Client } from "@modelcontextprotocol/client"; @@ -272,7 +273,7 @@ async function verifyGeneratedLauncher( expect((await client.listTools()).tools.map(({ name }) => name)).toContain( "binary_session", ); - const session = await client.callTool({ + const session = await artifactMcpCall(client, { name: "binary_session", arguments: {}, }); diff --git a/tests/acceptance/setup/launcherAliasesCli.test.ts b/tests/acceptance/setup/launcherAliasesCli.test.ts index 90d679f095..502a2e88de 100644 --- a/tests/acceptance/setup/launcherAliasesCli.test.ts +++ b/tests/acceptance/setup/launcherAliasesCli.test.ts @@ -1,3 +1,4 @@ +import { artifactMcpCall } from "../../../scripts/lib/artifact-e2e.mjs"; import { mkdir, readFile, symlink, writeFile } from "node:fs/promises"; import { join, resolve } from "node:path"; @@ -113,7 +114,7 @@ cliTest.skipIf(process.platform === "win32")( expect( (await client.listTools()).tools.map(({ name }) => name), ).toContain("binary_session"); - const session = await client.callTool({ + const session = await artifactMcpCall(client, { name: "binary_session", arguments: {}, }); diff --git a/tests/acceptance/wasmInterfaceComparison.test.ts b/tests/acceptance/wasmInterfaceComparison.test.ts index 4fd5eeb2cd..9a12455a58 100644 --- a/tests/acceptance/wasmInterfaceComparison.test.ts +++ b/tests/acceptance/wasmInterfaceComparison.test.ts @@ -103,7 +103,10 @@ cliTest( const ajv = new Ajv2020({ strict: false, validateFormats: false }); expect(ajv.validate(tool.inputSchema, input)).toBe(true); expect(ajv.validate(tool.inputSchema, { left, right })).toBe(false); - const mcp = await client.callTool({ name: tool.name, arguments: input }); + const mcp = await client.callTool( + { name: tool.name, arguments: input }, + { toolDefinition: tool }, + ); expect(mcp.isError, JSON.stringify(mcp)).not.toBe(true); expect( ajv.validate(outputSchema, mcp.structuredContent), @@ -116,22 +119,31 @@ cliTest( left.evidence_id, right.evidence_id, ]); - const retained = await client.callTool({ - name: tool.name, - arguments: { - scope: "both", - left: { kind: "retained-evidence", evidence_id: left.evidence_id }, - right: { kind: "retained-evidence", evidence_id: right.evidence_id }, + const retained = await client.callTool( + { + name: tool.name, + arguments: { + scope: "both", + left: { kind: "retained-evidence", evidence_id: left.evidence_id }, + right: { + kind: "retained-evidence", + evidence_id: right.evidence_id, + }, + }, }, - }); + { toolDefinition: tool }, + ); expect(retained.isError).not.toBe(true); expect(parseEvidence(retained.structuredContent).evidence_id).toBe( mcpEvidence.evidence_id, ); - const invalid = await client.callTool({ - name: tool.name, - arguments: { ...input, scope: "all" }, - }); + const invalid = await client.callTool( + { + name: tool.name, + arguments: { ...input, scope: "all" }, + }, + { toolDefinition: tool }, + ); expect(invalid.isError).toBe(true); } finally { await client.close(); diff --git a/tests/boundary/providers/kuna/kunaHashOwnerPropagation.test.ts b/tests/boundary/providers/kuna/kunaHashOwnerPropagation.test.ts index 28e49e25e0..68ee4c4511 100644 --- a/tests/boundary/providers/kuna/kunaHashOwnerPropagation.test.ts +++ b/tests/boundary/providers/kuna/kunaHashOwnerPropagation.test.ts @@ -860,7 +860,7 @@ it.skipIf(process.platform === "win32").each( }; const client = consumer === "health" ? fixture.open() : undefined; injected.fingerprintReads = 0; - injected.installationPath = fixture.kuna; + injected.installationPath = command; injected.abortOnClose = { stage, abort: () => controller.abort(reason) }; injected.failCloseReadStage = closeRefusal ? stage : undefined; let failure: AnalysisError | undefined;