test: bind MCP validation and cleanup faults to selected inputs

This commit is contained in:
morluto committed 2026-10-26 23:06:36 +08:00
1 parent fef76a02fb
commit 99062feaa7
7 files changed
+53 -26

No files matched your search

+11
View File
@@ -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<Client["callTool"]>[0],
options?: Omit<CallToolRequestOptions, "toolDefinition">,
): ReturnType<Client["callTool"]>;
@@ -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: {},
});
@@ -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: {
@@ -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: {},
});
@@ -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: {},
});
@@ -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();
@@ -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;