fix(mcp): preserve retained evidence on oversized delivery
This commit is contained in:
1 parent
826583dd43
commit
0639a541a3
36 files changed
+339
-184
No files matched your search
+10
-1
@@ -184,7 +184,9 @@ MCP SDK transport tests with recording providers remain integration tests.
|
||||
They are useful for schema drift and failure projection but do not prove that
|
||||
Hopper, Ghidra or another substituted engine works. `verify:package` proves
|
||||
packaging/install behavior and fake-provider integration; use the corresponding
|
||||
real-provider lanes for engine claims. Real Apple dispatch and Interface
|
||||
real-provider lanes for engine claims. Packed-bridge checks verify shipped files
|
||||
and Python syntax without deleted mock fixtures or source-spelling assertions.
|
||||
Real Apple dispatch and Interface
|
||||
Builder verifiers currently prove format integration through production readers.
|
||||
|
||||
`verify:hopper` exercises an installed Hopper through the production stdio MCP
|
||||
@@ -354,6 +356,13 @@ workflow verifies leaf and qualified renames, repeated reuse of fully qualified
|
||||
readback, lookup by the returned name, literal namespace-like leaf names,
|
||||
rejection of empty qualified leaf names without changing comments, CLI
|
||||
behavior, and independent CLI/MCP database ownership.
|
||||
Large-result probes compile initialized host-native data sized from the pinned
|
||||
MCP SDK receive budget. Real byte reads, annotation edits and function dossiers
|
||||
exceed that budget while preserving the connection and active analysis run.
|
||||
Each delivery error must identify its successfully retained Evidence record;
|
||||
export must recover every source byte and complete annotation, with CLI parity
|
||||
and an unchanged executable. The focused formatter check retains only the
|
||||
separate case where recording was not acknowledged.
|
||||
Malformed annotation readback, memory completeness, and inventory data remain separate
|
||||
SDK/provider integration cases; success from a real
|
||||
provider cannot establish rejection of a contradictory provider response.
|
||||
|
||||
@@ -7,10 +7,12 @@ import { setTimeout as delay } from "node:timers/promises";
|
||||
import { promisify } from "node:util";
|
||||
import Ajv from "ajv/dist/2020.js";
|
||||
import addFormats from "ajv-formats";
|
||||
import { analysisErrorProjectionSchema } from "../../dist/contracts/errorSchemas.js";
|
||||
import { mcpTextValue, requireMcpResult } from "./mcp-verifier-results.mjs";
|
||||
import { verifyLegacyGhidraReferenceSnapshot } from "./ghidra-reference-snapshot-e2e.mjs";
|
||||
import { verifyGhidraSnapshotLifecycle } from "./real-ghidra-snapshot-lifecycle.mjs";
|
||||
import { verifyGhidraTargetAdmission } from "./real-ghidra-target-admission.mjs";
|
||||
import { verifyGhidraLargeResults } from "./real-ghidra-large-results.mjs";
|
||||
import { verifyGhidraNamespaceAnnotations } from "./real-ghidra-namespace-annotations.mjs";
|
||||
|
||||
/** Probe real Ghidra location, annotation and error contracts through public adapters. */
|
||||
@@ -53,6 +55,15 @@ export async function verifyGhidraBoundaries(
|
||||
true,
|
||||
`${name} accepted ${JSON.stringify(args)}`,
|
||||
);
|
||||
// SDK input-schema rejections are text-only; application errors use the
|
||||
// canonical structured error projection, separately from success schemas.
|
||||
if (reply.structuredContent !== undefined) {
|
||||
analysisErrorProjectionSchema.parse(reply.structuredContent.error);
|
||||
assert.deepEqual(
|
||||
reply.structuredContent,
|
||||
JSON.parse(mcpTextValue(reply)),
|
||||
);
|
||||
}
|
||||
if (diagnostic !== undefined) {
|
||||
const error = reply.structuredContent?.error;
|
||||
assert.equal(error?.code, "invalid_request");
|
||||
@@ -664,6 +675,13 @@ export async function verifyGhidraBoundaries(
|
||||
entrypoint,
|
||||
env,
|
||||
});
|
||||
await verifyGhidraLargeResults({
|
||||
call,
|
||||
reject: invalid,
|
||||
target,
|
||||
entrypoint,
|
||||
env,
|
||||
});
|
||||
await assert.rejects(access(socketRoot), { code: "ENOENT" });
|
||||
await assert.rejects(access(runtimeRoot), { code: "ENOENT" });
|
||||
assert.equal(await call("procedure_address", { procedure: name }), address);
|
||||
@@ -705,6 +723,7 @@ export async function verifyGhidraBoundaries(
|
||||
imported_source_identity_retained: true,
|
||||
equivalent_instruction_address_spellings: true,
|
||||
qualified_annotation_name_roundtrip: true,
|
||||
oversized_result_retention_and_complete_export: true,
|
||||
source_immutable: true,
|
||||
reopen_discards_edits: true,
|
||||
long_tmpdir_private_socket_cleanup: true,
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { STDIO_DEFAULT_MAX_BUFFER_SIZE } from "@modelcontextprotocol/server";
|
||||
|
||||
/** Exercise complete oversized results and recovery through real CLI/MCP adapters. */
|
||||
export async function verifyGhidraLargeResults({
|
||||
call,
|
||||
reject,
|
||||
target,
|
||||
entrypoint,
|
||||
env,
|
||||
}) {
|
||||
const exec = promisify(execFile);
|
||||
const compiler = process.env.REA_CC ?? "cc";
|
||||
try {
|
||||
await exec(compiler, ["--version"], { env, timeout: 30000 });
|
||||
} catch (cause) {
|
||||
throw new Error(
|
||||
`Ghidra large-result verification requires a working host C compiler '${compiler}'.`,
|
||||
{ cause },
|
||||
);
|
||||
}
|
||||
// Hex encoding, Evidence projection and the two MCP representations multiply
|
||||
// the source size. Derive the fixture from the pinned SDK's receive budget.
|
||||
const length = Math.ceil(STDIO_DEFAULT_MAX_BUFFER_SIZE / 4);
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-ghidra-large-results-"));
|
||||
const path = join(workspace, "large-results");
|
||||
const source = fileURLToPath(
|
||||
new URL("../../tests/conformance/ghidra/large-results.c", import.meta.url),
|
||||
);
|
||||
try {
|
||||
await exec(
|
||||
compiler,
|
||||
["-O0", `-DREA_PAYLOAD_BYTES=${length}`, source, "-o", path],
|
||||
{ env, timeout: 30000 },
|
||||
);
|
||||
const bytes = await readFile(path);
|
||||
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
||||
await call("close_binary");
|
||||
await call("open_binary", { path, provider_id: "ghidra" });
|
||||
const names = await call("list_names");
|
||||
const payload = names.find(({ value }) =>
|
||||
/(?:^|_)rea_large_payload$/u.test(value),
|
||||
);
|
||||
const entry = names.find(({ value }) =>
|
||||
/(?:^|_)rea_large_entry$/u.test(value),
|
||||
);
|
||||
assert.ok(payload);
|
||||
assert.ok(entry);
|
||||
const mapping = await call("address_to_file_offset", {
|
||||
address: payload.address,
|
||||
});
|
||||
const expected = bytes.subarray(
|
||||
mapping.file_offset,
|
||||
mapping.file_offset + length,
|
||||
);
|
||||
assert.equal(expected.length, length);
|
||||
assert.equal(expected[0], 0x5a);
|
||||
assert.equal(expected.at(-1), 0xa5);
|
||||
const before = await call("binary_session");
|
||||
const readError = await reject("read_bytes", {
|
||||
address: payload.address,
|
||||
length,
|
||||
});
|
||||
const readId = retainedReference(readError, "read_bytes");
|
||||
const comment =
|
||||
"REA_BIG_COMMENT:" +
|
||||
"x".repeat(Math.ceil(STDIO_DEFAULT_MAX_BUFFER_SIZE / 10));
|
||||
const annotationError = await reject("annotate_native_function", {
|
||||
procedure: entry.address,
|
||||
name: "rea_frame_probe",
|
||||
comment,
|
||||
inline_comment: comment,
|
||||
});
|
||||
const annotationId = retainedReference(
|
||||
annotationError,
|
||||
"annotate_native_function",
|
||||
);
|
||||
assert.equal(
|
||||
await call("procedure_address", { procedure: "rea_frame_probe" }),
|
||||
entry.address,
|
||||
"A delivery constraint must preserve the successfully committed edit",
|
||||
);
|
||||
const dossierError = await reject("analyze_function", {
|
||||
procedure: "rea_frame_probe",
|
||||
});
|
||||
const dossierId = retainedReference(dossierError, "analyze_function");
|
||||
const after = await call("binary_session");
|
||||
assert.equal(after.analysis_run.run_id, before.analysis_run.run_id);
|
||||
assert.equal(after.sha256, sha256);
|
||||
assert.equal(
|
||||
(await call("read_bytes", { address: payload.address, length: 1 }))
|
||||
.bytes_hex,
|
||||
"5a",
|
||||
);
|
||||
const lastAddress = `0x${(BigInt(payload.address) + BigInt(length - 1)).toString(16)}`;
|
||||
assert.equal(
|
||||
(await call("read_bytes", { address: lastAddress, length: 1 })).bytes_hex,
|
||||
"a5",
|
||||
);
|
||||
const bundlePath = join(workspace, "bundle.json");
|
||||
await call("export_evidence_bundle", { path: bundlePath });
|
||||
const bundle = JSON.parse(await readFile(bundlePath, "utf8"));
|
||||
const retained = (id) => {
|
||||
const record = bundle.records.find(
|
||||
({ evidence_id }) => evidence_id === id,
|
||||
);
|
||||
assert.ok(record, `Missing exported retained record ${id}`);
|
||||
assert.equal(record.provider.id, "ghidra");
|
||||
assert.equal(record.subject.digest.sha256, sha256);
|
||||
return record;
|
||||
};
|
||||
const readRecord = retained(readId);
|
||||
const read = readRecord.normalized_result;
|
||||
assert.equal(read.requested_bytes, length);
|
||||
assert.equal(read.returned_bytes, length);
|
||||
assert.equal(read.complete, true);
|
||||
assert.equal(read.bytes_hex, expected.toString("hex"));
|
||||
assert.equal(readRecord.raw_result.bytes_hex, read.bytes_hex);
|
||||
const annotationRecord = retained(annotationId);
|
||||
const annotation = annotationRecord.normalized_result;
|
||||
assert.equal(annotation.annotations.name, "rea_frame_probe");
|
||||
assert.equal(annotation.annotations.comment, comment);
|
||||
assert.equal(annotation.annotations.inline_comment, comment);
|
||||
assert.equal(annotationRecord.raw_result.annotations.comment, comment);
|
||||
assert.equal(
|
||||
annotationRecord.raw_result.annotations.inline_comment,
|
||||
comment,
|
||||
);
|
||||
const dossierRecord = retained(dossierId);
|
||||
const dossier = dossierRecord.normalized_result;
|
||||
assert.equal(dossier.procedure.name, "rea_frame_probe");
|
||||
assert.ok(dossier.comments.some(({ text }) => text === comment));
|
||||
assert.ok(
|
||||
dossierRecord.raw_result.comments.some(({ text }) => text === comment),
|
||||
);
|
||||
const { stdout } = await exec(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"read-bytes",
|
||||
path,
|
||||
payload.address,
|
||||
"--length",
|
||||
String(length),
|
||||
"--provider",
|
||||
"ghidra",
|
||||
"--json",
|
||||
],
|
||||
{ env, timeout: 240000, maxBuffer: 72 * 1024 * 1024 },
|
||||
);
|
||||
assert.deepEqual(JSON.parse(stdout).normalized_result, read);
|
||||
assert.deepEqual(await readFile(path), bytes);
|
||||
await call("close_binary");
|
||||
await call("open_binary", { path: target.path, provider_id: "ghidra" });
|
||||
} finally {
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function retainedReference(error, operation) {
|
||||
assert.equal(error.code, "resource_constraint");
|
||||
assert.equal(error.details.operation, operation);
|
||||
assert.equal(error.details.resource, "transport");
|
||||
const limits = error.details.reported_limits;
|
||||
assert.equal(limits.boundary, "mcp-response");
|
||||
assert.equal(limits.constraint, "receive-buffer");
|
||||
assert.ok(limits.response_bytes_at_least > limits.result_budget_bytes);
|
||||
assert.equal(limits.evidence_reference.kind, "retained-evidence");
|
||||
assert.match(limits.evidence_reference.evidence_id, /^ev_[a-f0-9]{64}$/u);
|
||||
return limits.evidence_reference.evidence_id;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export const analysisErrorRemediationAction = (
|
||||
return (
|
||||
error.remediationAction ??
|
||||
(error.resource === "transport"
|
||||
? "Use the reported Evidence reference with a focused analysis tool when retained in this session, or export the session through export_evidence_bundle to a caller-selected path. Complete CLI JSON output is also available. The connection remains usable."
|
||||
? "Export retained session evidence through export_evidence_bundle to a caller-selected path, or use complete CLI JSON output. Tools that accept retained-evidence references can inspect compatible records. The connection remains usable."
|
||||
: error.resource === "cpu"
|
||||
? "Review the reported worker CPU limits and observed signal. Retry with sufficient CPU time or a smaller artifact; REA retains tighter inherited limits."
|
||||
: error.resource === "file-size"
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { AndroidOperation } from "../domain/android/androidAnalysis.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind Android handlers to their exact named contracts through shared workflows. */
|
||||
export const registerAndroidTools = (
|
||||
@@ -24,9 +24,7 @@ export const registerAndroidTools = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
};
|
||||
};
|
||||
const packageContract = toolContract("inspect_android_package");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { APPLICATION_TOOL_CONTRACTS } from "../../contracts/applicationToolContracts.js";
|
||||
import type { Evidence } from "../../domain/evidence.js";
|
||||
import { toCallToolResult } from "../toolResult.js";
|
||||
import { toEvidenceToolResult } from "../toolResult.js";
|
||||
import type { ApplicationToolRegistration } from "./types.js";
|
||||
|
||||
export const recordResult = (
|
||||
@@ -19,15 +19,7 @@ export const recordResult = (
|
||||
evidence,
|
||||
unknownRegistration(evidence, unknownKind),
|
||||
);
|
||||
if (recorded !== undefined && !recorded.ok)
|
||||
return toCallToolResult(recorded, contract);
|
||||
return toCallToolResult(
|
||||
{ ok: true, value: evidence },
|
||||
contract,
|
||||
recorded === undefined
|
||||
? undefined
|
||||
: { retainedEvidenceId: evidence.evidence_id },
|
||||
);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
};
|
||||
|
||||
const unknownRegistration = (
|
||||
|
||||
@@ -10,7 +10,7 @@ import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
|
||||
import { runDerivedOperation } from "./runDerivedOperation.js";
|
||||
import { ARTIFACT_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Register Evidence-backed deterministic artifact comparison. */
|
||||
export const registerArtifactComparisonTool = (
|
||||
@@ -50,13 +50,14 @@ export const registerArtifactComparisonTool = (
|
||||
limitations: comparison.limitations,
|
||||
evidenceLinks: [...leftEvidenceIds, ...rightEvidenceIds],
|
||||
});
|
||||
return toCallToolResult(
|
||||
return toEvidenceToolResult(
|
||||
evidence,
|
||||
contract,
|
||||
recordDerivedEvidence(
|
||||
session,
|
||||
evidence,
|
||||
artifactUnknownInput(left, right, comparison.status),
|
||||
),
|
||||
contract,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind offline layout inspection to its named contract and session Evidence owner. */
|
||||
export const registerBinaryDiagnosticsTools = (
|
||||
@@ -24,9 +24,7 @@ export const registerBinaryDiagnosticsTools = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,7 @@ import { browserScenarioSchema } from "../domain/browserScenario.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
interface BrowserScenarioToolRegistration {
|
||||
readonly logger: Logger;
|
||||
@@ -45,9 +45,7 @@ export const registerBrowserScenarioTool = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = options.recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult({ ok: true, value: result.value }, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -28,7 +28,7 @@ import type { Logger } from "../logger.js";
|
||||
import { mcpProgressReporter } from "./mcpProgress.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
interface BrowserToolRegistration {
|
||||
readonly logger: Logger;
|
||||
@@ -206,13 +206,5 @@ const evidenceResult = (
|
||||
evidence: Evidence,
|
||||
) => {
|
||||
const recorded = options.recordEvidence?.(evidence);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(
|
||||
{ ok: true, value: evidence },
|
||||
contract,
|
||||
recorded === undefined
|
||||
? undefined
|
||||
: { retainedEvidenceId: evidence.evidence_id },
|
||||
);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
};
|
||||
@@ -6,11 +6,10 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import { compareBundles } from "../domain/bundleComparison.js";
|
||||
import { createEvidence } from "../domain/evidence.js";
|
||||
import { jsonValueSchema } from "../domain/jsonValue.js";
|
||||
import { ok } from "../domain/result.js";
|
||||
import { runDerivedOperation } from "./runDerivedOperation.js";
|
||||
import { BUNDLE_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Register canonical Evidence bundle comparison. */
|
||||
export const registerBundleComparisonTool = (
|
||||
@@ -47,7 +46,7 @@ export const registerBundleComparisonTool = (
|
||||
limitations: comparison.limitations,
|
||||
});
|
||||
const recorded = session.recordEvidence(evidence);
|
||||
return toCallToolResult(recorded.ok ? ok(evidence) : recorded, contract);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -25,7 +25,7 @@ import type { Logger } from "../logger.js";
|
||||
import { mcpProgressReporter } from "./mcpProgress.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
interface ElectronToolRegistration {
|
||||
readonly logger: Logger;
|
||||
@@ -166,13 +166,5 @@ const evidenceResult = (
|
||||
evidence: Evidence,
|
||||
) => {
|
||||
const recorded = options.recordEvidence?.(evidence);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(
|
||||
{ ok: true, value: evidence },
|
||||
contract,
|
||||
recorded === undefined
|
||||
? undefined
|
||||
: { retainedEvidenceId: evidence.evidence_id },
|
||||
);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
};
|
||||
@@ -27,7 +27,7 @@ import type { Logger } from "../logger.js";
|
||||
import { mcpProgressReporter } from "./mcpProgress.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
import { executeFunctionAnalysisEvidence } from "../application/FunctionAnalysisEvidence.js";
|
||||
|
||||
/** Optional session services used by enhanced tool registration. */
|
||||
@@ -271,7 +271,7 @@ const executeEnhancedTool = async (
|
||||
recordUnknown: registration.recordUnknown,
|
||||
});
|
||||
if (!unknowns.ok) return toCallToolResult(unknowns, contract);
|
||||
return toCallToolResult({ ok: true, value: evidence }, contract);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
}
|
||||
return toCallToolResult(result, contract);
|
||||
};
|
||||
@@ -308,12 +308,12 @@ const executeFunctionTool = async (
|
||||
message: result.ok ? "completed" : "failed",
|
||||
terminal: true,
|
||||
});
|
||||
if (result.ok) {
|
||||
const recorded = registration.recordEvidence?.(result.value);
|
||||
if (recorded !== undefined && !recorded.ok)
|
||||
return toCallToolResult(recorded, contract);
|
||||
}
|
||||
return toCallToolResult(result, contract);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(
|
||||
result.value,
|
||||
contract,
|
||||
registration.recordEvidence?.(result.value),
|
||||
);
|
||||
};
|
||||
|
||||
const jsonParameters = (
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { Logger } from "../logger.js";
|
||||
import { mcpProgressReporter } from "./mcpProgress.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
import { createArtifactExtractionDestination } from "../application/artifacts/ArtifactExtractionDestination.js";
|
||||
|
||||
interface EvidenceToolRegistration {
|
||||
@@ -94,9 +94,7 @@ export const registerEvidenceTools = (
|
||||
return toCallToolResult(sourceRecorded, contract);
|
||||
}
|
||||
const recorded = options.recordEvidence?.(evidence);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult({ ok: true, value: evidence }, contract);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind the named EVM contract to shared validation and session Evidence recording. */
|
||||
export const registerEvmTools = (
|
||||
@@ -24,9 +24,7 @@ export const registerEvmTools = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import type { FirmwareOperation } from "../domain/firmware/firmwareAnalysis.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind firmware handlers to their exact named schemas. */
|
||||
export const registerFirmwareTools = (
|
||||
@@ -26,9 +26,7 @@ export const registerFirmwareTools = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
};
|
||||
const inspect = toolContract("inspect_firmware_regions");
|
||||
server.registerTool(
|
||||
|
||||
@@ -17,7 +17,7 @@ import { recordSessionEvidenceSources } from "./sessionEvidence.js";
|
||||
import { runDerivedOperation } from "./runDerivedOperation.js";
|
||||
import { FUNCTION_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Register explicit Evidence-backed function comparison. */
|
||||
export const registerFunctionComparisonTool = (
|
||||
@@ -89,7 +89,7 @@ export const registerFunctionComparisonTool = (
|
||||
rightIds,
|
||||
}),
|
||||
);
|
||||
return toCallToolResult(recorded, contract);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
STATIC_RUNTIME_PROVIDER,
|
||||
} from "./sessionToolPolicies.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Register Evidence-composed differential investigation workflows. */
|
||||
export const registerInvestigationTools = (
|
||||
@@ -101,7 +101,7 @@ const registerChangedBehavior = (
|
||||
],
|
||||
},
|
||||
);
|
||||
return toCallToolResult(recorded, contract);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -158,7 +158,7 @@ const registerCallPath = (
|
||||
],
|
||||
},
|
||||
);
|
||||
return toCallToolResult(recorded, contract);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -198,7 +198,9 @@ const registerStaticRuntime = (
|
||||
limitations: result.limitations,
|
||||
evidenceLinks: links,
|
||||
});
|
||||
return toCallToolResult(
|
||||
return toEvidenceToolResult(
|
||||
evidence,
|
||||
contract,
|
||||
recordWorkflowEvidence(
|
||||
session,
|
||||
evidence,
|
||||
@@ -218,7 +220,6 @@ const registerStaticRuntime = (
|
||||
],
|
||||
},
|
||||
),
|
||||
contract,
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -255,7 +256,9 @@ const registerReconstruction = (
|
||||
limitations: result.limitations,
|
||||
evidenceLinks: links,
|
||||
});
|
||||
return toCallToolResult(
|
||||
return toEvidenceToolResult(
|
||||
evidence,
|
||||
contract,
|
||||
recordWorkflowEvidence(session, evidence, result.status === "unknown", {
|
||||
question: "Does the reconstruction satisfy every declared claim?",
|
||||
domain: "reconstruction-verification",
|
||||
@@ -266,7 +269,6 @@ const registerReconstruction = (
|
||||
rationale,
|
||||
})),
|
||||
}),
|
||||
contract,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind optional recovery to its exact named contract without acquiring an engine. */
|
||||
export const registerJavaScriptRecoveryTool = (
|
||||
@@ -24,9 +24,7 @@ export const registerJavaScriptRecoveryTool = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -19,7 +19,7 @@ import { observeJavaScriptRuntimeInputSchema } from "../domain/javascript/javasc
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
interface RuntimeToolRegistration {
|
||||
readonly logger: Logger;
|
||||
@@ -83,7 +83,5 @@ const runRuntimeTool = async <Input>(
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = options.recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult({ ok: true, value: result.value }, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { compareManagedMembersEvidenceValidated } from "../../application/manage
|
||||
import { managedMemberComparisonResultSchema } from "../../domain/managed/managedMemberComparison.js";
|
||||
import { logToolExecution } from "../toolLogging.js";
|
||||
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "../toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "../toolResult.js";
|
||||
import { managedWorkflowContract } from "./contract.js";
|
||||
import { resolveManagedEvidence } from "./evidence.js";
|
||||
import type { ManagedWorkflowToolRegistration } from "./types.js";
|
||||
@@ -68,12 +68,7 @@ export const registerCompareManagedMembers = (
|
||||
relationships: [],
|
||||
})
|
||||
: options.recordEvidence?.(result.value);
|
||||
if (output !== undefined && !output.ok)
|
||||
return toCallToolResult(output, compareContract);
|
||||
return toCallToolResult(
|
||||
{ ok: true, value: result.value },
|
||||
compareContract,
|
||||
);
|
||||
return toEvidenceToolResult(result.value, compareContract, output);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import type { McpServer } from "@modelcontextprotocol/server";
|
||||
import { importManagedReconstructionEvidenceValidated } from "../../application/managed/ManagedReconstructionService.js";
|
||||
import { logToolExecution } from "../toolLogging.js";
|
||||
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "../toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "../toolResult.js";
|
||||
import { managedWorkflowContract } from "./contract.js";
|
||||
import { resolveManagedEvidence } from "./evidence.js";
|
||||
import type { ManagedWorkflowToolRegistration } from "./types.js";
|
||||
@@ -41,9 +41,11 @@ export const registerImportManagedReconstruction = (
|
||||
if (recordedSource !== undefined && !recordedSource.ok)
|
||||
return toCallToolResult(recordedSource, reconstructionContract);
|
||||
const recorded = options.recordEvidence?.(result.value);
|
||||
if (recorded !== undefined && !recorded.ok)
|
||||
return toCallToolResult(recorded, reconstructionContract);
|
||||
return toCallToolResult(result, reconstructionContract);
|
||||
return toEvidenceToolResult(
|
||||
result.value,
|
||||
reconstructionContract,
|
||||
recorded,
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import type { McpServer } from "@modelcontextprotocol/server";
|
||||
import { projectManagedApplicationGraphEvidence } from "../../application/managed/ManagedApplicationGraphService.js";
|
||||
import { logToolExecution } from "../toolLogging.js";
|
||||
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "../toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "../toolResult.js";
|
||||
import { managedWorkflowContract } from "./contract.js";
|
||||
import {
|
||||
resolveManagedArtifactEvidence,
|
||||
@@ -63,9 +63,7 @@ export const registerProjectManagedApplicationGraph = (
|
||||
if (!recordedSources.ok)
|
||||
return toCallToolResult(recordedSources, graphContract);
|
||||
const recorded = options.recordEvidence?.(result.value);
|
||||
if (recorded !== undefined && !recorded.ok)
|
||||
return toCallToolResult(recorded, graphContract);
|
||||
return toCallToolResult(result, graphContract);
|
||||
return toEvidenceToolResult(result.value, graphContract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { verifyManagedNativeBoundariesEvidence } from "../../application/managed
|
||||
import { managedNativeVerificationResultSchema } from "../../domain/managed/managedNativeVerification.js";
|
||||
import { logToolExecution } from "../toolLogging.js";
|
||||
import { toolRegistrationOptions } from "../toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "../toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "../toolResult.js";
|
||||
import { managedWorkflowContract } from "./contract.js";
|
||||
import {
|
||||
resolveManagedBoundaryEvidence,
|
||||
@@ -90,9 +90,11 @@ export const registerVerifyManagedNativeBoundaries = (
|
||||
relationships: [],
|
||||
})
|
||||
: options.recordEvidence?.(result.value);
|
||||
if (output !== undefined && !output.ok)
|
||||
return toCallToolResult(output, nativeVerificationContract);
|
||||
return toCallToolResult(result, nativeVerificationContract);
|
||||
return toEvidenceToolResult(
|
||||
result.value,
|
||||
nativeVerificationContract,
|
||||
output,
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -22,7 +22,7 @@ import type { Logger } from "../logger.js";
|
||||
import { mcpProgressReporter } from "./mcpProgress.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Optional session services used by direct tool registration. */
|
||||
export interface OfficialToolRegistration {
|
||||
@@ -91,9 +91,7 @@ const registerOfficialTool = (
|
||||
},
|
||||
);
|
||||
const recorded = registration.recordEvidence?.(evidence);
|
||||
if (recorded !== undefined && !recorded.ok)
|
||||
return toCallToolResult(recorded, contract);
|
||||
return toCallToolResult({ ok: true, value: evidence }, contract);
|
||||
return toEvidenceToolResult(evidence, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ import { recordSessionEvidenceSources } from "./sessionEvidence.js";
|
||||
import { runDerivedOperation } from "./runDerivedOperation.js";
|
||||
import { PROCESS_PROVIDER } from "./sessionToolPolicies.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
const PROCESS_CAPTURE_EVIDENCE = {
|
||||
operation: "capture_process_scenario",
|
||||
@@ -143,7 +143,9 @@ export const registerProcessComparisonTool = (
|
||||
);
|
||||
if (!recordedSources.ok)
|
||||
return toCallToolResult(recordedSources, contract);
|
||||
return toCallToolResult(
|
||||
return toEvidenceToolResult(
|
||||
evidence,
|
||||
contract,
|
||||
recordDerivedEvidence(
|
||||
session,
|
||||
evidence,
|
||||
@@ -156,7 +158,6 @@ export const registerProcessComparisonTool = (
|
||||
comparison,
|
||||
),
|
||||
),
|
||||
contract,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind recorded crash inspection to its named contract and session Evidence owner. */
|
||||
export const registerRecordedCrashTools = (
|
||||
@@ -24,9 +24,7 @@ export const registerRecordedCrashTools = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
} from "./sessionAvailabilityPolicy.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
const recordProcessResidualUnknowns = (
|
||||
session: BinarySessionPort,
|
||||
@@ -122,7 +122,7 @@ const registerProcessTools = ({
|
||||
captured.value.residual_unknowns,
|
||||
);
|
||||
if (!unknowns.ok) return toCallToolResult(unknowns, captureContract);
|
||||
return toCallToolResult(ok(evidence), captureContract);
|
||||
return toEvidenceToolResult(evidence, captureContract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind the module relationship workflow to its exact canonical contract. */
|
||||
export const registerWebModuleTool = (
|
||||
@@ -24,9 +24,7 @@ export const registerWebModuleTool = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind historical inspection to its named contract and caller-owned Evidence writer. */
|
||||
export const registerWebNetworkCaptureTool = (
|
||||
@@ -38,9 +38,7 @@ export const registerWebNetworkCaptureTool = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import type { Logger } from "../logger.js";
|
||||
import { mcpProgressReporter } from "./mcpProgress.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind distinct runtime operations to named contracts and the session's Evidence owner. */
|
||||
export const registerWebRuntimeTools = (
|
||||
@@ -29,9 +29,7 @@ export const registerWebRuntimeTools = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, execution);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, execution)
|
||||
: toCallToolResult(result, execution);
|
||||
return toEvidenceToolResult(result.value, execution, recorded);
|
||||
},
|
||||
);
|
||||
server.registerTool(
|
||||
@@ -43,9 +41,7 @@ export const registerWebRuntimeTools = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, listeners);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, listeners)
|
||||
: toCallToolResult(result, listeners);
|
||||
return toEvidenceToolResult(result.value, listeners, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Register local script export without requiring a live browser provider. */
|
||||
export const registerWebScriptTool = (
|
||||
@@ -26,9 +26,7 @@ export const registerWebScriptTool = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = options.recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { toCallToolResult } from "./toolResult.js";
|
||||
import { toCallToolResult, toEvidenceToolResult } from "./toolResult.js";
|
||||
|
||||
/** Bind source point tracing to its exact named contract and session Evidence owner. */
|
||||
export const registerWebSourceLocationTool = (
|
||||
@@ -24,9 +24,7 @@ export const registerWebSourceLocationTool = (
|
||||
);
|
||||
if (!result.ok) return toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult(result, contract);
|
||||
return toEvidenceToolResult(result.value, contract, recorded);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import type { CallToolResult } from "@modelcontextprotocol/server";
|
||||
import { STDIO_DEFAULT_MAX_BUFFER_SIZE } from "@modelcontextprotocol/server";
|
||||
|
||||
import type { ToolContract } from "../contracts/toolContracts.js";
|
||||
import type { Evidence } from "../domain/evidence.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { type AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
@@ -28,6 +29,22 @@ export const toCallToolResult = (
|
||||
? successResult(result.value, contract, context)
|
||||
: errorResult(result.error);
|
||||
|
||||
/** Deliver Evidence using the producer's explicit recording acknowledgment. */
|
||||
export const toEvidenceToolResult = (
|
||||
evidence: Evidence,
|
||||
contract: ToolContract,
|
||||
recorded: Result<unknown, AnalysisError> | undefined,
|
||||
): CallToolResult =>
|
||||
recorded !== undefined && !recorded.ok
|
||||
? errorResult(recorded.error)
|
||||
: toCallToolResult(
|
||||
{ ok: true, value: evidence },
|
||||
contract,
|
||||
recorded === undefined
|
||||
? undefined
|
||||
: { retainedEvidenceId: evidence.evidence_id },
|
||||
);
|
||||
|
||||
const errorResult = (error: AnalysisError): CallToolResult => {
|
||||
const projected = projectAnalysisError(error);
|
||||
const structuredContent = { error: projected };
|
||||
@@ -66,8 +83,8 @@ const successResult = (
|
||||
contract.name,
|
||||
"transport",
|
||||
encoded.constraint === "string-length"
|
||||
? "The complete MCP response exceeds Node's single-string representation limit. Use a focused retained-Evidence query or export to consume the complete analysis."
|
||||
: "The complete MCP response exceeds the stdio response budget. The analysis result was not truncated; use a focused retained-Evidence query or export to consume it.",
|
||||
? "The operation completed, but its complete MCP response exceeds Node's single-string representation limit. Export retained evidence to consume the complete analysis, or use complete CLI JSON output."
|
||||
: "The operation completed, but its complete MCP response exceeds the stdio response budget. The analysis result was not truncated; export retained evidence to consume it, or use complete CLI JSON output.",
|
||||
{
|
||||
boundary: "mcp-response",
|
||||
default_receive_buffer_bytes: STDIO_DEFAULT_MAX_BUFFER_SIZE,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
import { STDIO_DEFAULT_MAX_BUFFER_SIZE } from "@modelcontextprotocol/server";
|
||||
|
||||
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { ok } from "../../../src/domain/result.js";
|
||||
import { toCallToolResult } from "../../../src/server/toolResult.js";
|
||||
import { EvidenceLedger } from "../../../src/application/investigation/EvidenceLedger.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.js";
|
||||
import { toEvidenceToolResult } from "../../../src/server/toolResult.js";
|
||||
|
||||
describe("large complete Evidence MCP delivery", () => {
|
||||
it("preserves retained content and returns an actionable framing constraint", () => {
|
||||
it("does not claim retention without a recording acknowledgment", () => {
|
||||
const normalized = {
|
||||
payload: "x".repeat(Math.ceil(STDIO_DEFAULT_MAX_BUFFER_SIZE / 3)),
|
||||
};
|
||||
@@ -21,49 +20,29 @@ describe("large complete Evidence MCP delivery", () => {
|
||||
result: normalized,
|
||||
},
|
||||
);
|
||||
const ledger = new EvidenceLedger();
|
||||
expect(ledger.record(evidence).ok).toBe(true);
|
||||
const result = toCallToolResult(
|
||||
ok(evidence),
|
||||
const detached = toEvidenceToolResult(
|
||||
evidence,
|
||||
toolContract("analyze_javascript_application"),
|
||||
{ retainedEvidenceId: evidence.evidence_id },
|
||||
undefined,
|
||||
);
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.structuredContent).toMatchObject({
|
||||
error: {
|
||||
code: "resource_constraint",
|
||||
category: "resource_constraint",
|
||||
details: {
|
||||
resource: "transport",
|
||||
reported_limits: {
|
||||
boundary: "mcp-response",
|
||||
constraint: "receive-buffer",
|
||||
evidence_reference: {
|
||||
kind: "retained-evidence",
|
||||
evidence_id: evidence.evidence_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(Buffer.byteLength(JSON.stringify(result))).toBeLessThan(
|
||||
expect(detached.isError).toBe(true);
|
||||
expect(Buffer.byteLength(JSON.stringify(detached))).toBeLessThan(
|
||||
STDIO_DEFAULT_MAX_BUFFER_SIZE,
|
||||
);
|
||||
expect(result.content).toEqual([
|
||||
{ type: "text", text: JSON.stringify(result.structuredContent) },
|
||||
]);
|
||||
const retained = ledger.get(evidence.evidence_id);
|
||||
expect(parseEvidence(retained)).toEqual(evidence);
|
||||
expect(retained?.normalized_result).toEqual(normalized);
|
||||
const detached = toCallToolResult(
|
||||
ok(evidence),
|
||||
toolContract("analyze_javascript_application"),
|
||||
);
|
||||
expect(detached.structuredContent).toMatchObject({
|
||||
error: {
|
||||
details: { reported_limits: { evidence_id: evidence.evidence_id } },
|
||||
},
|
||||
});
|
||||
expect(JSON.stringify(detached)).not.toContain("retained-evidence");
|
||||
const limits = z
|
||||
.object({
|
||||
error: z.object({
|
||||
details: z.object({
|
||||
reported_limits: z.record(z.string(), z.unknown()),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.parse(detached.structuredContent).error.details.reported_limits;
|
||||
expect(limits).not.toHaveProperty("evidence_reference");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
// A real initialized data range, with independently recognizable endpoints.
|
||||
volatile unsigned char rea_large_payload[REA_PAYLOAD_BYTES] = {
|
||||
[0] = 0x5a, [REA_PAYLOAD_BYTES - 1] = 0xa5};
|
||||
|
||||
__attribute__((noinline, used)) int rea_large_entry(void) {
|
||||
return rea_large_payload[0] + rea_large_payload[REA_PAYLOAD_BYTES - 1];
|
||||
}
|
||||
|
||||
int main(void) { return rea_large_entry(); }
|
||||
Reference in new issue
Block a user