Files
reverse-engineering-anything/tests/acceptance/nonBinaryComposition.test.ts
T
morlutoandCursor Agent 562779bd71 fix(mcp): advertise absolute paths for tools that reject relative inputs (#9179)
* 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>
2026-11-07 15:49:50 +08:00

344 lines
12 KiB
TypeScript

import { parseMcpToolError } from "../fixtures/mcpToolError.js";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, onTestFinished } from "vitest";
import { createFirmwareAnalysisProvider } from "../../src/composition/firmware.js";
import { createJavaScriptRecoveryProvider } from "../../src/composition/javascriptRecovery.js";
import { createServer } from "../../src/server/createServer.js";
import { createTestBinarySession } from "../fixtures/binarySession.js";
import { writeOrderedZip } from "../fixtures/artifactEntryOrder.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest } from "../support/cli/cliFixture.js";
const connect = async (environment: NodeJS.ProcessEnv) => {
const session = createTestBinarySession(() => {
throw new Error("Target-free analysis must not acquire a deep provider");
});
const server = createServer(
{ kind: "session", session },
{
environment,
firmwareAnalysis: createFirmwareAnalysisProvider(environment),
javascriptRecovery: createJavaScriptRecoveryProvider(environment),
},
);
const client = new Client({ name: "composition-parity", version: "1" });
onTestFinished(async () => {
await client.close();
await server.close();
await session.close();
});
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
await client.connect(clientTransport);
return client;
};
for (const { jarConfigured, setting } of [
{ jarConfigured: false, setting: "REA_JADX_MCP_JAR" },
{ jarConfigured: true, setting: "JAVA_HOME" },
])
cliTest.skipIf(process.platform === "win32")(
`preserves the APK ${setting} failure through the compiled CLI and MCP factories`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-android-composition-");
const apk = join(root, "fixture.apk");
await writeOrderedZip(apk, ["AndroidManifest.xml", "classes.dex"]);
const jar = join(root, "jadx-headless-mcp.jar");
await writeFile(
jar,
"fixture jar; validation must fail before execution",
);
const environment = {
REA_LOG_LEVEL: "silent",
JAVA_HOME: "relative-fixture-jdk",
...(jarConfigured ? { REA_JADX_MCP_JAR: jar } : {}),
};
const cliResult = await cli.run({
arguments: ["inspect-android-package", apk, "--json"],
environment,
timeoutMs: 10_000,
});
expect(cliResult.exitCode).toBe(1);
expect(cliResult.json).toMatchObject({ code: "capability_unavailable" });
const client = await connect(environment);
const response = await client.callTool({
name: "inspect_android_package",
arguments: { path: apk },
});
expect(response.isError).toBe(true);
expect(parseMcpToolError(response)).toEqual({ error: cliResult.json });
expect(JSON.stringify(parseMcpToolError(response))).toContain(setting);
},
);
cliTest.skipIf(process.platform === "win32")(
"distinguishes a rejected relative REA_JADX_MCP_JAR from an unset one through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-android-relative-jar-");
const apk = join(root, "fixture.apk");
await writeOrderedZip(apk, ["AndroidManifest.xml"]);
const run = async (environment: Record<string, string>) => {
const cliResult = await cli.run({
arguments: ["inspect-android-package", apk, "--json"],
environment: { REA_LOG_LEVEL: "silent", ...environment },
timeoutMs: 10_000,
});
expect(cliResult.exitCode).toBe(1);
const client = await connect({ REA_LOG_LEVEL: "silent", ...environment });
const response = await client.callTool({
name: "inspect_android_package",
arguments: { path: apk },
});
expect(response.isError).toBe(true);
expect(parseMcpToolError(response)).toEqual({ error: cliResult.json });
return cliResult.json as {
code: string;
message: string;
details: { diagnostics: Record<string, unknown> };
};
};
const relative = "./tools/jadx-headless-mcp.jar";
const rejected = await run({ REA_JADX_MCP_JAR: relative });
expect(rejected.code).toBe("capability_unavailable");
expect(rejected.details.diagnostics).toEqual({
jar_configured: true,
jar_path: relative,
path_requirement: "absolute",
failure_kind: "jar",
failure_code: "open_options_invalid",
});
expect(rejected.message).toContain(`received "${relative}"`);
const empty = await run({ REA_JADX_MCP_JAR: "" });
expect(empty.details.diagnostics).toMatchObject({
jar_configured: true,
jar_path: "",
failure_code: "open_options_invalid",
});
const unset = await run({});
expect(unset.details.diagnostics).toEqual({
jar_configured: false,
failure_kind: "jar",
failure_code: "not_configured",
});
},
);
cliTest.skipIf(process.platform !== "linux")(
"preserves the missing firmware command reason through both caller paths",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-firmware-composition-");
const path = join(root, "input.bin");
await writeFile(path, "firmware fixture");
const environment = { REA_LOG_LEVEL: "silent" };
const cliResult = await cli.run({
arguments: ["inspect-firmware-regions", path, "--json"],
environment,
timeoutMs: 10_000,
});
expect(cliResult.exitCode).toBe(1);
expect(cliResult.json).toMatchObject({ code: "capability_unavailable" });
const client = await connect(environment);
const response = await client.callTool({
name: "inspect_firmware_regions",
arguments: { path },
});
expect(response.isError).toBe(true);
expect(parseMcpToolError(response)).toEqual({ error: cliResult.json });
expect(JSON.stringify(parseMcpToolError(response))).toContain(
"REA_BINWALK_COMMAND",
);
},
);
cliTest.skipIf(process.platform !== "linux")(
"resolves relative firmware CLI paths against the operator working directory",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-firmware-cli-relative-");
await writeFile(join(root, "input.bin"), "firmware fixture");
const cliResult = await cli.run({
arguments: ["inspect-firmware-regions", "input.bin", "--json"],
cwd: root,
environment: { REA_LOG_LEVEL: "silent" },
timeoutMs: 10_000,
});
// A relative path must pass input validation and reach the provider,
// which reports the missing firmware engine — not a validation error.
expect(cliResult.exitCode).toBe(1);
expect(cliResult.json).toMatchObject({ code: "capability_unavailable" });
},
);
cliTest(
"preserves optional JavaScript recovery configuration through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-recovery-composition-");
const path = join(root, "bundle.js");
await writeFile(path, "globalThis.fixture = 1;");
const environment = {
REA_LOG_LEVEL: "silent",
REA_WAKARU_COMMAND: "relative-unavailable-tool",
};
const cliResult = await cli.run({
arguments: [
"recover-javascript-sources",
path,
join(root, "output"),
"--json",
],
environment,
timeoutMs: 10000,
});
expect(cliResult.exitCode).toBe(1);
expect(cliResult.json).toMatchObject({ code: "capability_unavailable" });
const client = await connect(environment);
const response = await client.callTool({
name: "recover_javascript_sources",
arguments: { path, output_directory: join(root, "output") },
});
expect(response.isError).toBe(true);
expect(parseMcpToolError(response)).toEqual({ error: cliResult.json });
expect(JSON.stringify(parseMcpToolError(response))).toContain(
"REA_WAKARU_COMMAND",
);
},
);
cliTest(
"returns recovery path diagnostics before missing-engine errors in CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-recovery-path-parity-");
const environment = {
REA_LOG_LEVEL: "silent",
REA_WAKARU_COMMAND: "relative-unavailable-tool",
};
const client = await connect(environment);
for (const field of ["path", "output_directory"] as const) {
const input = {
path: join(root, "bundle.js"),
output_directory: join(root, "output"),
[field]: "relative.js",
};
const result = await cli.run({
arguments: [
"recover-javascript-sources",
input.path,
input.output_directory,
"--json",
],
environment,
timeoutMs: 10_000,
});
const response = await client.callTool({
name: "recover_javascript_sources",
arguments: input,
});
expect(result.exitCode).toBe(1);
expect(response.isError).toBe(true);
expect(result.json).toMatchObject({
code: "VALIDATION_ERROR",
fieldErrors: [
expect.objectContaining({
path: field === "path" ? "path" : "outputDirectory",
code: "invalid_format",
}),
],
});
expect(parseMcpToolError(response)).toMatchObject({
error: {
code: "invalid_request",
details: {
operation: "recover_javascript_sources",
issues: [
{
path: [field],
reason: "invalid_format",
expected: "regex",
},
],
},
},
});
expect(JSON.stringify(result.json)).not.toContain("REA_WAKARU_COMMAND");
expect(JSON.stringify(parseMcpToolError(response))).not.toContain(
"REA_WAKARU_COMMAND",
);
}
},
);
cliTest.skipIf(process.platform === "win32")(
"shares runtime JADX settings with the default embedded MCP provider",
async () => {
const root = await createTestTempDirectory("rea-embedded-jadx-settings-");
const apk = join(root, "fixture.apk");
await writeOrderedZip(apk, ["AndroidManifest.xml", "classes.dex"]);
const client = await connect({
REA_LOG_LEVEL: "silent",
REA_JADX_OPERATION_TIMEOUT_MS: "0",
});
const request = {
name: "inspect_android_package",
arguments: { path: apk },
};
const inspect = async () =>
parseMcpToolError(await client.callTool(request)).error;
const invalid = {
code: "capability_unavailable",
details: {
diagnostics: {
failure_kind: "operation_timeout",
failure_code: "open_options_invalid",
setting: "REA_JADX_OPERATION_TIMEOUT_MS",
value: "0",
},
},
};
expect(await inspect()).toMatchObject(invalid);
const updated = await client.callTool({
name: "update_analysis_settings",
arguments: {
changes: [
{ setting: "jadx.operation_timeout_ms", action: "set", value: 42000 },
],
},
});
expect(updated.isError).not.toBe(true);
expect(updated.structuredContent).toMatchObject({
settings: [
{
setting: "jadx.operation_timeout_ms",
value: 42000,
source: "runtime",
},
],
});
// The same provider/request now passes timeout validation and reaches the
// deliberately absent JAR prerequisite. No engine launch is simulated.
expect(await inspect()).toMatchObject({
code: "capability_unavailable",
details: {
diagnostics: {
failure_kind: "jar",
failure_code: "not_configured",
jar_configured: false,
},
},
});
const reset = await client.callTool({
name: "update_analysis_settings",
arguments: {
changes: [{ setting: "jadx.operation_timeout_ms", action: "reset" }],
},
});
expect(reset.isError).not.toBe(true);
expect(await inspect()).toMatchObject(invalid);
},
);