From 3d4f55652b25856a9ee810b786e19847fa622a29 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:09:42 +0800 Subject: [PATCH] refactor(mcp): consolidate contract and summary policy owners Resolve application and managed workflow contracts through the canonical name-based resolver, removing catalog-position overloads and the obsolete local facade. Use the existing retained-summary policy for eight offline registrations. Pass only validated delivery detail to that owner while preserving exact typed service inputs and validation, admission, logging, and Evidence recording order. Align export-shape MCP parity coverage with the existing absolute-path contract using an absolute dot alias. Preserve exact caller-path parameters, canonical artifact parity, distinct Evidence identity, and the export comparison assertions. --- src/contracts/applicationToolContracts.ts | 42 ------------------- .../compareExportShapes.ts | 4 +- .../compareSourceToBundle.ts | 4 +- .../compareVersions.ts | 4 +- .../registerApplicationTools/coverage.ts | 4 +- .../obligationLedger.ts | 6 +-- .../projectMobileApplicationGraph.ts | 12 +++--- .../registerApplicationTools/traceFeature.ts | 4 +- .../traceSemantics.ts | 4 +- src/server/registerBinaryDiagnosticsTools.ts | 30 +++++++------ src/server/registerByteEntropyTool.ts | 28 ++++++------- src/server/registerDesktopApplicationTool.ts | 21 +++++----- src/server/registerEvmTools.ts | 21 +++++----- .../compareManagedMembers.ts | 4 +- .../registerManagedWorkflowTools/contract.ts | 24 ----------- .../importManagedReconstruction.ts | 6 +-- .../projectManagedApplicationGraph.ts | 6 +-- .../verifyManagedNativeBoundaries.ts | 4 +- src/server/registerNativeExportsTool.ts | 21 +++++----- src/server/registerPcapTools.ts | 21 +++++----- src/server/registerWasmTools.ts | 21 +++++----- src/server/registerWebNetworkCaptureTool.ts | 21 +++++----- .../mcp/applicationExportShapeMcp.test.ts | 22 +++++----- 23 files changed, 126 insertions(+), 208 deletions(-) delete mode 100644 src/server/registerManagedWorkflowTools/contract.ts diff --git a/src/contracts/applicationToolContracts.ts b/src/contracts/applicationToolContracts.ts index d7c08c5d32..3521741fe9 100644 --- a/src/contracts/applicationToolContracts.ts +++ b/src/contracts/applicationToolContracts.ts @@ -319,45 +319,3 @@ export const APPLICATION_TOOL_CONTRACTS = [ ], }, ] as const satisfies readonly ToolContract[]; - -/** Resolve one named application contract without relying on array position. */ -export function applicationToolContract( - name: "trace_application_feature", -): (typeof APPLICATION_TOOL_CONTRACTS)[0]; -export function applicationToolContract( - name: "trace_javascript_semantics", -): (typeof APPLICATION_TOOL_CONTRACTS)[1]; -export function applicationToolContract( - name: "compare_application_versions", -): (typeof APPLICATION_TOOL_CONTRACTS)[2]; -export function applicationToolContract( - name: "compare_source_to_bundle", -): (typeof APPLICATION_TOOL_CONTRACTS)[3]; -export function applicationToolContract( - name: "compare_javascript_export_shapes", -): (typeof APPLICATION_TOOL_CONTRACTS)[4]; -export function applicationToolContract( - name: "build_reconstruction_obligation_ledger", -): (typeof APPLICATION_TOOL_CONTRACTS)[5]; -export function applicationToolContract( - name: "evaluate_reconstruction_coverage", -): (typeof APPLICATION_TOOL_CONTRACTS)[6]; -export function applicationToolContract( - name: "project_android_application_graph", -): (typeof APPLICATION_TOOL_CONTRACTS)[7]; -export function applicationToolContract( - name: "project_apple_application_graph", -): (typeof APPLICATION_TOOL_CONTRACTS)[8]; -export function applicationToolContract( - name: "project_harmony_application_graph", -): (typeof APPLICATION_TOOL_CONTRACTS)[9]; -export function applicationToolContract( - name: (typeof APPLICATION_TOOL_CONTRACTS)[number]["name"], -): (typeof APPLICATION_TOOL_CONTRACTS)[number] { - const contract = APPLICATION_TOOL_CONTRACTS.find( - ({ name: candidate }) => candidate === name, - ); - if (contract === undefined) - throw new Error(`Missing application tool contract: ${name}`); - return contract; -} diff --git a/src/server/registerApplicationTools/compareExportShapes.ts b/src/server/registerApplicationTools/compareExportShapes.ts index 6032d10639..5b73070424 100644 --- a/src/server/registerApplicationTools/compareExportShapes.ts +++ b/src/server/registerApplicationTools/compareExportShapes.ts @@ -3,13 +3,13 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js"; import { recordSessionEvidenceSources } from "../sessionEvidence.js"; import { compareJavaScriptExportShapesEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.js"; -import { applicationToolContract } from "../../contracts/applicationToolContracts.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; import { recordResult } from "./helpers.js"; import type { ApplicationToolRegistration } from "./types.js"; -const contract = applicationToolContract("compare_javascript_export_shapes"); +const contract = toolContract("compare_javascript_export_shapes"); /** Register the execution-free exact JavaScript export-shape comparison. */ export const registerCompareJavaScriptExportShapesTool = ( diff --git a/src/server/registerApplicationTools/compareSourceToBundle.ts b/src/server/registerApplicationTools/compareSourceToBundle.ts index 106fc053af..1bc6c6a647 100644 --- a/src/server/registerApplicationTools/compareSourceToBundle.ts +++ b/src/server/registerApplicationTools/compareSourceToBundle.ts @@ -12,13 +12,13 @@ import { resolveApplicationEvidenceRequest } from "../../application/EvidenceInp import { recordSessionEvidenceSources } from "../sessionEvidence.js"; import { compareSourceToBundleEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.js"; -import { applicationToolContract } from "../../contracts/applicationToolContracts.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; import { recordApplicationResult } from "./helpers.js"; import type { ApplicationToolRegistration } from "./types.js"; -const contract = applicationToolContract("compare_source_to_bundle"); +const contract = toolContract("compare_source_to_bundle"); /** Register conservative historical-source to bundle comparison. */ export const registerCompareSourceToBundleTool = ( diff --git a/src/server/registerApplicationTools/compareVersions.ts b/src/server/registerApplicationTools/compareVersions.ts index 9780a78150..c98acc7b79 100644 --- a/src/server/registerApplicationTools/compareVersions.ts +++ b/src/server/registerApplicationTools/compareVersions.ts @@ -3,13 +3,13 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js"; import { recordSessionEvidenceSources } from "../sessionEvidence.js"; import { compareApplicationVersionsEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.js"; -import { applicationToolContract } from "../../contracts/applicationToolContracts.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; import { recordResult } from "./helpers.js"; import type { ApplicationToolRegistration } from "./types.js"; -const compareContract = applicationToolContract("compare_application_versions"); +const compareContract = toolContract("compare_application_versions"); /** Register the provider-neutral JavaScript version comparison tool. */ export const registerCompareApplicationVersionsTool = ( diff --git a/src/server/registerApplicationTools/coverage.ts b/src/server/registerApplicationTools/coverage.ts index 8957d170f1..53d24c0cf7 100644 --- a/src/server/registerApplicationTools/coverage.ts +++ b/src/server/registerApplicationTools/coverage.ts @@ -2,12 +2,12 @@ import type { EvidenceMcpServer } from "../EvidenceMcpServer.js"; import { runAdmittedToolOperation } from "../admittedToolOperation.js"; import { evaluateReconstructionCoverage } from "../../application/ReconstructionCoverageService.js"; -import { applicationToolContract } from "../../contracts/applicationToolContracts.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import { logToolExecution } from "../toolLogging.js"; import { applicationToolRegistrationOptions } from "../toolRegistrationOptions.js"; import type { ApplicationToolRegistration } from "./types.js"; -const contract = applicationToolContract("evaluate_reconstruction_coverage"); +const contract = toolContract("evaluate_reconstruction_coverage"); /** Register the inline fail-closed reconstruction coverage evaluator. */ export const registerCoverageTools = ( diff --git a/src/server/registerApplicationTools/obligationLedger.ts b/src/server/registerApplicationTools/obligationLedger.ts index 95e0caa995..9bd9aa9bda 100644 --- a/src/server/registerApplicationTools/obligationLedger.ts +++ b/src/server/registerApplicationTools/obligationLedger.ts @@ -5,15 +5,13 @@ import { recordSessionEvidenceSources } from "../sessionEvidence.js"; import { buildResolvedReconstructionObligationLedgerEvidence } from "../../application/ReconstructionObligationLedgerService.js"; import { resolveReconstructionObligationLedgerRequest } from "../../application/ReconstructionObligationLedgerRequest.js"; import { ok } from "../../domain/result.js"; -import { applicationToolContract } from "../../contracts/applicationToolContracts.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; import { recordResult } from "./helpers.js"; import type { ApplicationToolRegistration } from "./types.js"; -const contract = applicationToolContract( - "build_reconstruction_obligation_ledger", -); +const contract = toolContract("build_reconstruction_obligation_ledger"); /** Register conservative reconstruction-obligation generation and closure. */ export const registerReconstructionObligationLedgerTool = ( diff --git a/src/server/registerApplicationTools/projectMobileApplicationGraph.ts b/src/server/registerApplicationTools/projectMobileApplicationGraph.ts index c072556edb..6f8b2a5056 100644 --- a/src/server/registerApplicationTools/projectMobileApplicationGraph.ts +++ b/src/server/registerApplicationTools/projectMobileApplicationGraph.ts @@ -5,10 +5,8 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js"; import { projectAndroidApplicationEvidence } from "../../application/android/AndroidApplicationService.js"; import { projectAppleApplicationEvidence } from "../../application/apple/AppleApplicationService.js"; import { projectHarmonyApplicationEvidence } from "../../application/harmony/HarmonyApplicationService.js"; -import { - APPLICATION_TOOL_CONTRACTS, - applicationToolContract, -} from "../../contracts/applicationToolContracts.js"; +import { APPLICATION_TOOL_CONTRACTS } from "../../contracts/applicationToolContracts.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import type { Result } from "../../domain/result.js"; import type { InventoryProjectionEvidenceResult } from "../../application/InventoryProjectionEvidence.js"; @@ -26,19 +24,19 @@ export const registerProjectMobileApplicationGraphTools = ( registerProjection({ server, options, - contract: applicationToolContract("project_android_application_graph"), + contract: toolContract("project_android_application_graph"), project: projectAndroidApplicationEvidence, }); registerProjection({ server, options, - contract: applicationToolContract("project_apple_application_graph"), + contract: toolContract("project_apple_application_graph"), project: projectAppleApplicationEvidence, }); registerProjection({ server, options, - contract: applicationToolContract("project_harmony_application_graph"), + contract: toolContract("project_harmony_application_graph"), project: projectHarmonyApplicationEvidence, }); }; diff --git a/src/server/registerApplicationTools/traceFeature.ts b/src/server/registerApplicationTools/traceFeature.ts index c774b7a403..5db4253a35 100644 --- a/src/server/registerApplicationTools/traceFeature.ts +++ b/src/server/registerApplicationTools/traceFeature.ts @@ -3,13 +3,13 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js"; import { recordSessionEvidenceSources } from "../sessionEvidence.js"; import { traceApplicationFeatureEvidence } from "../../application/javascript/JavaScriptApplicationWorkflowService.js"; -import { applicationToolContract } from "../../contracts/applicationToolContracts.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; import { recordResult } from "./helpers.js"; import type { ApplicationToolRegistration } from "./types.js"; -const traceContract = applicationToolContract("trace_application_feature"); +const traceContract = toolContract("trace_application_feature"); /** Register the provider-neutral JavaScript feature trace tool. */ export const registerTraceFeatureTool = ( diff --git a/src/server/registerApplicationTools/traceSemantics.ts b/src/server/registerApplicationTools/traceSemantics.ts index 7f907a2723..14eae128a9 100644 --- a/src/server/registerApplicationTools/traceSemantics.ts +++ b/src/server/registerApplicationTools/traceSemantics.ts @@ -4,13 +4,13 @@ import { resolveApplicationEvidenceRequest } from "../../application/EvidenceInp import { recordSessionEvidenceSources } from "../sessionEvidence.js"; import { traceJavaScriptSemanticsEvidenceValidated } from "../../application/javascript/JavaScriptSemanticTraceService.js"; -import { applicationToolContract } from "../../contracts/applicationToolContracts.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; import { recordResult } from "./helpers.js"; import type { ApplicationToolRegistration } from "./types.js"; -const contract = applicationToolContract("trace_javascript_semantics"); +const contract = toolContract("trace_javascript_semantics"); /** Register the bounded JavaScript semantic relation trace tool. */ export const registerTraceJavaScriptSemanticsTool = ( diff --git a/src/server/registerBinaryDiagnosticsTools.ts b/src/server/registerBinaryDiagnosticsTools.ts index ec23b3a59b..586411aa33 100644 --- a/src/server/registerBinaryDiagnosticsTools.ts +++ b/src/server/registerBinaryDiagnosticsTools.ts @@ -1,10 +1,7 @@ import type { ByteEntropyService } from "../application/binaryDiagnostics/ByteEntropyService.js"; import type { EvidenceMcpServer } from "./EvidenceMcpServer.js"; -import { - requireAnalysisViewRetention, - summarizeRetainedAnalysis, -} from "../application/analysisView/AnalysisViewService.js"; +import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js"; import type { BinaryLayoutService } from "../application/binaryDiagnostics/BinaryLayoutService.js"; import type { EvidenceLookup } from "../application/EvidenceInputResolver.js"; import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js"; @@ -38,6 +35,10 @@ export const registerBinaryDiagnosticsTools = ( withAdmittedAnalysis, ); const contract = toolContract("inspect_binary_layout"); + const delivery = retainedSummaryDelivery(new Set([contract.name]), { + recordEvidence, + evidenceById, + }); server.registerTool( contract.name, applicationToolRegistrationOptions(contract), @@ -56,6 +57,7 @@ export const registerBinaryDiagnosticsTools = ( contract, ); const { detail, ...request } = parsed.data; + const parameters = { detail }; return runAdmittedToolOperation( server, withAdmittedAnalysis, @@ -66,21 +68,17 @@ export const registerBinaryDiagnosticsTools = ( logger, contract.name, async () => { - if (detail === "summary") { - const ready = requireAnalysisViewRetention(contract.name, { - recordEvidence, - evidenceById, - }); - if (!ready.ok) return ready; - } + const ready = delivery.preflight(contract.name, parameters); + if (!ready.ok) return ready; const inspected = await service.inspect(request, { signal: context.mcpReq.signal, }); - return detail === "summary" && inspected.ok - ? summarizeRetainedAnalysis(inspected.value, { - recordEvidence, - evidenceById, - }) + return inspected.ok + ? delivery.projectEvidence( + contract.name, + parameters, + inspected.value, + ) : inspected; }, ); diff --git a/src/server/registerByteEntropyTool.ts b/src/server/registerByteEntropyTool.ts index a318136e8e..012aa3d59d 100644 --- a/src/server/registerByteEntropyTool.ts +++ b/src/server/registerByteEntropyTool.ts @@ -1,10 +1,7 @@ import type { ByteEntropyService } from "../application/binaryDiagnostics/ByteEntropyService.js"; import type { EvidenceMcpServer } from "./EvidenceMcpServer.js"; -import { - requireAnalysisViewRetention, - summarizeRetainedAnalysis, -} from "../application/analysisView/AnalysisViewService.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"; @@ -25,7 +22,10 @@ export const registerByteEntropyTool = ( withAdmittedAnalysis: WithAdmittedAnalysis | undefined, ): void => { const contract = toolContract("inspect_byte_entropy"); - const retention = { recordEvidence, evidenceById }; + const delivery = retainedSummaryDelivery(new Set([contract.name]), { + recordEvidence, + evidenceById, + }); server.registerTool( contract.name, toolRegistrationOptions(contract), @@ -40,18 +40,18 @@ export const registerByteEntropyTool = ( logger, contract.name, async () => { - if (detail === "summary") { - const ready = requireAnalysisViewRetention( - contract.name, - retention, - ); - if (!ready.ok) return ready; - } + const parameters = { detail }; + const ready = delivery.preflight(contract.name, parameters); + if (!ready.ok) return ready; const inspected = await entropyService.inspect(request, { signal: context.mcpReq.signal, }); - return detail === "summary" && inspected.ok - ? summarizeRetainedAnalysis(inspected.value, retention) + return inspected.ok + ? delivery.projectEvidence( + contract.name, + parameters, + inspected.value, + ) : inspected; }, ); diff --git a/src/server/registerDesktopApplicationTool.ts b/src/server/registerDesktopApplicationTool.ts index 8edcdecc48..7a8b142005 100644 --- a/src/server/registerDesktopApplicationTool.ts +++ b/src/server/registerDesktopApplicationTool.ts @@ -7,10 +7,7 @@ import type { WithAdmittedAnalysis } from "./analysisAdmission.js"; import { toolContract } from "../contracts/toolContracts.js"; import { createEvidenceToolHandler } from "./contractToolHandler.js"; import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js"; -import { - requireAnalysisViewRetention, - summarizeRetainedAnalysis, -} from "../application/analysisView/AnalysisViewService.js"; +import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err } from "../domain/result.js"; @@ -24,7 +21,10 @@ export const registerDesktopApplicationTool = ( evidenceById: EvidenceLookup | undefined, ): void => { const contract = toolContract("inspect_desktop_application"); - const retention = { recordEvidence, evidenceById }; + const delivery = retainedSummaryDelivery(new Set([contract.name]), { + recordEvidence, + evidenceById, + }); const handler = createEvidenceToolHandler( server, async (raw, options) => { @@ -34,13 +34,12 @@ export const registerDesktopApplicationTool = ( analysisInputErrorFromIssues(contract.name, parsed.error.issues, raw), ); const { detail, ...input } = parsed.data; - if (detail === "summary") { - const ready = requireAnalysisViewRetention(contract.name, retention); - if (!ready.ok) return ready; - } + const parameters = { detail }; + const ready = delivery.preflight(contract.name, parameters); + if (!ready.ok) return ready; const result = await service.inspect(input, options); - return result.ok && detail === "summary" - ? summarizeRetainedAnalysis(result.value, retention) + return result.ok + ? delivery.projectEvidence(contract.name, parameters, result.value) : result; }, logger, diff --git a/src/server/registerEvmTools.ts b/src/server/registerEvmTools.ts index 53a792435f..def8947d33 100644 --- a/src/server/registerEvmTools.ts +++ b/src/server/registerEvmTools.ts @@ -3,10 +3,7 @@ import type { EvidenceMcpServer } from "./EvidenceMcpServer.js"; import type { EvmInterfaceService } from "../application/evm/EvmInterfaceService.js"; import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js"; import type { EvidenceLookup } from "../application/EvidenceInputResolver.js"; -import { - requireAnalysisViewRetention, - summarizeRetainedAnalysis, -} from "../application/analysisView/AnalysisViewService.js"; +import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err } from "../domain/result.js"; import { evmInterfaceModule } from "../composition/evm.js"; @@ -25,6 +22,10 @@ export const registerEvmTools = ( evidenceById?: EvidenceLookup, ): void => { const contract = evmInterfaceModule.contract; + const delivery = retainedSummaryDelivery(new Set([contract.name]), { + recordEvidence, + evidenceById, + }); const handler = createEvidenceToolHandler( server, async (rawInput, options) => { @@ -39,14 +40,12 @@ export const registerEvmTools = ( ), ); const { detail, ...input } = parsed.data; - const retention = { recordEvidence, evidenceById }; - if (detail === "summary") { - const ready = requireAnalysisViewRetention(contract.name, retention); - if (!ready.ok) return ready; - } + const parameters = { detail }; + const ready = delivery.preflight(contract.name, parameters); + if (!ready.ok) return ready; const inspected = await service.inspect(input, options); - return detail === "summary" && inspected.ok - ? summarizeRetainedAnalysis(inspected.value, retention) + return inspected.ok + ? delivery.projectEvidence(contract.name, parameters, inspected.value) : inspected; }, logger, diff --git a/src/server/registerManagedWorkflowTools/compareManagedMembers.ts b/src/server/registerManagedWorkflowTools/compareManagedMembers.ts index 70370cc436..0c3f451ff2 100644 --- a/src/server/registerManagedWorkflowTools/compareManagedMembers.ts +++ b/src/server/registerManagedWorkflowTools/compareManagedMembers.ts @@ -6,10 +6,10 @@ import { recordSessionEvidenceSources } from "../sessionEvidence.js"; import { executeManagedMemberComparison } from "../../application/managed/ManagedMemberComparisonService.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; -import { managedWorkflowContract } from "./contract.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import type { ManagedWorkflowToolRegistration } from "./types.js"; -const compareContract = managedWorkflowContract("compare_managed_members"); +const compareContract = toolContract("compare_managed_members"); /** Register the managed member comparison workflow tool. */ export const registerCompareManagedMembers = ( diff --git a/src/server/registerManagedWorkflowTools/contract.ts b/src/server/registerManagedWorkflowTools/contract.ts deleted file mode 100644 index a82ecbe538..0000000000 --- a/src/server/registerManagedWorkflowTools/contract.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../../contracts/managed/managedWorkflowToolContracts.js"; - -/** Locate one managed workflow tool contract by exact name. */ -export function managedWorkflowContract( - name: "compare_managed_members", -): (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[0]; -export function managedWorkflowContract( - name: "verify_managed_native_boundaries", -): (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[1]; -export function managedWorkflowContract( - name: "import_managed_reconstruction", -): (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[2]; -export function managedWorkflowContract( - name: "project_managed_application_graph", -): (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[3]; -export function managedWorkflowContract( - name: (typeof MANAGED_WORKFLOW_TOOL_CONTRACTS)[number]["name"], -) { - const found = MANAGED_WORKFLOW_TOOL_CONTRACTS.find( - (candidate) => candidate.name === name, - ); - if (found === undefined) throw new Error(`Missing ${name} contract`); - return found; -} diff --git a/src/server/registerManagedWorkflowTools/importManagedReconstruction.ts b/src/server/registerManagedWorkflowTools/importManagedReconstruction.ts index 150277d2ee..cf43482b3a 100644 --- a/src/server/registerManagedWorkflowTools/importManagedReconstruction.ts +++ b/src/server/registerManagedWorkflowTools/importManagedReconstruction.ts @@ -7,12 +7,10 @@ import { } from "../../application/managed/ManagedReconstructionService.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; -import { managedWorkflowContract } from "./contract.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import type { ManagedWorkflowToolRegistration } from "./types.js"; -const reconstructionContract = managedWorkflowContract( - "import_managed_reconstruction", -); +const reconstructionContract = toolContract("import_managed_reconstruction"); /** Register the managed reconstruction import workflow tool. */ export const registerImportManagedReconstruction = ( diff --git a/src/server/registerManagedWorkflowTools/projectManagedApplicationGraph.ts b/src/server/registerManagedWorkflowTools/projectManagedApplicationGraph.ts index a34d3d9bbe..a8690a7b13 100644 --- a/src/server/registerManagedWorkflowTools/projectManagedApplicationGraph.ts +++ b/src/server/registerManagedWorkflowTools/projectManagedApplicationGraph.ts @@ -8,12 +8,10 @@ import type { Evidence } from "../../domain/evidence.js"; import { ok } from "../../domain/result.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; -import { managedWorkflowContract } from "./contract.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import type { ManagedWorkflowToolRegistration } from "./types.js"; -const graphContract = managedWorkflowContract( - "project_managed_application_graph", -); +const graphContract = toolContract("project_managed_application_graph"); /** Register the managed application graph projection workflow tool. */ export const registerProjectManagedApplicationGraph = ( diff --git a/src/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.ts b/src/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.ts index 52f6a9184c..23f6ca7d42 100644 --- a/src/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.ts +++ b/src/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.ts @@ -6,10 +6,10 @@ import { recordSessionEvidenceSources } from "../sessionEvidence.js"; import { executeManagedNativeBoundaries } from "../../application/managed/ManagedNativeVerificationService.js"; import { logToolExecution } from "../toolLogging.js"; import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; -import { managedWorkflowContract } from "./contract.js"; +import { toolContract } from "../../contracts/toolContracts.js"; import type { ManagedWorkflowToolRegistration } from "./types.js"; -const nativeVerificationContract = managedWorkflowContract( +const nativeVerificationContract = toolContract( "verify_managed_native_boundaries", ); diff --git a/src/server/registerNativeExportsTool.ts b/src/server/registerNativeExportsTool.ts index bb5b39fff1..e34e8a6206 100644 --- a/src/server/registerNativeExportsTool.ts +++ b/src/server/registerNativeExportsTool.ts @@ -7,10 +7,7 @@ import { createEvidenceToolHandler } from "./contractToolHandler.js"; import type { WithAdmittedAnalysis } from "./analysisAdmission.js"; import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js"; import type { EvidenceLookup } from "../application/EvidenceInputResolver.js"; -import { - requireAnalysisViewRetention, - summarizeRetainedAnalysis, -} from "../application/analysisView/AnalysisViewService.js"; +import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err } from "../domain/result.js"; @@ -24,6 +21,10 @@ export const registerNativeExportsTool = ( withAdmittedAnalysis?: WithAdmittedAnalysis, ): void => { const contract = toolContract("inspect_native_exports"); + const delivery = retainedSummaryDelivery(new Set([contract.name]), { + recordEvidence, + evidenceById, + }); const handler = createEvidenceToolHandler( server, async (rawInput, options) => { @@ -38,14 +39,12 @@ export const registerNativeExportsTool = ( ), ); const { detail, ...input } = parsed.data; - const retention = { recordEvidence, evidenceById }; - if (detail === "summary") { - const ready = requireAnalysisViewRetention(contract.name, retention); - if (!ready.ok) return ready; - } + const parameters = { detail }; + const ready = delivery.preflight(contract.name, parameters); + if (!ready.ok) return ready; const inspected = await service.inspect(input, options); - return detail === "summary" && inspected.ok - ? summarizeRetainedAnalysis(inspected.value, retention) + return inspected.ok + ? delivery.projectEvidence(contract.name, parameters, inspected.value) : inspected; }, logger, diff --git a/src/server/registerPcapTools.ts b/src/server/registerPcapTools.ts index 22045362c5..90e50f64b3 100644 --- a/src/server/registerPcapTools.ts +++ b/src/server/registerPcapTools.ts @@ -13,10 +13,7 @@ import { applicationToolRegistrationOptions, } from "./toolRegistrationOptions.js"; import type { EvidenceLookup } from "../application/EvidenceInputResolver.js"; -import { - requireAnalysisViewRetention, - summarizeRetainedAnalysis, -} from "../application/analysisView/AnalysisViewService.js"; +import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err } from "../domain/result.js"; @@ -43,6 +40,10 @@ export const registerPcapTools = ( handler(clientContract), ); const contract = toolContract("inspect_pcap_capture"); + const delivery = retainedSummaryDelivery(new Set([contract.name]), { + recordEvidence, + evidenceById, + }); const capture = createEvidenceToolHandler( server, async (rawInput, options) => { @@ -57,14 +58,12 @@ export const registerPcapTools = ( ), ); const { detail, ...input } = parsed.data; - const retention = { recordEvidence, evidenceById }; - if (detail === "summary") { - const ready = requireAnalysisViewRetention(contract.name, retention); - if (!ready.ok) return ready; - } + const parameters = { detail }; + const ready = delivery.preflight(contract.name, parameters); + if (!ready.ok) return ready; const result = await service.execute(contract.name, input, options); - return detail === "summary" && result.ok - ? summarizeRetainedAnalysis(result.value, retention) + return result.ok + ? delivery.projectEvidence(contract.name, parameters, result.value) : result; }, logger, diff --git a/src/server/registerWasmTools.ts b/src/server/registerWasmTools.ts index de4107e9c1..814390c166 100644 --- a/src/server/registerWasmTools.ts +++ b/src/server/registerWasmTools.ts @@ -7,10 +7,7 @@ import type { EvidenceMcpServer } from "./EvidenceMcpServer.js"; import type { WasmArtifactService } from "../application/wasm/WasmArtifactService.js"; import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js"; import type { EvidenceLookup } from "../application/EvidenceInputResolver.js"; -import { - requireAnalysisViewRetention, - summarizeRetainedAnalysis, -} from "../application/analysisView/AnalysisViewService.js"; +import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err } from "../domain/result.js"; import { wasmArtifactModule } from "../composition/wasm.js"; @@ -64,6 +61,10 @@ export const registerWasmTools = ( compareHandler(comparison), ); const contract = wasmArtifactModule.contract; + const delivery = retainedSummaryDelivery(new Set([contract.name]), { + recordEvidence, + evidenceById, + }); const handler = createEvidenceToolHandler( server, async (rawInput, options) => { @@ -78,14 +79,12 @@ export const registerWasmTools = ( ), ); const { detail, ...input } = parsed.data; - const retention = { recordEvidence, evidenceById }; - if (detail === "summary") { - const ready = requireAnalysisViewRetention(contract.name, retention); - if (!ready.ok) return ready; - } + const parameters = { detail }; + const ready = delivery.preflight(contract.name, parameters); + if (!ready.ok) return ready; const inspected = await service.inspect(input, options); - return detail === "summary" && inspected.ok - ? summarizeRetainedAnalysis(inspected.value, retention) + return inspected.ok + ? delivery.projectEvidence(contract.name, parameters, inspected.value) : inspected; }, logger, diff --git a/src/server/registerWebNetworkCaptureTool.ts b/src/server/registerWebNetworkCaptureTool.ts index b3eac73816..83d82d6440 100644 --- a/src/server/registerWebNetworkCaptureTool.ts +++ b/src/server/registerWebNetworkCaptureTool.ts @@ -1,9 +1,6 @@ import type { EvidenceMcpServer } from "./EvidenceMcpServer.js"; import type { EvidenceLookup } from "../application/EvidenceInputResolver.js"; -import { - requireAnalysisViewRetention, - summarizeRetainedAnalysis, -} from "../application/analysisView/AnalysisViewService.js"; +import { retainedSummaryDelivery } from "./retainedSummaryDelivery.js"; import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { redactExplicitFailure } from "../domain/explicitSensitiveFailure.js"; import { captureSensitiveValues } from "../domain/webNetworkCapture.js"; @@ -26,6 +23,10 @@ export const registerWebNetworkCaptureTool = ( evidenceById?: EvidenceLookup, ): void => { const contract = toolContract("inspect_web_network_capture"); + const delivery = retainedSummaryDelivery(new Set([contract.name]), { + recordEvidence, + evidenceById, + }); const registration = applicationToolRegistrationOptions(contract); // Keep the exact advertised contract; the shared service validates raw input // before effects so caller-declared sensitivity also covers invalid arguments. @@ -47,14 +48,12 @@ export const registerWebNetworkCaptureTool = ( ), ); const { detail, ...input } = parsed.data; - const retention = { recordEvidence, evidenceById }; - if (detail === "summary") { - const ready = requireAnalysisViewRetention(contract.name, retention); - if (!ready.ok) return ready; - } + const parameters = { detail }; + const ready = delivery.preflight(contract.name, parameters); + if (!ready.ok) return ready; const inspected = await service.inspect(input, options); - return detail === "summary" && inspected.ok - ? summarizeRetainedAnalysis(inspected.value, retention) + return inspected.ok + ? delivery.projectEvidence(contract.name, parameters, inspected.value) : inspected; }, logger, diff --git a/tests/boundary/mcp/applicationExportShapeMcp.test.ts b/tests/boundary/mcp/applicationExportShapeMcp.test.ts index dbd55510bf..e4f549cf62 100644 --- a/tests/boundary/mcp/applicationExportShapeMcp.test.ts +++ b/tests/boundary/mcp/applicationExportShapeMcp.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, onTestFinished } from "vitest"; import { copyFile, mkdir, rm, writeFile } from "node:fs/promises"; -import { join, relative, resolve } from "node:path"; +import { join, resolve, sep } from "node:path"; import { Ajv2020 } from "ajv/dist/2020.js"; import { z } from "zod"; @@ -166,22 +166,22 @@ describe("application workflow MCP parity", () => { right_export_name: "default", }; try { - const relativePath = relative(process.cwd(), leftRoot); - const relativeApplication = await client.callTool({ + const selectedPath = `${leftRoot}${sep}.`; + const selectedApplication = await client.callTool({ name: "analyze_javascript_application", - arguments: { input_path: relativePath }, + arguments: { input_path: selectedPath }, }); - expect(relativeApplication.isError).not.toBe(true); - const relativeEvidence = parseEvidence( - relativeApplication.structuredContent, + expect(selectedApplication.isError).not.toBe(true); + const selectedEvidence = parseEvidence( + selectedApplication.structuredContent, ); - expect(relativeEvidence.parameters.input_path).toBe(relativePath); + expect(selectedEvidence.parameters.input_path).toBe(selectedPath); expect(left.value.parameters.input_path).toBe(leftRoot); - expect(relativeEvidence.normalized_result).toEqual( + expect(selectedEvidence.normalized_result).toEqual( left.value.normalized_result, ); - expect(relativeEvidence.subject).toEqual(left.value.subject); - expect(relativeEvidence.evidence_id).not.toBe(left.value.evidence_id); + expect(selectedEvidence.subject).toEqual(left.value.subject); + expect(selectedEvidence.evidence_id).not.toBe(left.value.evidence_id); const full = await client.callTool({ name: "compare_javascript_export_shapes", arguments: { left: left.value, right: right.value, ...selectors },