diff --git a/src/server/registerByteEntropyTool.ts b/src/server/registerByteEntropyTool.ts index 10458abfc8..ce3618daf1 100644 --- a/src/server/registerByteEntropyTool.ts +++ b/src/server/registerByteEntropyTool.ts @@ -1,12 +1,11 @@ import type { ByteEntropyService } from "../application/binaryDiagnostics/ByteEntropyService.js"; import type { EvidenceMcpServer } from "./EvidenceMcpServer.js"; -import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js"; import type { EvidenceLookup } from "../application/EvidenceInputResolver.js"; import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js"; import { toolContract } from "../contracts/toolContracts.js"; import type { Logger } from "pino"; -import { runEvidenceToolOperation } from "./contractToolHandler.js"; +import { createRetainedSummaryToolHandler } from "./contractToolHandler.js"; import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; import type { WithAdmittedAnalysis } from "./analysisAdmission.js"; @@ -20,28 +19,16 @@ export const registerByteEntropyTool = ( withAdmittedAnalysis: WithAdmittedAnalysis | undefined, ): void => { const contract = toolContract("inspect_byte_entropy"); - const delivery = retainedSummaryDelivery(contract.name, { - recordEvidence, - evidenceById, - }); server.registerTool( contract.name, toolRegistrationOptions(contract), - async ({ detail, ...request }, context) => - runEvidenceToolOperation( - { server, logger, recordEvidence, withAdmittedAnalysis }, - contract, - undefined, - { input: request, context }, - async (input, { signal }) => { - const parameters = { detail }; - const ready = delivery.preflight(parameters); - if (!ready.ok) return ready; - const inspected = await entropyService.inspect(input, { signal }); - return inspected.ok - ? delivery.projectEvidence(parameters, inspected.value) - : inspected; - }, - ), + createRetainedSummaryToolHandler( + server, + contract, + (input, options) => entropyService.inspect(input, options), + logger, + { recordEvidence, evidenceById }, + withAdmittedAnalysis, + ), ); }; diff --git a/tests/boundary/mcp/binaryDiagnosticsMcp.test.ts b/tests/boundary/mcp/binaryDiagnosticsMcp.test.ts index 1e64477190..a09acba305 100644 --- a/tests/boundary/mcp/binaryDiagnosticsMcp.test.ts +++ b/tests/boundary/mcp/binaryDiagnosticsMcp.test.ts @@ -33,48 +33,63 @@ import { parseNativeExports } from "../../../src/native/exports/NativeExportPars import { createNativeExportsService } from "../../../src/composition/binaryDiagnostics.js"; import { registerNativeExportsTool } from "../../../src/server/registerNativeExportsTool.js"; -it("refuses a native exports summary before file admission when retention is unavailable", async () => { - const service = createNativeExportsService(); - const server = new EvidenceMcpServer( - { name: "exports-preflight", version: "1" }, - {}, - undefined, - createToolResultDelivery(undefined), - ); - registerNativeExportsTool(server, service, silentLogger); - const client = new Client({ name: "exports-preflight-client", version: "1" }); - onTestFinished(async () => { - await client.close(); - await server.close(); - await service.close(); - }); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - await server.connect(serverTransport); - await client.connect(clientTransport); - const schemas = await advertisedMcpToolSchemas( - client, - "inspect_native_exports", - ); - expect( - schemas.ajv.validate(schemas.inputSchema, { - path: "/absent/native-library.so", - detail: "summary", - }), - ).toBe(true); - const refused = await client.callTool({ - name: "inspect_native_exports", - arguments: { path: "/absent/native-library.so", detail: "summary" }, - }); - expect(parseMcpToolError(refused).error).toMatchObject({ - code: "capability_unavailable", - }); - const complete = await client.callTool({ - name: "inspect_native_exports", - arguments: { path: "/absent/native-library.so" }, - }); - expect(parseMcpToolError(complete).error.code).toBe("invalid_request"); -}); +it.each(["inspect_native_exports", "inspect_byte_entropy"] as const)( + "refuses %s summary before file admission when retention is unavailable", + async (name) => { + const service = + name === "inspect_native_exports" + ? createNativeExportsService() + : new ByteEntropyService(); + const server = new EvidenceMcpServer( + { name: "exports-preflight", version: "1" }, + {}, + undefined, + createToolResultDelivery(undefined), + ); + if (service instanceof ByteEntropyService) + registerByteEntropyTool( + server, + service, + silentLogger, + undefined, + undefined, + undefined, + ); + else registerNativeExportsTool(server, service, silentLogger); + const client = new Client({ + name: "exports-preflight-client", + version: "1", + }); + onTestFinished(async () => { + await client.close(); + await server.close(); + await service.close(); + }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await server.connect(serverTransport); + await client.connect(clientTransport); + const schemas = await advertisedMcpToolSchemas(client, name); + expect( + schemas.ajv.validate(schemas.inputSchema, { + path: "/absent/native-library.so", + detail: "summary", + }), + ).toBe(true); + const refused = await client.callTool({ + name, + arguments: { path: "/absent/native-library.so", detail: "summary" }, + }); + expect(parseMcpToolError(refused).error).toMatchObject({ + code: "capability_unavailable", + }); + const complete = await client.callTool({ + name, + arguments: { path: "/absent/native-library.so" }, + }); + expect(parseMcpToolError(complete).error.code).toBe("invalid_request"); + }, +); it.each([false, true])( "preserves a completed byte report through MCP cleanup failure and retry (retained=%s)",