fix(inputs): validate command admission and preserve valid MCP requests (#9901)

* fix(cli): validate named inputs before target admission

* fix(mcp): preserve valid inputs when advertised schema references widen

* test(inputs): reach session behavior with admitted inputs

One-shot admission now rejects incomplete requests before a session exists. Lifecycle tests supply the required fields, and unknown arguments are rejected before provider issues are retained.

* fix(cli): preserve Electron fuse path spelling

Anchor relative fuse paths without stripping dot segments, and let an empty path reach the operation so it still reports invalid_request.

* fix(cli): reject outline-depth unless detail is summary

The advertised schema already required detail summary whenever outline-depth
was present. The parser still applied the full default and accepted that
combination, so its output failed the same schema. Parsing now rejects it
before the command runs.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
morlutoandCursor Agent authored and GitHub committed 2026-11-11 00:43:30 -05:00
1 parent 1371ba1e07
commit 5c2917266d
35 files changed
+1340 -360

No files matched your search

@@ -13,6 +13,9 @@ import { err, ok } from "../../domain/result.js";
import { EvidenceFileError } from "../../domain/evidenceErrors.js";
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
import type { Il2cppSeedWriterPort } from "./Il2cppSeedWriterPort.js";
import { toolContract } from "../../contracts/toolContracts.js";
import { z } from "zod";
import { Ajv2020 } from "ajv/dist/2020.js";
const identity = {
id: "fixture-il2cpp",
@@ -21,6 +24,29 @@ const identity = {
};
const report = createEmptyIl2cppMetadataFixture();
it.each([{ binary_architecture: "arm64" }, { generic_instances: true }])(
"binds binary-dependent selectors in the named input contract: %j",
(selection) => {
const schema = toolContract("inspect_il2cpp_metadata").inputSchema;
const validate = new Ajv2020({ strict: false }).compile(
z.toJSONSchema(schema, { io: "input" }),
);
const missingBinary = { path: report.artifact.path, ...selection };
const selectedBinary = {
...missingBinary,
binary_path: "/artifacts/GameAssembly.dll",
};
expect(schema.safeParse(missingBinary).success).toBe(false);
expect(validate(missingBinary)).toBe(false);
expect(schema.safeParse(selectedBinary).success).toBe(true);
expect(validate(selectedBinary)).toBe(true);
expect(
schema.safeParse({ path: report.artifact.path, generic_instances: false })
.success,
).toBe(true);
},
);
const serviceReturning = (value: Il2cppMetadata, calls = { count: 0 }) =>
new Il2cppMetadataService({
close: async () => {},
@@ -106,32 +106,6 @@ export class Il2cppMetadataService {
options?.signal,
);
if (!input.ok) return input;
if (
input.value.binary_architecture !== undefined &&
input.value.binary_path === undefined
)
return err(
new AnalysisInputError(OPERATION, undefined, [
{
path: ["binary_architecture"],
reason: "invalid_value",
message: "binary_architecture selects a slice of binary_path.",
},
]),
);
if (
input.value.generic_instances === true &&
input.value.binary_path === undefined
)
return err(
new AnalysisInputError(OPERATION, undefined, [
{
path: ["generic_instances"],
reason: "invalid_value",
message: "generic_instances links generic methods in binary_path.",
},
]),
);
if (output !== undefined) {
if (input.value.binary_path === undefined)
return err(