diff --git a/docs/mcp-schema-report.json b/docs/mcp-schema-report.json index dda37b59b..edd6f7449 100644 --- a/docs/mcp-schema-report.json +++ b/docs/mcp-schema-report.json @@ -1,7 +1,7 @@ { "generated_from": "@modelcontextprotocol/server 2.0.0-beta.4 wire registration", - "tool_count": 84, - "tools_list_bytes": 518457, + "tool_count": 88, + "tools_list_bytes": 517143, "budgets": { "tools_list_bytes": 524288, "per_tool_bytes": 65536 @@ -15,15 +15,15 @@ }, { "name": "analyze_function", - "bytes": 10206, + "bytes": 8935, "input_schema_bytes": 2037, - "output_schema_bytes": 7692 + "output_schema_bytes": 6421 }, { "name": "analyze_javascript_application", - "bytes": 19871, + "bytes": 17446, "input_schema_bytes": 2633, - "output_schema_bytes": 16597 + "output_schema_bytes": 14172 }, { "name": "analyze_swift_types", @@ -33,45 +33,45 @@ }, { "name": "analyze_web_bundle", - "bytes": 14995, + "bytes": 14276, "input_schema_bytes": 6550, - "output_schema_bytes": 7940 + "output_schema_bytes": 7221 }, { "name": "batch_decompile", - "bytes": 12102, + "bytes": 10638, "input_schema_bytes": 226, - "output_schema_bytes": 11475 + "output_schema_bytes": 10011 }, { "name": "binary_overview", - "bytes": 1643, + "bytes": 1620, "input_schema_bytes": 250, - "output_schema_bytes": 977 + "output_schema_bytes": 954 }, { "name": "binary_session", - "bytes": 13235, + "bytes": 11822, "input_schema_bytes": 367, - "output_schema_bytes": 12368 + "output_schema_bytes": 10955 }, { "name": "build_call_path", - "bytes": 11300, - "input_schema_bytes": 7493, - "output_schema_bytes": 3335 + "bytes": 10235, + "input_schema_bytes": 6850, + "output_schema_bytes": 2913 }, { "name": "capture_process_scenario", - "bytes": 20991, - "input_schema_bytes": 10463, - "output_schema_bytes": 9774 + "bytes": 19830, + "input_schema_bytes": 10303, + "output_schema_bytes": 8773 }, { "name": "capture_web_screenshot", - "bytes": 5661, + "bytes": 5389, "input_schema_bytes": 771, - "output_schema_bytes": 4454 + "output_schema_bytes": 4182 }, { "name": "close_binary", @@ -87,51 +87,57 @@ }, { "name": "compare_application_versions", - "bytes": 27986, - "input_schema_bytes": 10250, - "output_schema_bytes": 17168 + "bytes": 24888, + "input_schema_bytes": 9296, + "output_schema_bytes": 15024 }, { "name": "compare_artifacts", - "bytes": 4095, + "bytes": 3869, "input_schema_bytes": 759, - "output_schema_bytes": 2851 + "output_schema_bytes": 2625 }, { "name": "compare_bundles", - "bytes": 4559, + "bytes": 4169, "input_schema_bytes": 928, - "output_schema_bytes": 3132 + "output_schema_bytes": 2742 }, { "name": "compare_functions", - "bytes": 4828, + "bytes": 4356, "input_schema_bytes": 725, - "output_schema_bytes": 3611 + "output_schema_bytes": 3139 + }, + { + "name": "compare_managed_members", + "bytes": 7583, + "input_schema_bytes": 960, + "output_schema_bytes": 6099 }, { "name": "compare_process_captures", - "bytes": 3866, + "bytes": 3311, "input_schema_bytes": 565, - "output_schema_bytes": 2716 + "output_schema_bytes": 2161 }, { "name": "compare_web_captures", - "bytes": 33869, - "input_schema_bytes": 31489, - "output_schema_bytes": 1940 + "bytes": 29996, + "input_schema_bytes": 27683, + "output_schema_bytes": 1873 }, { "name": "compare_web_screenshots", - "bytes": 3428, - "input_schema_bytes": 1489, + "bytes": 3351, + "input_schema_bytes": 1412, "output_schema_bytes": 1546 }, { "name": "correlate_static_and_runtime", - "bytes": 13332, - "input_schema_bytes": 8963, - "output_schema_bytes": 3876 + "bytes": 12426, + "input_schema_bytes": 8307, + "output_schema_bytes": 3626 }, { "name": "current_address", @@ -159,27 +165,27 @@ }, { "name": "discover_webmcp_tools", - "bytes": 7256, + "bytes": 6797, "input_schema_bytes": 1087, - "output_schema_bytes": 5765 + "output_schema_bytes": 5306 }, { "name": "export_evidence_bundle", - "bytes": 12103, + "bytes": 11008, "input_schema_bytes": 196, - "output_schema_bytes": 11479 + "output_schema_bytes": 10384 }, { "name": "extract_artifact", - "bytes": 6414, + "bytes": 6212, "input_schema_bytes": 1321, - "output_schema_bytes": 4701 + "output_schema_bytes": 4499 }, { "name": "find_changed_behavior", - "bytes": 12296, - "input_schema_bytes": 8586, - "output_schema_bytes": 3182 + "bytes": 12036, + "input_schema_bytes": 8540, + "output_schema_bytes": 2968 }, { "name": "find_xrefs_to_name", @@ -189,9 +195,9 @@ }, { "name": "get_call_graph", - "bytes": 12061, + "bytes": 10619, "input_schema_bytes": 361, - "output_schema_bytes": 11228 + "output_schema_bytes": 9786 }, { "name": "get_objc_classes", @@ -225,51 +231,63 @@ }, { "name": "inspect_electron_page", - "bytes": 10081, + "bytes": 9514, "input_schema_bytes": 1936, - "output_schema_bytes": 7685 + "output_schema_bytes": 7118 }, { "name": "inspect_macho", - "bytes": 9368, + "bytes": 7672, "input_schema_bytes": 62, - "output_schema_bytes": 8933 + "output_schema_bytes": 7237 }, { "name": "inspect_managed_artifact", - "bytes": 12894, + "bytes": 11489, "input_schema_bytes": 1251, - "output_schema_bytes": 11159 + "output_schema_bytes": 9754 + }, + { + "name": "inspect_managed_members", + "bytes": 13925, + "input_schema_bytes": 2048, + "output_schema_bytes": 11387 + }, + { + "name": "inspect_managed_native_boundaries", + "bytes": 9704, + "input_schema_bytes": 1267, + "output_schema_bytes": 7912 }, { "name": "inspect_plist", - "bytes": 2883, + "bytes": 2769, "input_schema_bytes": 189, - "output_schema_bytes": 2320 + "output_schema_bytes": 2206 }, { "name": "inspect_signature", - "bytes": 2946, + "bytes": 2793, "input_schema_bytes": 62, - "output_schema_bytes": 2519 + "output_schema_bytes": 2366 }, { "name": "inspect_web_page", - "bytes": 23933, + "bytes": 21829, "input_schema_bytes": 5042, - "output_schema_bytes": 18381 + "output_schema_bytes": 16277 }, { "name": "inventory_artifact", - "bytes": 10608, + "bytes": 9791, "input_schema_bytes": 1885, - "output_schema_bytes": 8336 + "output_schema_bytes": 7519 }, { "name": "list_architectures", - "bytes": 2782, + "bytes": 2555, "input_schema_bytes": 62, - "output_schema_bytes": 2387 + "output_schema_bytes": 2160 }, { "name": "list_bookmarks", @@ -279,9 +297,9 @@ }, { "name": "list_browser_targets", - "bytes": 3072, + "bytes": 2950, "input_schema_bytes": 658, - "output_schema_bytes": 1966 + "output_schema_bytes": 1844 }, { "name": "list_documents", @@ -291,39 +309,39 @@ }, { "name": "list_electron_targets", - "bytes": 2991, + "bytes": 2869, "input_schema_bytes": 636, - "output_schema_bytes": 1932 + "output_schema_bytes": 1810 }, { "name": "list_names", - "bytes": 2842, + "bytes": 2786, "input_schema_bytes": 594, - "output_schema_bytes": 1818 + "output_schema_bytes": 1762 }, { "name": "list_procedures", - "bytes": 2739, + "bytes": 2683, "input_schema_bytes": 475, - "output_schema_bytes": 1818 + "output_schema_bytes": 1762 }, { "name": "list_segments", - "bytes": 2817, + "bytes": 2656, "input_schema_bytes": 282, - "output_schema_bytes": 2128 + "output_schema_bytes": 1967 }, { "name": "list_strings", - "bytes": 2837, + "bytes": 2781, "input_schema_bytes": 594, - "output_schema_bytes": 1818 + "output_schema_bytes": 1762 }, { "name": "list_unknowns", - "bytes": 4477, + "bytes": 4279, "input_schema_bytes": 358, - "output_schema_bytes": 3710 + "output_schema_bytes": 3512 }, { "name": "next_address", @@ -333,9 +351,9 @@ }, { "name": "observe_web_session", - "bytes": 6320, + "bytes": 5976, "input_schema_bytes": 765, - "output_schema_bytes": 5090 + "output_schema_bytes": 4746 }, { "name": "open_binary", @@ -343,6 +361,12 @@ "input_schema_bytes": 386, "output_schema_bytes": 696 }, + { + "name": "plan_managed_runtime_correlation", + "bytes": 7556, + "input_schema_bytes": 2128, + "output_schema_bytes": 4768 + }, { "name": "prev_address", "bytes": 1045, @@ -387,21 +411,21 @@ }, { "name": "procedure_references", - "bytes": 4319, + "bytes": 4192, "input_schema_bytes": 798, - "output_schema_bytes": 3054 + "output_schema_bytes": 2927 }, { "name": "reconcile_javascript_runtime", - "bytes": 30201, - "input_schema_bytes": 9566, - "output_schema_bytes": 20110 + "bytes": 27214, + "input_schema_bytes": 8993, + "output_schema_bytes": 17696 }, { "name": "record_unknown", - "bytes": 6725, + "bytes": 6527, "input_schema_bytes": 2646, - "output_schema_bytes": 3685 + "output_schema_bytes": 3487 }, { "name": "resolve_containing_procedure", @@ -411,21 +435,21 @@ }, { "name": "run_controlled_replay", - "bytes": 21072, - "input_schema_bytes": 6098, - "output_schema_bytes": 14387 + "bytes": 19234, + "input_schema_bytes": 5922, + "output_schema_bytes": 12725 }, { "name": "search_procedures", - "bytes": 2238, + "bytes": 2215, "input_schema_bytes": 812, - "output_schema_bytes": 964 + "output_schema_bytes": 941 }, { "name": "search_strings", - "bytes": 2247, + "bytes": 2224, "input_schema_bytes": 812, - "output_schema_bytes": 964 + "output_schema_bytes": 941 }, { "name": "set_address_name", @@ -471,9 +495,9 @@ }, { "name": "trace_application_feature", - "bytes": 27741, - "input_schema_bytes": 9241, - "output_schema_bytes": 17969 + "bytes": 24958, + "input_schema_bytes": 8688, + "output_schema_bytes": 15739 }, { "name": "trace_feature", @@ -489,21 +513,21 @@ }, { "name": "update_unknown", - "bytes": 7884, - "input_schema_bytes": 3810, - "output_schema_bytes": 3685 + "bytes": 7572, + "input_schema_bytes": 3696, + "output_schema_bytes": 3487 }, { "name": "verify_reconstruction", - "bytes": 6692, - "input_schema_bytes": 2688, - "output_schema_bytes": 3510 + "bytes": 6161, + "input_schema_bytes": 2455, + "output_schema_bytes": 3212 }, { "name": "verify_unknown_resolution", - "bytes": 4481, + "bytes": 4283, "input_schema_bytes": 177, - "output_schema_bytes": 3866 + "output_schema_bytes": 3668 }, { "name": "xrefs", diff --git a/src/application/ManagedMemberComparisonService.ts b/src/application/ManagedMemberComparisonService.ts index 5eb2b4242..754bcf5f5 100644 --- a/src/application/ManagedMemberComparisonService.ts +++ b/src/application/ManagedMemberComparisonService.ts @@ -29,15 +29,23 @@ export const compareManagedMembersEvidence = ( const operation = "compare_managed_members"; const parsed = compareManagedMembersInputSchema.safeParse(rawInput); if (!parsed.success) return err(new AnalysisInputError(operation)); + return compareManagedMembersEvidenceValidated(parsed.data); +}; + +/** Compare managed members from input parsed by a trusted adapter. */ +export const compareManagedMembersEvidenceValidated = ( + input: CompareManagedMembersInput, +): Result => { + const operation = "compare_managed_members"; try { - const left = parseManagedMemberEvidence(parsed.data.left); - const right = parseManagedMemberEvidence(parsed.data.right); + const left = parseManagedMemberEvidence(input.left); + const right = parseManagedMemberEvidence(input.right); const result = compareManagedMembers( { evidenceId: left.evidenceId, result: left.result }, { evidenceId: right.evidenceId, result: right.result }, - parsed.data.limits, + input.limits, ); - return ok(createManagedMemberComparisonEvidence(parsed.data, result)); + return ok(createManagedMemberComparisonEvidence(input, result)); } catch (cause: unknown) { return workflowFailure(operation, cause); } diff --git a/src/application/ManagedRuntimeCorrelationService.ts b/src/application/ManagedRuntimeCorrelationService.ts index 5bebb46b6..160b29a6d 100644 --- a/src/application/ManagedRuntimeCorrelationService.ts +++ b/src/application/ManagedRuntimeCorrelationService.ts @@ -42,6 +42,17 @@ export const planManagedRuntimeCorrelationEvidence = async ( const parsed = managedRuntimeCorrelationInputSchema.safeParse(rawInput); if (!parsed.success) return err(new AnalysisInputError(OPERATION, { cause: parsed.error })); + return planManagedRuntimeCorrelationEvidenceValidated( + dependencies, + parsed.data, + ); +}; + +/** Plan runtime correlation from input parsed by a trusted adapter. */ +export const planManagedRuntimeCorrelationEvidenceValidated = async ( + dependencies: ManagedRuntimeCorrelationDependencies, + input: ManagedRuntimeCorrelationInput, +): Promise> => { if (!dependencies.policy.enabled) return err( new AnalysisCapabilityUnavailableError( @@ -61,7 +72,7 @@ export const planManagedRuntimeCorrelationEvidence = async ( let staticEvidence: Evidence; let artifactPath: string; try { - staticEvidence = parseEvidence(parsed.data.static_members); + staticEvidence = parseEvidence(input.static_members); artifactPath = artifactPathFor(staticEvidence); } catch (cause: unknown) { return err(new AnalysisInputError(OPERATION, { cause })); @@ -74,7 +85,7 @@ export const planManagedRuntimeCorrelationEvidence = async ( environment_names: [], network: "none", mount: false, - operation_identity: operationIdentity(parsed.data, staticEvidence), + operation_identity: operationIdentity(input, staticEvidence), }, "read", { restartRequired: true }, @@ -89,7 +100,7 @@ export const planManagedRuntimeCorrelationEvidence = async ( ); try { const result = planManagedRuntimeCorrelation( - parsed.data, + input, dependencies.policy.executablePath, authorized.value.grant_id, ); @@ -106,10 +117,10 @@ export const planManagedRuntimeCorrelationEvidence = async ( operation: OPERATION, parameters: { static_members_evidence_id: staticEvidence.evidence_id, - method: jsonValueSchema.parse(parsed.data.method), - requested_effect: parsed.data.requested_effect, - host: jsonValueSchema.parse(parsed.data.host), - bounds: jsonValueSchema.parse(parsed.data.bounds), + method: jsonValueSchema.parse(input.method), + requested_effect: input.requested_effect, + host: jsonValueSchema.parse(input.host), + bounds: jsonValueSchema.parse(input.bounds), }, result: jsonValueSchema.parse(result), rawResult: null, diff --git a/src/contracts/managedToolContracts.ts b/src/contracts/managedToolContracts.ts index 8b390dd09..35a6da1b2 100644 --- a/src/contracts/managedToolContracts.ts +++ b/src/contracts/managedToolContracts.ts @@ -135,17 +135,12 @@ export const MANAGED_TOOL_CONTRACTS = [ }, { name: "inspect_managed_members", + ...toolContractMetadata("inspect_managed_members"), description: "Inspect bounded PE/CLI metadata members, signatures, method body IL hashes, exception regions, call edges, and field-access anchors without loading or executing target code. Metadata tokens are reported as build-local coordinates bound to the artifact SHA-256 and MVID.", kind: "managed-provider", inputSchema: managedMemberInputSchema, outputSchema: memberOutputSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, examples: [ { title: "Example inspect managed members request", @@ -155,17 +150,12 @@ export const MANAGED_TOOL_CONTRACTS = [ }, { name: "inspect_managed_native_boundaries", + ...toolContractMetadata("inspect_managed_native_boundaries"), description: "Inspect PE/CLI ModuleRef, ImplMap/PInvoke declarations, CLI native-header indicators, and non-IL method implementation flags without loading or executing target code. Results are managed declarations and degraded native-boundary observations, not verified native exports or addresses.", kind: "managed-provider", inputSchema: managedNativeBoundaryInputSchema, outputSchema: nativeBoundaryOutputSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, examples: [ { title: "Example inspect managed/native boundary request", diff --git a/src/contracts/managedWorkflowToolContracts.ts b/src/contracts/managedWorkflowToolContracts.ts index 627b34edc..b689e7b1c 100644 --- a/src/contracts/managedWorkflowToolContracts.ts +++ b/src/contracts/managedWorkflowToolContracts.ts @@ -8,6 +8,36 @@ import { MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE, MANAGED_RUNTIME_CORRELATION_EXAMPLE, } from "./managedWorkflowExamples.js"; +import { toolContractMetadata } from "./toolEffects.js"; + +const managedEvidenceIdSchema = z + .string() + .regex(/^ev_[a-f0-9]{64}$/u) + .describe("Session-owned inspect_managed_members Evidence ID"); + +/** MCP references for comparing two session-owned managed observations. */ +export const compareManagedMembersReferenceInputSchema = z + .strictObject({ + left_evidence_id: managedEvidenceIdSchema, + right_evidence_id: managedEvidenceIdSchema, + limits: compareManagedMembersInputSchema.shape.limits, + unknown_registry_approved: + compareManagedMembersInputSchema.shape.unknown_registry_approved, + }) + .superRefine((input, context) => { + if (input.left_evidence_id === input.right_evidence_id) + context.addIssue({ + code: "custom", + path: ["right_evidence_id"], + message: "Managed member Evidence must be distinct", + }); + }); + +/** MCP reference for planning from one session-owned managed observation. */ +export const managedRuntimeCorrelationReferenceInputSchema = + managedRuntimeCorrelationInputSchema + .omit({ static_members: true }) + .extend({ static_members_evidence_id: managedEvidenceIdSchema }); const comparisonOutputSchema = managedWorkflowOutputSchemas.compare_managed_members; @@ -32,21 +62,21 @@ if (reconstructionOutputSchema === undefined) export const MANAGED_WORKFLOW_TOOL_CONTRACTS = [ { name: "compare_managed_members", + ...toolContractMetadata("compare_managed_members"), description: "Compare two authenticated inspect_managed_members Evidence records using unique-only CIL/signature and structural method-shape tiers. Names are reported as observations but are not used as a matching basis; metadata tokens are remapped as build-local coordinates bound to each artifact SHA-256 and MVID.", kind: "application", - inputSchema: compareManagedMembersInputSchema, + inputSchema: compareManagedMembersReferenceInputSchema, outputSchema: comparisonOutputSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, examples: [ { title: "Compare two managed member observations", - input: MANAGED_MEMBER_COMPARISON_EXAMPLE, + input: { + left_evidence_id: MANAGED_MEMBER_COMPARISON_EXAMPLE.left.evidence_id, + right_evidence_id: + MANAGED_MEMBER_COMPARISON_EXAMPLE.right.evidence_id, + limits: MANAGED_MEMBER_COMPARISON_EXAMPLE.limits, + }, }, ], }, @@ -72,22 +102,26 @@ export const MANAGED_WORKFLOW_TOOL_CONTRACTS = [ }, { name: "plan_managed_runtime_correlation", + ...toolContractMetadata("plan_managed_runtime_correlation"), description: "Prepare a separately authorized managed runtime-correlation admission plan from authenticated inspect_managed_members Evidence. The operation is default-disabled, requires the managed_runtime permission ceiling and grant, locks the exact artifact SHA-256, MVID, method signature, and normalized IL shape, distinguishes attach/load/debugger/reflection/instrumentation effects, and records that no target code was executed.", kind: "application", - inputSchema: managedRuntimeCorrelationInputSchema, + inputSchema: managedRuntimeCorrelationReferenceInputSchema, outputSchema: runtimeOutputSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, examples: [ { title: "Plan an exact-build managed runtime correlation", - input: MANAGED_RUNTIME_CORRELATION_EXAMPLE, + input: { + static_members_evidence_id: + MANAGED_RUNTIME_CORRELATION_EXAMPLE.static_members.evidence_id, + method: MANAGED_RUNTIME_CORRELATION_EXAMPLE.method, + requested_effect: + MANAGED_RUNTIME_CORRELATION_EXAMPLE.requested_effect, + host: MANAGED_RUNTIME_CORRELATION_EXAMPLE.host, + bounds: MANAGED_RUNTIME_CORRELATION_EXAMPLE.bounds, + }, }, ], }, ] as const satisfies readonly ToolContract[]; +import { z } from "zod"; diff --git a/src/contracts/toolEffects.ts b/src/contracts/toolEffects.ts index ba40b7473..9c266a87d 100644 --- a/src/contracts/toolEffects.ts +++ b/src/contracts/toolEffects.ts @@ -95,6 +95,10 @@ export const TOOL_EFFECTS: Readonly> = { inventory_artifact: evidence, extract_artifact: effects({ mutatesSession: true, writesFilesystem: true }), inspect_managed_artifact: evidence, + inspect_managed_members: evidence, + inspect_managed_native_boundaries: evidence, + compare_managed_members: sessionEvidence, + plan_managed_runtime_correlation: sessionEvidence, list_browser_targets: browserEvidence, inspect_web_page: browserEvidence, analyze_web_bundle: browserEvidence, diff --git a/src/server/createServer.ts b/src/server/createServer.ts index 100942c9d..b9bdb496e 100644 --- a/src/server/createServer.ts +++ b/src/server/createServer.ts @@ -183,6 +183,7 @@ export const createServer = ( logger: toolLogger, recordEvidence, recordEvidenceWithUnknown, + session, runtime: { policy: options.managedRuntimePolicy ?? { enabled: false, diff --git a/src/server/registerManagedWorkflowTools.ts b/src/server/registerManagedWorkflowTools.ts index b34709cbf..65a7b1f23 100644 --- a/src/server/registerManagedWorkflowTools.ts +++ b/src/server/registerManagedWorkflowTools.ts @@ -1,24 +1,31 @@ import type { McpServer } from "@modelcontextprotocol/server"; import type { BinarySessionPort } from "../application/BinarySession.js"; -import { compareManagedMembersEvidence } from "../application/ManagedMemberComparisonService.js"; +import { compareManagedMembersEvidenceValidated } from "../application/ManagedMemberComparisonService.js"; import { importManagedReconstructionEvidence } from "../application/ManagedReconstructionService.js"; import { - planManagedRuntimeCorrelationEvidence, + planManagedRuntimeCorrelationEvidenceValidated, type ManagedRuntimeCorrelationDependencies, } from "../application/ManagedRuntimeCorrelationService.js"; -import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../contracts/managedWorkflowToolContracts.js"; import { - compareManagedMembersInputSchema, - managedMemberComparisonResultSchema, -} from "../domain/managedMemberComparison.js"; + compareManagedMembersReferenceInputSchema, + managedRuntimeCorrelationReferenceInputSchema, + MANAGED_WORKFLOW_TOOL_CONTRACTS, +} from "../contracts/managedWorkflowToolContracts.js"; +import { managedMemberComparisonResultSchema } from "../domain/managedMemberComparison.js"; import { managedReconstructionImportInputSchema } from "../domain/managedReconstruction.js"; -import { managedRuntimeCorrelationInputSchema } from "../domain/managedRuntimeCorrelation.js"; import type { Evidence } from "../domain/evidence.js"; +import { + EvidenceIntegrityError, + EvidenceReferenceError, +} from "../domain/errors.js"; +import { err, type Result } from "../domain/result.js"; import type { Logger } from "../logger.js"; import { logToolExecution } from "./toolLogging.js"; import { toCallToolResult } from "./toolResult.js"; import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; +import { safeParseToolInput } from "./toolInputValidation.js"; +import { resolveSessionEvidenceIds } from "./sessionEvidence.js"; interface ManagedWorkflowToolRegistration { readonly logger: Logger; @@ -27,6 +34,7 @@ interface ManagedWorkflowToolRegistration { | BinarySessionPort["recordEvidenceWithUnknown"] | undefined; readonly runtime: ManagedRuntimeCorrelationDependencies; + readonly session: BinarySessionPort | undefined; } /** Register provider-neutral managed-code workflows. */ @@ -41,11 +49,31 @@ export const registerManagedWorkflowTools = ( compareContract.name, toolRegistrationOptions(compareContract), async (input) => { - const parsed = compareManagedMembersInputSchema.parse(input); + const parsedInput = safeParseToolInput( + compareManagedMembersReferenceInputSchema, + input, + compareContract.name, + ); + if (!parsedInput.ok) + return toCallToolResult(parsedInput, compareContract); + const resolved = resolveManagedEvidence(options.session, [ + parsedInput.value.left_evidence_id, + parsedInput.value.right_evidence_id, + ]); + if (!resolved.ok) return toCallToolResult(resolved, compareContract); + const [left, right] = resolved.value; + if (left === undefined || right === undefined) + throw new TypeError("Managed comparison Evidence resolution failed"); + const { + left_evidence_id: _leftEvidenceId, + right_evidence_id: _rightEvidenceId, + ...referencedInput + } = parsedInput.value; + const parsed = { ...referencedInput, left, right }; const result = await logToolExecution( options.logger, compareContract.name, - () => Promise.resolve(compareManagedMembersEvidence(parsed)), + () => Promise.resolve(compareManagedMembersEvidenceValidated(parsed)), ); if (!result.ok) return toCallToolResult(result, compareContract); const recorded = recordSources(options.recordEvidence, [ @@ -117,11 +145,33 @@ export const registerManagedWorkflowTools = ( runtimeContract.name, toolRegistrationOptions(runtimeContract), async (input) => { - const parsed = managedRuntimeCorrelationInputSchema.parse(input); + const parsedInput = safeParseToolInput( + managedRuntimeCorrelationReferenceInputSchema, + input, + runtimeContract.name, + ); + if (!parsedInput.ok) + return toCallToolResult(parsedInput, runtimeContract); + const resolved = resolveManagedEvidence(options.session, [ + parsedInput.value.static_members_evidence_id, + ]); + if (!resolved.ok) return toCallToolResult(resolved, runtimeContract); + const staticMembers = resolved.value[0]; + if (staticMembers === undefined) + throw new TypeError("Managed runtime Evidence resolution failed"); + const { + static_members_evidence_id: _staticMembersEvidenceId, + ...referencedInput + } = parsedInput.value; + const parsed = { ...referencedInput, static_members: staticMembers }; const result = await logToolExecution( options.logger, runtimeContract.name, - () => planManagedRuntimeCorrelationEvidence(options.runtime, parsed), + () => + planManagedRuntimeCorrelationEvidenceValidated( + options.runtime, + parsed, + ), ); if (!result.ok) return toCallToolResult(result, runtimeContract); const recordedSource = options.recordEvidence?.(parsed.static_members); @@ -147,6 +197,26 @@ const contract = ( return found; }; +const resolveManagedEvidence = ( + session: BinarySessionPort | undefined, + evidenceIds: readonly string[], +): Result => { + const firstId = evidenceIds[0] ?? "ev_missing"; + return session === undefined + ? err( + new EvidenceReferenceError( + firstId, + "missing", + "inspect_managed_members", + null, + ), + ) + : resolveSessionEvidenceIds(session, evidenceIds, { + operation: "inspect_managed_members", + predicate: "rea.analysis/v2", + }); +}; + const recordSources = ( recordEvidence: ManagedWorkflowToolRegistration["recordEvidence"], sources: readonly Evidence[], diff --git a/src/server/toolRegistrationOptions.ts b/src/server/toolRegistrationOptions.ts index f5d6616f3..a584a79df 100644 --- a/src/server/toolRegistrationOptions.ts +++ b/src/server/toolRegistrationOptions.ts @@ -127,13 +127,35 @@ const compactJsonSchema = ( const counts = new Map(); collectSchemas(schemaBody, counts, true); const candidates = [...counts.entries()] - .filter(([encoded, count]) => count > 1 && encoded.length >= 128) + .filter(([, count]) => count > 1) .sort( ([left], [right]) => right.length - left.length || left.localeCompare(right), ) - .map(([encoded], index) => [encoded, `rea_${String(index)}`] as const); + .map(([encoded]) => encoded); if (candidates.length === 0) return schemaBody; + let compacted: Record = schemaBody; + let compactedBytes = JSON.stringify(compacted).length; + const selected: Array = []; + for (const encoded of candidates) { + const proposed = [ + ...selected, + [encoded, `rea_${String(selected.length)}`] as const, + ]; + const candidate = compactWithDefinitions(schemaBody, proposed); + const candidateBytes = JSON.stringify(candidate).length; + if (candidateBytes >= compactedBytes) continue; + selected.push([encoded, `rea_${String(selected.length)}`]); + compacted = candidate; + compactedBytes = candidateBytes; + } + return compacted; +}; + +const compactWithDefinitions = ( + schemaBody: Readonly>, + candidates: readonly (readonly [string, string])[], +): Record => { const names = new Map(candidates); const definitions = Object.fromEntries( candidates.map(([encoded, name]) => [ diff --git a/tests/contractSnapshot.test.ts b/tests/contractSnapshot.test.ts index ce2adc529..81a635f8f 100644 --- a/tests/contractSnapshot.test.ts +++ b/tests/contractSnapshot.test.ts @@ -101,6 +101,7 @@ describe("tool contract surface", () => { ...NATIVE_TOOL_CONTRACTS, ...ARTIFACT_TOOL_CONTRACTS, ...MANAGED_TOOL_CONTRACTS, + ...MANAGED_WORKFLOW_TOOL_CONTRACTS, ...BROWSER_TOOL_CONTRACTS, ...ELECTRON_TOOL_CONTRACTS, ...APPLICATION_TOOL_CONTRACTS, diff --git a/tests/managedMcp.test.ts b/tests/managedMcp.test.ts index ef83b8c08..666b53cc6 100644 --- a/tests/managedMcp.test.ts +++ b/tests/managedMcp.test.ts @@ -117,12 +117,13 @@ describe("managed artifact MCP tools", () => { }, }); - const members = structured( + const membersResult = structured( await client.callTool({ name: "inspect_managed_members", arguments: { method_limit: 1 }, }), ); + const members = sessionEvidence(session, membersResult); expect(members).toMatchObject({ operation: "inspect_managed_members", @@ -144,10 +145,7 @@ describe("managed artifact MCP tools", () => { ); expect(boundaries).toMatchObject({ - operation: "inspect_managed_native_boundaries", - provider: { id: "rea-dotnet-static" }, - subject: { local_path: path, format: "pe" }, - normalized_result: { + result: { identity_scope: { token_identity: "build-local" }, pinvoke_imports: { total: 0, limit: 1 }, native_implementations: { total: 0 }, @@ -158,18 +156,19 @@ describe("managed artifact MCP tools", () => { name: "open_binary", arguments: { path: rightPath }, }); - const rightMembers = structured( + const rightMembersResult = structured( await client.callTool({ name: "inspect_managed_members", arguments: { method_limit: 1 }, }), ); - const compared = structured( + const rightMembers = sessionEvidence(session, rightMembersResult); + const comparedResult = structured( await client.callTool({ name: "compare_managed_members", arguments: { - left: members, - right: rightMembers, + left_evidence_id: members.evidence_id, + right_evidence_id: rightMembers.evidence_id, limits: { max_method_matches: 100, max_field_matches: 100, @@ -178,6 +177,7 @@ describe("managed artifact MCP tools", () => { }, }), ); + const compared = sessionEvidence(session, comparedResult); expect(compared).toMatchObject({ operation: "compare_managed_members", @@ -205,7 +205,7 @@ describe("managed artifact MCP tools", () => { .parse(method).items[0]; expect(methodItem).toBeDefined(); if (methodItem === undefined) return; - const imported = structured( + const importedResult = structured( await client.callTool({ name: "import_managed_reconstruction", arguments: { @@ -233,6 +233,7 @@ describe("managed artifact MCP tools", () => { }, }), ); + const imported = sessionEvidence(session, importedResult); expect(imported).toMatchObject({ operation: "import_managed_reconstruction", @@ -249,11 +250,11 @@ describe("managed artifact MCP tools", () => { ], }, }); - const planned = structured( + const plannedResult = structured( await client.callTool({ name: "plan_managed_runtime_correlation", arguments: { - static_members: members, + static_members_evidence_id: members.evidence_id, method: { token: methodItem.token, signature_sha256: methodItem.signature.raw_sha256, @@ -275,6 +276,7 @@ describe("managed artifact MCP tools", () => { }, }), ); + const planned = sessionEvidence(session, plannedResult); expect(planned).toMatchObject({ operation: "plan_managed_runtime_correlation", @@ -302,3 +304,13 @@ const structured = (result: CallToolResult): Record => { throw new Error("missing structured result"); return z.record(z.string(), z.unknown()).parse(result.structuredContent); }; + +const sessionEvidence = ( + session: BinarySession, + result: Readonly>, +): Record => { + const evidenceId = z.string().parse(result.evidence_id); + const evidence = session.evidenceById(evidenceId); + if (evidence === undefined) throw new Error("missing session Evidence"); + return z.record(z.string(), z.unknown()).parse(evidence); +};