mirror of
https://github.com/morluto/rea.git
synced 2026-11-08 07:46:28 +00:00
* fix(mcp): advertise absolute paths for tools that reject relative inputs The domain schemas accepted relative paths, then handlers rejected them with isAbsolute. Those inputs now use absoluteLocalPathStringSchema, and the duplicate handler checks are gone. * fix(javascript): own recovery path admission in the schema Relative recovery paths now fail in absoluteLocalPathStringSchema, including the additional_paths index. Drop the duplicate handler check, and make the CLI, MCP, and tools/list tests assert that schema fact. * test: assert absolute-path facts instead of error prose Recovery and web-script tests keep the field, format code, and regex expectation. They no longer freeze the schema's explanatory sentence. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
118 lines
4.1 KiB
TypeScript
118 lines
4.1 KiB
TypeScript
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
import type { AnySchema } from "ajv";
|
|
import { expect, it } from "vitest";
|
|
|
|
import { JAVASCRIPT_RECOVERY_TOOL_CONTRACTS } from "../../../src/contracts/javascript/javascriptRecoveryToolContracts.js";
|
|
import { BINARY_DIAGNOSTICS_TOOL_CONTRACTS } from "../../../src/contracts/native/binaryDiagnosticsToolContracts.js";
|
|
import { WEB_SCRIPT_TOOL_CONTRACTS } from "../../../src/contracts/webScriptToolContracts.js";
|
|
import { RequestTrackingMcpServer } from "../../../src/server/RequestTrackingMcpServer.js";
|
|
import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js";
|
|
|
|
const CONTRACTS = [
|
|
...BINARY_DIAGNOSTICS_TOOL_CONTRACTS,
|
|
...WEB_SCRIPT_TOOL_CONTRACTS,
|
|
...JAVASCRIPT_RECOVERY_TOOL_CONTRACTS,
|
|
];
|
|
|
|
const PATH_FIELDS = {
|
|
inspect_apple_ips: ["path"],
|
|
inspect_elf_notes: ["path"],
|
|
inspect_kernel_module: ["path"],
|
|
inspect_byte_entropy: ["path"],
|
|
export_web_scripts: ["capture_path", "output_directory"],
|
|
recover_javascript_sources: ["path", "output_directory", "additional_paths"],
|
|
} as const;
|
|
|
|
const absoluteFile =
|
|
process.platform === "win32" ? "C:\\sample.bin" : "/tmp/sample.bin";
|
|
const absoluteDirectory = process.platform === "win32" ? "C:\\out" : "/tmp/out";
|
|
|
|
const contractFor = (name: keyof typeof PATH_FIELDS) => {
|
|
const contract = CONTRACTS.find((candidate) => candidate.name === name);
|
|
if (contract === undefined) throw new Error(`Missing contract ${name}`);
|
|
return contract;
|
|
};
|
|
|
|
const validInput = (
|
|
name: keyof typeof PATH_FIELDS,
|
|
): Record<string, unknown> => {
|
|
if (name === "export_web_scripts")
|
|
return {
|
|
capture_path: absoluteFile,
|
|
output_directory: absoluteDirectory,
|
|
};
|
|
if (name === "recover_javascript_sources")
|
|
return {
|
|
path: absoluteFile,
|
|
output_directory: absoluteDirectory,
|
|
additional_paths: [absoluteFile],
|
|
};
|
|
return { path: absoluteFile };
|
|
};
|
|
|
|
const withRelativeField = (
|
|
name: keyof typeof PATH_FIELDS,
|
|
field: string,
|
|
): Record<string, unknown> => ({
|
|
...validInput(name),
|
|
[field]: field === "additional_paths" ? ["sample.bin"] : "sample.bin",
|
|
});
|
|
|
|
it("rejects one relative path field at a time on tools/list", async () => {
|
|
const server = new RequestTrackingMcpServer({
|
|
name: "absolute-path-schema",
|
|
version: "1",
|
|
});
|
|
for (const name of Object.keys(PATH_FIELDS) as (keyof typeof PATH_FIELDS)[]) {
|
|
const contract = contractFor(name);
|
|
server.registerTool(
|
|
contract.name,
|
|
toolRegistrationOptions(contract),
|
|
() => ({ content: [] }),
|
|
);
|
|
}
|
|
const client = new Client({ name: "absolute-path-client", version: "1" });
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
|
const checked: string[] = [];
|
|
try {
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
const listed = await client.listTools();
|
|
for (const name of Object.keys(
|
|
PATH_FIELDS,
|
|
) as (keyof typeof PATH_FIELDS)[]) {
|
|
const schema = listed.tools.find(
|
|
(tool) => tool.name === name,
|
|
)?.inputSchema;
|
|
expect(schema, name).toBeDefined();
|
|
if (schema === undefined) throw new Error(`Missing schema ${name}`);
|
|
const validate = ajv.compile(schema as AnySchema);
|
|
expect(validate(validInput(name)), `${name} valid`).toBe(true);
|
|
for (const field of PATH_FIELDS[name]) {
|
|
expect(
|
|
validate(withRelativeField(name, field)),
|
|
`${name}.${field}`,
|
|
).toBe(false);
|
|
checked.push(`${name}.${field}`);
|
|
}
|
|
}
|
|
} finally {
|
|
await client.close();
|
|
await server.close();
|
|
}
|
|
expect(checked).toEqual([
|
|
"inspect_apple_ips.path",
|
|
"inspect_elf_notes.path",
|
|
"inspect_kernel_module.path",
|
|
"inspect_byte_entropy.path",
|
|
"export_web_scripts.capture_path",
|
|
"export_web_scripts.output_directory",
|
|
"recover_javascript_sources.path",
|
|
"recover_javascript_sources.output_directory",
|
|
"recover_javascript_sources.additional_paths",
|
|
]);
|
|
});
|