mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
feat(ida)!: require an explicit protocol for attached IDA registrations (#10846)
* feat(ida)!: require an explicit protocol for attached IDA registrations An attached registration (the default mode) silently selected the legacy ida-pro-mcp 1.4 GUI proxy when `protocol` was omitted. The registration schema now resolves the upstream protocol once: attached registrations must name `"legacy"` or `"native"`, headless registrations resolve to the database supervisor, and consumers read the resolved protocol instead of inferring it from `mode`. BREAKING CHANGE: an attached IDA registration without `protocol` is refused with a configuration error naming the choice. Add `"protocol": "legacy"` to an ida-pro-mcp 1.4 proxy registration (or `"protocol": "native"` for the ida-free-mcp plugin). Headless registrations are unchanged. * test(ida): name the protocol in every attached registration fixture The setup budget fixture still wrote an attached URL registration without a protocol, and the IDA registration rejection fixtures relied on the missing protocol as one of several failures. Each now names its protocol, so setup reaches its timeout checks and every rejection case fails only for its own constraint.
This commit is contained in:
1 parent
66d1527a3d
commit
8428de13d3
36 files changed
+256
-52
No files matched your search
@@ -2,7 +2,7 @@
|
||||
|
||||
REA adapts an existing IDA MCP server into its native analysis tools. Existing IDA Pro profiles use [mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp); an optional native bridge for IDA Pro and IDA Free is described below. Install and configure your selected engine and upstream MCP using their instructions. REA does not install, upgrade, activate, or license them, and does not bundle another IDA server.
|
||||
|
||||
Choose one lifecycle mode in an upstream MCP JSON registration. Set `REA_IDA_MCP_CONFIG` to that file, then select `ida` once through `REA_ANALYSIS_PROVIDER`, the CLI's `--provider ida`, or MCP's `open_binary.provider_id`. No provider startup occurs during discovery.
|
||||
Choose one lifecycle mode in an upstream MCP JSON registration. An attached registration (the default mode) must also name its upstream `protocol`: `"legacy"` for the ida-pro-mcp 1.4 GUI proxy or `"native"` for the ida-free-mcp plugin. Headless registrations omit `protocol`. An attached registration without `protocol` is refused with a configuration error naming the choice; REA no longer assumes the legacy proxy. Set `REA_IDA_MCP_CONFIG` to that file, then select `ida` once through `REA_ANALYSIS_PROVIDER`, the CLI's `--provider ida`, or MCP's `open_binary.provider_id`. No provider startup occurs during discovery.
|
||||
|
||||
## Supported upstream versions
|
||||
|
||||
@@ -12,17 +12,18 @@ Startup failures identify the accepted REA analysis operation. Missing-profile a
|
||||
|
||||
The headless profile is defined by the database-supervisor API, not by an upstream version string. The upstream source snapshot [`c133c385`](https://github.com/mrexodia/ida-pro-mcp/tree/c133c3853faa111a9b00ee615c013b720d0c4acd) includes `idb_close` (introduced by [upstream PR #495](https://github.com/mrexodia/ida-pro-mcp/pull/495)) alongside `idb_open` and `idb_list`. Earlier builds may report the same `2.0.0` version while lacking that tool, so REA checks the live catalog rather than treating a version number or branch name as a compatibility guarantee.
|
||||
|
||||
| Mode | Upstream profile | Known working |
|
||||
| -------- | ------------------- | ------------------------------------------------------ |
|
||||
| attached | legacy GUI tools | 1.4.0 |
|
||||
| headless | database supervisor | builds serving `idb_open`, `idb_list`, and `idb_close` |
|
||||
| Mode | Upstream profile | Known working |
|
||||
| --------------------------------- | ------------------- | ------------------------------------------------------ |
|
||||
| attached (`"protocol": "legacy"`) | legacy GUI tools | 1.4.0 |
|
||||
| headless | database supervisor | builds serving `idb_open`, `idb_list`, and `idb_close` |
|
||||
|
||||
## Analyze an already-open GUI target
|
||||
|
||||
The attached profile supports upstream **1.4.0 legacy tools**, including `get_metadata`, `list_functions`, and `decompile_function`. Copy the upstream registration's `command`, `args`, and optional `env` into a JSON file. An existing `mcpServers` object with an `ida-pro-mcp` entry is also accepted. For example:
|
||||
The attached profile supports upstream **1.4.0 legacy tools**, including `get_metadata`, `list_functions`, and `decompile_function`. Copy the upstream registration's `command`, `args`, and optional `env` into a JSON file and add `"protocol": "legacy"`. An existing `mcpServers` object with an `ida-pro-mcp` entry is also accepted; add `"protocol": "legacy"` to that entry. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"protocol": "legacy",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"/absolute/path/to/ida_pro_mcp/server.py",
|
||||
@@ -73,7 +74,7 @@ IDA Pro and IDA Free can use the separate [ida-free-mcp native plugin](https://g
|
||||
}
|
||||
```
|
||||
|
||||
Set `REA_IDA_MCP_CONFIG` to this file and select `ida` using the same CLI/MCP commands above. Omitting `protocol` preserves existing IDA Pro registrations. Native mode requires `resources/read` for `ida://idb/metadata` and its recorded input SHA-256; it never substitutes a digest of an unrelated local file. The native resource's `path` describes the IDB in the upstream protocol, not the original input path. REA retains it and checks database identity and image base before and after requests.
|
||||
Set `REA_IDA_MCP_CONFIG` to this file and select `ida` using the same CLI/MCP commands above. Attached IDA Pro registrations name `"protocol": "legacy"` instead. Native mode requires `resources/read` for `ida://idb/metadata` and its recorded input SHA-256; it never substitutes a digest of an unrelated local file. The native resource's `path` describes the IDB in the upstream protocol, not the original input path. REA retains it and checks database identity and image base before and after requests.
|
||||
|
||||
This experimental profile provides function/string inventory and search, function lookup, assembly, GUI pseudocode, callees, address xrefs, instruction windows, and function dossiers. Direct callers remain unavailable: native code/data xref labels do not prove call edges. It requires neither IDAPython nor idalib. Native headless registrations are rejected.
|
||||
|
||||
@@ -110,6 +111,7 @@ its modern tool catalog does not select this profile.
|
||||
```json
|
||||
{
|
||||
"mode": "attached",
|
||||
"protocol": "legacy",
|
||||
"command": "python",
|
||||
"args": [
|
||||
"/absolute/path/to/ida_pro_mcp/server.py",
|
||||
|
||||
@@ -472,7 +472,7 @@ it("shares one parsed IDA registration between timeout-budget and doctor project
|
||||
const registration = join(root, "registration.json");
|
||||
await writeFile(
|
||||
registration,
|
||||
JSON.stringify({ command: "fixture", timeoutMs: 3210 }),
|
||||
JSON.stringify({ command: "fixture", protocol: "legacy", timeoutMs: 3210 }),
|
||||
);
|
||||
const host = createSystemDoctorHost({ REA_IDA_MCP_CONFIG: registration });
|
||||
try {
|
||||
|
||||
@@ -227,6 +227,7 @@ describe("headless IDA callee edges on x86 targets", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -14,6 +14,11 @@ const resourceScope: RetainedResourceScope = {
|
||||
retain: () => undefined,
|
||||
run: (operation) => operation(),
|
||||
};
|
||||
/** An attached registration names its upstream protocol. */
|
||||
const LEGACY_REGISTRATION = JSON.stringify({
|
||||
command: "python",
|
||||
protocol: "legacy",
|
||||
});
|
||||
const read = (path: string) => readIdaConfiguration(path, { resourceScope });
|
||||
|
||||
const roots: string[] = [];
|
||||
@@ -28,10 +33,14 @@ describe("IDA registration and MCP result boundaries", () => {
|
||||
roots.push(root);
|
||||
const path = join(root, "mcp.json");
|
||||
for (const input of [
|
||||
{ command: "python", args: ["server.py"] },
|
||||
{ command: "python", args: ["server.py"], protocol: "legacy" },
|
||||
{
|
||||
mcpServers: {
|
||||
"ida-pro-mcp": { command: "python", args: ["server.py"] },
|
||||
"ida-pro-mcp": {
|
||||
command: "python",
|
||||
args: ["server.py"],
|
||||
protocol: "legacy",
|
||||
},
|
||||
unrelated: { command: "untouched" },
|
||||
},
|
||||
},
|
||||
@@ -39,10 +48,16 @@ describe("IDA registration and MCP result boundaries", () => {
|
||||
await writeFile(path, JSON.stringify(input));
|
||||
expect(await read(path)).toMatchObject({
|
||||
ok: true,
|
||||
value: { command: "python", args: ["server.py"], mode: "attached" },
|
||||
value: {
|
||||
command: "python",
|
||||
args: ["server.py"],
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("validates local transport and lifecycle inputs without leaking auth into a profile", async () => {
|
||||
const { root } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
@@ -57,7 +72,7 @@ describe("IDA registration and MCP result boundaries", () => {
|
||||
{ command: "python", workspaceRoot: "relative" },
|
||||
{ command: "python", args: "server.py" },
|
||||
]) {
|
||||
await writeFile(path, JSON.stringify(input));
|
||||
await writeFile(path, JSON.stringify({ ...input, protocol: "legacy" }));
|
||||
expect((await read(path)).ok).toBe(false);
|
||||
}
|
||||
await writeFile(
|
||||
@@ -73,13 +88,13 @@ describe("IDA registration and MCP result boundaries", () => {
|
||||
"http://host.docker.internal:13337/mcp",
|
||||
"http://HOST.CONTAINERS.INTERNAL:13337/mcp",
|
||||
]) {
|
||||
await writeFile(path, JSON.stringify({ url }));
|
||||
await writeFile(path, JSON.stringify({ url, protocol: "legacy" }));
|
||||
expect(await read(path)).toMatchObject({ ok: true, value: { url } });
|
||||
}
|
||||
await writeFile(path, "malformed");
|
||||
expect((await read(path)).ok).toBe(false);
|
||||
expect((await read(join(root, "absent"))).ok).toBe(false);
|
||||
const registration = JSON.stringify({ command: "python" });
|
||||
const registration = LEGACY_REGISTRATION;
|
||||
await writeFile(path, registration.padEnd(65_536));
|
||||
expect((await read(path)).ok).toBe(true);
|
||||
await writeFile(path, registration.padEnd(65_537));
|
||||
@@ -94,7 +109,7 @@ it("rejects registration growth beyond the byte limit after descriptor admission
|
||||
const { root } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "mcp.json");
|
||||
await writeFile(path, JSON.stringify({ command: "python" }));
|
||||
await writeFile(path, LEGACY_REGISTRATION);
|
||||
const openRegularFile = regularFile.openRegularFile;
|
||||
const admission = vi
|
||||
.spyOn(regularFile, "openRegularFile")
|
||||
@@ -118,7 +133,7 @@ describe("IDA registration descriptor ownership", () => {
|
||||
const { root } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "mcp.json");
|
||||
await writeFile(path, JSON.stringify({ command: "python" }));
|
||||
await writeFile(path, LEGACY_REGISTRATION);
|
||||
const retained: { close(): Promise<void> }[] = [];
|
||||
const owner: RetainedResourceScope = {
|
||||
retain: (resource) => retained.push(resource),
|
||||
@@ -150,7 +165,7 @@ describe("IDA registration descriptor ownership", () => {
|
||||
const { root } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "mcp.json");
|
||||
await writeFile(path, JSON.stringify({ command: "python" }));
|
||||
await writeFile(path, LEGACY_REGISTRATION);
|
||||
const reason = new Error("stop registration inspection");
|
||||
const controller = new AbortController();
|
||||
controller.abort(reason);
|
||||
@@ -165,7 +180,7 @@ describe("IDA registration descriptor ownership", () => {
|
||||
const { root } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "mcp.json");
|
||||
await writeFile(path, JSON.stringify({ command: "python" }));
|
||||
await writeFile(path, LEGACY_REGISTRATION);
|
||||
const retained: { close(): Promise<void> }[] = [];
|
||||
const owner: RetainedResourceScope = {
|
||||
retain: (resource) => retained.push(resource),
|
||||
@@ -206,7 +221,7 @@ describe("IDA registration descriptor ownership", () => {
|
||||
const { root } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "mcp.json");
|
||||
await writeFile(path, JSON.stringify({ command: "python" }));
|
||||
await writeFile(path, LEGACY_REGISTRATION);
|
||||
const retained: { close(): Promise<void> }[] = [];
|
||||
const owner: RetainedResourceScope = {
|
||||
retain: (resource) => retained.push(resource),
|
||||
@@ -264,8 +279,8 @@ it.each(["env", "headers"] as const)(
|
||||
);
|
||||
const transport =
|
||||
field === "env"
|
||||
? { command: "python" }
|
||||
: { url: "http://127.0.0.1:8745/mcp" };
|
||||
? { command: "python", protocol: "legacy" }
|
||||
: { url: "http://127.0.0.1:8745/mcp", protocol: "legacy" };
|
||||
await writeFile(path, JSON.stringify({ ...transport, [field]: mapping }));
|
||||
const result = await read(path);
|
||||
if (!result.ok) throw result.error;
|
||||
@@ -288,8 +303,8 @@ it.each(["env", "headers"] as const)(
|
||||
const path = join(root, "mcp.json");
|
||||
const transport =
|
||||
field === "env"
|
||||
? { command: "python" }
|
||||
: { url: "http://127.0.0.1:8745/mcp" };
|
||||
? { command: "python", protocol: "legacy" }
|
||||
: { url: "http://127.0.0.1:8745/mcp", protocol: "legacy" };
|
||||
for (const value of [42, null, {}]) {
|
||||
await writeFile(
|
||||
path,
|
||||
@@ -302,3 +317,50 @@ it.each(["env", "headers"] as const)(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// An attached registration no longer defaults to the legacy 1.4 proxy; it
|
||||
// fails closed naming the protocol choice instead of guessing.
|
||||
it.each([
|
||||
{ command: "python", args: ["server.py"] },
|
||||
{ command: "python", mode: "attached" },
|
||||
{ url: "http://127.0.0.1:13337/mcp" },
|
||||
])(
|
||||
"requires an explicit protocol for attached registration %j",
|
||||
async (input) => {
|
||||
const { root } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "mcp.json");
|
||||
await writeFile(path, JSON.stringify(input));
|
||||
const parsed = await read(path);
|
||||
if (parsed.ok) throw new Error("Expected an omitted protocol to fail");
|
||||
expect(parsed.error.settings).toEqual([
|
||||
{
|
||||
setting: "REA_IDA_MCP_CONFIG",
|
||||
constraint: expect.stringContaining('set protocol to "native"'),
|
||||
},
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
it("resolves the single headless protocol without a protocol field", async () => {
|
||||
const { root } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "mcp.json");
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "python", mode: "headless" }),
|
||||
);
|
||||
expect(await read(path)).toMatchObject({
|
||||
ok: true,
|
||||
value: { mode: "headless", protocol: "modern" },
|
||||
});
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({
|
||||
command: "python",
|
||||
mode: "headless",
|
||||
protocol: "legacy",
|
||||
}),
|
||||
);
|
||||
expect((await read(path)).ok).toBe(false);
|
||||
});
|
||||
@@ -45,7 +45,12 @@ const common = {
|
||||
})
|
||||
.optional(),
|
||||
mode: z.enum(["attached", "headless"]).default("attached"),
|
||||
protocol: z.literal("native").optional(),
|
||||
/**
|
||||
* Attached mode serves two upstream protocols, so an attached registration
|
||||
* names one; omitting it no longer selects the legacy 1.4 GUI proxy.
|
||||
* Headless mode has one protocol (the database supervisor).
|
||||
*/
|
||||
protocol: z.enum(["legacy", "native"]).optional(),
|
||||
timeoutMs: z
|
||||
.int()
|
||||
.min(MIN_IDA_REQUEST_TIMEOUT_MS)
|
||||
@@ -85,9 +90,18 @@ const registrationSchema = z
|
||||
.strict(),
|
||||
])
|
||||
.refine(
|
||||
(config) => config.protocol !== "native" || config.mode === "attached",
|
||||
"The native IDA MCP protocol requires attached mode",
|
||||
);
|
||||
(config) => config.protocol === undefined || config.mode === "attached",
|
||||
"An IDA MCP protocol requires attached mode; headless registrations omit protocol",
|
||||
)
|
||||
.refine(
|
||||
(config) => config.protocol !== undefined || config.mode !== "attached",
|
||||
'attached IDA registrations must set protocol to "native" (ida-free-mcp plugin) or "legacy" (ida-pro-mcp 1.4 GUI proxy)',
|
||||
)
|
||||
// Resolve the upstream protocol once; consumers never infer it from mode.
|
||||
.transform((config) => ({
|
||||
...config,
|
||||
protocol: config.protocol ?? ("modern" as const),
|
||||
}));
|
||||
|
||||
/** An upstream MCP registration plus REA's database lifecycle selection. */
|
||||
export type IdaConfiguration = z.infer<typeof registrationSchema>;
|
||||
@@ -130,13 +144,10 @@ export const IDA_COMPATIBILITY_PROFILES = {
|
||||
export type IdaCompatibilityProfile =
|
||||
(typeof IDA_COMPATIBILITY_PROFILES)[keyof typeof IDA_COMPATIBILITY_PROFILES];
|
||||
|
||||
/** Preserve existing attached/headless registrations when protocol is omitted. */
|
||||
/** The compatibility facts of the registration's resolved protocol. */
|
||||
export const idaCompatibilityProfile = (
|
||||
config: IdaConfiguration,
|
||||
): IdaCompatibilityProfile =>
|
||||
IDA_COMPATIBILITY_PROFILES[
|
||||
config.protocol ?? (config.mode === "attached" ? "legacy" : "modern")
|
||||
];
|
||||
): IdaCompatibilityProfile => IDA_COMPATIBILITY_PROFILES[config.protocol];
|
||||
|
||||
/** Read one explicit registration without starting or installing a provider. */
|
||||
export const readIdaConfiguration = (
|
||||
|
||||
@@ -127,6 +127,7 @@ const session = async (
|
||||
args: [],
|
||||
env: {},
|
||||
mode,
|
||||
protocol: mode === "attached" ? "legacy" : "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ describe("IDA diagnostic credential redaction", () => {
|
||||
url: "http://localhost:1234/mcp",
|
||||
headers: { Authorization: "Bearer fixture-secret" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
}).url;
|
||||
expect(
|
||||
@@ -25,6 +26,7 @@ describe("IDA diagnostic credential redaction", () => {
|
||||
headers: { "X-Private": "café secret" },
|
||||
sensitive: { headers: ["X-Private"], env: [] },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
}).url;
|
||||
expect(
|
||||
@@ -46,6 +48,7 @@ describe("IDA diagnostic credential redaction", () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: Object.fromEntries(reversed ? entries.reverse() : entries),
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
}).text;
|
||||
expect(
|
||||
@@ -66,6 +69,7 @@ describe("IDA diagnostic credential redaction", () => {
|
||||
CoOkIe: " session=cookie-secret\t",
|
||||
},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
}).text;
|
||||
expect(
|
||||
@@ -85,6 +89,7 @@ describe("IDA diagnostic credential redaction", () => {
|
||||
Cookie: "\t",
|
||||
},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
}).text;
|
||||
const message = "Failed at /caller/input \t with ordinary whitespace";
|
||||
@@ -98,6 +103,7 @@ describe("IDA diagnostic credential redaction", () => {
|
||||
headers: { [name]: "\t" },
|
||||
sensitive: { headers: [name.toLowerCase()], env: [] },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
}).text;
|
||||
expect(redact("input\tvalue")).toBe(
|
||||
@@ -110,6 +116,7 @@ describe("IDA diagnostic credential redaction", () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: "Bearer alpha", Cookie: "redacted" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
}).text;
|
||||
expect(redact("alpha redacted")).toBe("␟ ␟");
|
||||
@@ -192,6 +199,7 @@ it.each(credentialCases)(
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers,
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
}).text;
|
||||
expect(redact(message)).toBe(expected);
|
||||
@@ -204,6 +212,7 @@ it("redacts full configured malformed headers without guessing their internal va
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: header },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
expect(redactor.text(header)).toBe("[redacted transport credential]");
|
||||
@@ -218,6 +227,7 @@ const redactorFor = (header: string, name = "Authorization") =>
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { [name]: header },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
|
||||
@@ -406,6 +416,7 @@ it("redacts protocol-decoded Basic credentials and password without treating ord
|
||||
const redactor = createIdaDiagnosticRedactor({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from("user:secret").toString("base64")}`,
|
||||
@@ -425,6 +436,7 @@ it.each(["text", "json"] as const)(
|
||||
const redactor = createIdaDiagnosticRedactor({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
headers: { Cookie: "a=Q" },
|
||||
});
|
||||
@@ -446,6 +458,7 @@ it.each(["text", "json"] as const)(
|
||||
const redactor = createIdaDiagnosticRedactor({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
headers,
|
||||
});
|
||||
@@ -463,6 +476,7 @@ it.each(["dXNlcjpzZWNyZXQ=", "dXNlcjpzZWNyZXQ", "dXNlcjpzZWNyZXQ=="])(
|
||||
const redactor = createIdaDiagnosticRedactor({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
headers: { Authorization: `Basic ${payload}` },
|
||||
});
|
||||
@@ -487,6 +501,7 @@ describe("overlapping IDA credential source spans", () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: Object.fromEntries(reversed ? entries.reverse() : entries),
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
const result = redactor.text(
|
||||
@@ -502,6 +517,7 @@ describe("overlapping IDA credential source spans", () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: "Custom abc", Cookie: "a=cde; b=def" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
expect(redactor.text("prefix abcdefg suffix")).toBe(
|
||||
@@ -537,6 +553,7 @@ describe("credential-safe redaction markers", () => {
|
||||
const redactor = createIdaDiagnosticRedactor({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
headers: { Authorization: "Custom " + secret, Cookie: "a=X" },
|
||||
});
|
||||
@@ -553,6 +570,7 @@ describe("credential-safe redaction markers", () => {
|
||||
const redactor = createIdaDiagnosticRedactor({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
headers: {
|
||||
Authorization:
|
||||
@@ -574,6 +592,7 @@ describe("unclassified diagnostic blocks and message ownership", () => {
|
||||
const redactor = createIdaDiagnosticRedactor({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
headers,
|
||||
});
|
||||
@@ -612,6 +631,7 @@ it("redacts explicitly marked registration values without guessing from environm
|
||||
env: { PRIVATE_VALUE: "marked-secret", IDADIR: "/caller/ida" },
|
||||
sensitive: { env: ["PRIVATE_VALUE"], headers: [] },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
expect(environment.text("marked-secret /caller/ida")).toBe(
|
||||
@@ -622,6 +642,7 @@ it("redacts explicitly marked registration values without guessing from environm
|
||||
headers: { "X-Custom": "marked-header", "X-Input": "retained" },
|
||||
sensitive: { headers: ["x-custom"], env: [] },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
expect(headers.text("marked-header retained")).toBe(
|
||||
|
||||
@@ -30,6 +30,7 @@ const forCredential = (credential: string) =>
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: `Custom ${credential}` },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
|
||||
@@ -179,6 +180,7 @@ describe("IDA JSON diagnostic source preservation", () => {
|
||||
headers: {},
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
expect(redactor.json(source)).toBe(source);
|
||||
@@ -279,6 +281,7 @@ describe("overlapping raw and decoded JSON credential spans", () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: "Custom X]", Cookie: "a=]SECRET" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
it.each([
|
||||
@@ -303,6 +306,7 @@ describe("overlapping raw and decoded JSON credential spans", () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: "Basic " + encoded, Cookie: "a=]SECRET" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
expect(
|
||||
|
||||
@@ -23,6 +23,7 @@ describe("upstream IDA tool result decoding", () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: "Bearer fixture-secret" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
const evidence = "Caller-selected fixture-secret evidence";
|
||||
@@ -54,6 +55,7 @@ describe("upstream IDA tool result decoding", () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: "Bearer fixture-secret" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
expect(() =>
|
||||
@@ -181,6 +183,7 @@ const failingHttpConnection = (
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: "Custom " + credential },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
@@ -282,6 +285,7 @@ it("preserves the SDK request timeout as a typed IDA timeout", async () => {
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: { Authorization: "Bearer fixture-secret" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs,
|
||||
},
|
||||
{},
|
||||
@@ -348,6 +352,7 @@ it("bounds a stalled health poll by the remaining readiness deadline", async ()
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs,
|
||||
},
|
||||
{},
|
||||
@@ -501,6 +506,7 @@ const withIdaResource = async (
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
@@ -564,6 +570,7 @@ it("owns unclassified truncated-output hints separately from their context", ()
|
||||
const diagnostic = createIdaDiagnosticRedactor({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
headers: { Authorization: 'Custom q"uote' },
|
||||
});
|
||||
@@ -666,6 +673,7 @@ it("uses the actual SDK abort outcome to preserve the caller reason", async () =
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
@@ -722,6 +730,7 @@ it("keeps a real SDK physical failure when abort arrives during its outer unwind
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
@@ -866,6 +875,7 @@ const cleanupConnection = () =>
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
@@ -1112,6 +1122,7 @@ it.each(["completed", "pending", "rejected"] as const)(
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 30,
|
||||
},
|
||||
{},
|
||||
|
||||
@@ -638,7 +638,7 @@ export const createIdaMcpConnection = (
|
||||
if (cause instanceof AnalysisCancelledError) throw cause;
|
||||
return transportFailure(cause, name, requestTimeoutMs);
|
||||
}),
|
||||
config.protocol,
|
||||
config.protocol === "native" ? "native" : undefined,
|
||||
redactor,
|
||||
options?.reply,
|
||||
);
|
||||
|
||||
@@ -61,6 +61,7 @@ input.on('line', (line) => {
|
||||
args: [script],
|
||||
env: registrationEnvironment,
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 5000,
|
||||
},
|
||||
selected,
|
||||
|
||||
@@ -127,7 +127,11 @@ describe("native IDA attached protocol", () => {
|
||||
const path = join(root, "mutable-registration.json");
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "first-server", mode: "attached" }),
|
||||
JSON.stringify({
|
||||
command: "first-server",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
}),
|
||||
);
|
||||
const config = parseConfig({ REA_IDA_MCP_CONFIG: path });
|
||||
if (!config.ok) throw config.error;
|
||||
@@ -169,7 +173,10 @@ describe("native IDA attached protocol", () => {
|
||||
it("does not reuse a registration across independent scopes sharing one signal", async () => {
|
||||
const { root, target } = await fixture();
|
||||
const path = join(root, "independent-registration.json");
|
||||
await writeFile(path, JSON.stringify({ command: "first-server" }));
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "first-server", protocol: "legacy" }),
|
||||
);
|
||||
const config = parseConfig({ REA_IDA_MCP_CONFIG: path });
|
||||
if (!config.ok) throw config.error;
|
||||
const provider = new IdaProvider(config.value, {});
|
||||
@@ -187,7 +194,10 @@ describe("native IDA attached protocol", () => {
|
||||
resourceScope,
|
||||
});
|
||||
if (!firstProfile.ok) throw firstProfile.error;
|
||||
await writeFile(path, JSON.stringify({ command: "second-server" }));
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "second-server", protocol: "legacy" }),
|
||||
);
|
||||
const secondScope = new ProviderInspectionScope();
|
||||
const second = await provider.inspectReadinessSnapshot({
|
||||
signal: controller.signal,
|
||||
@@ -232,7 +242,10 @@ describe("IDA selected registration factories", () => {
|
||||
}[] = [];
|
||||
const signal = new AbortController().signal;
|
||||
for (let index = 0; index < 40; index += 1) {
|
||||
await writeFile(path, JSON.stringify({ command: `server-${index}` }));
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: `server-${index}`, protocol: "legacy" }),
|
||||
);
|
||||
const scope = new ProviderInspectionScope();
|
||||
const resolution = await provider.resolveAnalysisProfile(target, {
|
||||
signal,
|
||||
@@ -298,9 +311,12 @@ describe("IDA selected registration factories", () => {
|
||||
expect((await readIdaConfiguration(path, { resourceScope })).ok).toBe(
|
||||
false,
|
||||
);
|
||||
await writeFile(path, JSON.stringify({ command: "python" }));
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "python", protocol: "legacy" }),
|
||||
);
|
||||
const old = await readIdaConfiguration(path, { resourceScope });
|
||||
expect(old.ok && old.value.protocol).toBeUndefined();
|
||||
expect(old.ok && old.value.protocol).toBe("legacy");
|
||||
});
|
||||
|
||||
it("commits native profile semantics and advertises GUI effects without starting a provider", async () => {
|
||||
|
||||
@@ -35,6 +35,7 @@ it("preserves a rejected headless decompile diagnostic with completed lookup evi
|
||||
Cookie: "session=cookie-fixture-secret",
|
||||
},
|
||||
mode: "headless" as const,
|
||||
protocol: "modern" as const,
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
};
|
||||
@@ -141,6 +142,7 @@ describe("headless IDA tool diagnostics in partial evidence", () => {
|
||||
Buffer.from("user:" + password, "utf8").toString("base64"),
|
||||
},
|
||||
mode: "headless" as const,
|
||||
protocol: "modern" as const,
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
};
|
||||
@@ -235,6 +237,7 @@ describe("headless IDA partial operation evidence", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -287,6 +290,7 @@ describe("headless IDA partial operation evidence", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -342,6 +346,7 @@ it("retains the session analysis profile when an operation fails after a complet
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -439,6 +444,7 @@ describe("headless IDA operation failures", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -500,6 +506,7 @@ const missingProcedureFixture = async (
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -641,6 +648,7 @@ describe("headless IDA procedure lookup failures", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -705,6 +713,7 @@ describe("attached IDA procedure lookup failures", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -743,6 +752,7 @@ describe("attached IDA procedure lookup failures", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -81,7 +81,11 @@ it("keeps a selected valid registration distinct from a live IDA connection", as
|
||||
const path = join(root, "registration.json");
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ mode: "attached", url: "http://127.0.0.1:13337/mcp" }),
|
||||
JSON.stringify({
|
||||
mode: "attached",
|
||||
url: "http://127.0.0.1:13337/mcp",
|
||||
protocol: "legacy",
|
||||
}),
|
||||
);
|
||||
const config = parseConfig({ REA_IDA_MCP_CONFIG: path });
|
||||
if (!config.ok) throw config.error;
|
||||
@@ -329,10 +333,12 @@ it.each(["http", "stdio"] as const)(
|
||||
transport === "http"
|
||||
? {
|
||||
url: "http://127.0.0.1:8745/mcp",
|
||||
protocol: "legacy",
|
||||
headers: { Authorization: token, "X-Input": "retained" },
|
||||
}
|
||||
: {
|
||||
command: "fixture",
|
||||
protocol: "legacy",
|
||||
env: { PRIVATE_VALUE: token, IDADIR: "/fixture/ida" },
|
||||
sensitive: { env: ["PRIVATE_VALUE"] },
|
||||
},
|
||||
@@ -360,6 +366,7 @@ it.each(["http", "stdio"] as const)(
|
||||
transport === "http"
|
||||
? {
|
||||
url: "http://127.0.0.1:8745/mcp",
|
||||
protocol: "legacy",
|
||||
headers: {
|
||||
Authorization: "second credential",
|
||||
"X-Input": "changed input",
|
||||
@@ -367,6 +374,7 @@ it.each(["http", "stdio"] as const)(
|
||||
}
|
||||
: {
|
||||
command: "fixture",
|
||||
protocol: "legacy",
|
||||
env: {
|
||||
PRIVATE_VALUE: "second credential",
|
||||
IDADIR: "/changed/ida",
|
||||
@@ -396,7 +404,11 @@ it("applies runtime regex budgets through a bound IDA client and recovers in the
|
||||
try {
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ mode: "attached", url: "http://127.0.0.1:13337/mcp" }),
|
||||
JSON.stringify({
|
||||
mode: "attached",
|
||||
url: "http://127.0.0.1:13337/mcp",
|
||||
protocol: "legacy",
|
||||
}),
|
||||
);
|
||||
const parsed = parseConfig({ REA_IDA_MCP_CONFIG: path });
|
||||
if (!parsed.ok) throw parsed.error;
|
||||
|
||||
@@ -78,6 +78,7 @@ describe("headless IDA reference coverage", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -87,7 +87,7 @@ const fixture = async (
|
||||
args: [],
|
||||
env: {},
|
||||
mode,
|
||||
...(protocol === undefined ? {} : { protocol }),
|
||||
protocol: protocol ?? (mode === "attached" ? "legacy" : "modern"),
|
||||
timeoutMs,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -123,6 +123,7 @@ it.each([
|
||||
url: selected,
|
||||
headers: { Authorization: "Bearer fixture-secret" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
target,
|
||||
@@ -157,6 +158,7 @@ it.each([
|
||||
url: `http://localhost:1234/mcp?token=${encoded}&cursor=keep`,
|
||||
headers: { Authorization: "Bearer fixture-secret" },
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
target,
|
||||
@@ -2323,6 +2325,7 @@ it("keeps the session cleanup owner until an ignored-abort HTTP acquisition and
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "attached" as const,
|
||||
protocol: "legacy" as const,
|
||||
timeoutMs: 30,
|
||||
};
|
||||
const client = new IdaSessionClient(
|
||||
|
||||
@@ -53,6 +53,7 @@ it.each(["timeout", "resource"] as const)(
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -170,6 +171,7 @@ it.each([
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
headers: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ const clientFor = (root: string, target: BinaryTarget) => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -278,6 +278,7 @@ describe("IDA workspace admission identity", () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -38,6 +38,7 @@ for (const encodedCredential of [false, true])
|
||||
registration,
|
||||
JSON.stringify({
|
||||
url: endpoint,
|
||||
protocol: "legacy",
|
||||
headers: { Authorization: "Bearer fixture-secret" },
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -787,6 +787,7 @@ for (const [client, provider] of [
|
||||
idaConfig,
|
||||
JSON.stringify({
|
||||
url: "http://127.0.0.1:12345/mcp",
|
||||
protocol: "legacy",
|
||||
headers: { Authorization: "Bearer fixture-credential" },
|
||||
timeoutMs,
|
||||
}),
|
||||
|
||||
@@ -125,12 +125,20 @@ cliTest.for([
|
||||
})),
|
||||
...[1, 2_147_483_647].map((timeoutMs) => ({
|
||||
name: `valid timeout ${String(timeoutMs)}`,
|
||||
registration: { command: "provider-must-not-launch", timeoutMs },
|
||||
registration: {
|
||||
command: "provider-must-not-launch",
|
||||
protocol: "legacy",
|
||||
timeoutMs,
|
||||
},
|
||||
constraint: undefined,
|
||||
})),
|
||||
{
|
||||
name: "valid registration",
|
||||
registration: { command: "provider-must-not-launch", args: [] },
|
||||
registration: {
|
||||
command: "provider-must-not-launch",
|
||||
args: [],
|
||||
protocol: "legacy",
|
||||
},
|
||||
constraint: undefined,
|
||||
},
|
||||
] as const)(
|
||||
|
||||
@@ -20,6 +20,7 @@ describe("IDA startup diagnostics", () => {
|
||||
],
|
||||
env: launcherEnvironment(),
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
@@ -47,6 +48,7 @@ describe("IDA startup diagnostics", () => {
|
||||
],
|
||||
env: launcherEnvironment(),
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
@@ -76,6 +78,7 @@ it("drops a leading partial UTF-8 character from the bounded stderr tail", async
|
||||
],
|
||||
env: launcherEnvironment(),
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
@@ -103,6 +106,7 @@ it("retains an in-budget undecodable leading byte without claiming tail truncati
|
||||
],
|
||||
env: launcherEnvironment(),
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
{},
|
||||
|
||||
@@ -38,6 +38,7 @@ it.each([2, 3])(
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -64,7 +64,11 @@ it("advertises attached IDA UI and network effects in SDK and session discovery"
|
||||
const registration = join(root, "registration.json");
|
||||
await writeFile(
|
||||
registration,
|
||||
JSON.stringify({ command: "fixture", mode: "attached" }),
|
||||
JSON.stringify({
|
||||
command: "fixture",
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
}),
|
||||
);
|
||||
const config = parseConfig({ REA_IDA_MCP_CONFIG: registration });
|
||||
if (!config.ok) throw config.error;
|
||||
|
||||
@@ -184,7 +184,7 @@ const fixture = async (options: IdaTransportFixtureOptions = {}) => {
|
||||
url: `http://127.0.0.1:${address.port}/mcp`,
|
||||
headers: configurationHeaders,
|
||||
mode: native ? "attached" : "headless",
|
||||
...(native ? { protocol: "native" as const } : {}),
|
||||
protocol: native ? "native" : "modern",
|
||||
timeoutMs: 2000,
|
||||
},
|
||||
{},
|
||||
@@ -300,6 +300,7 @@ process.stdin.on("data", (chunk) => {
|
||||
args: ["-e", script],
|
||||
env: {},
|
||||
mode: "attached",
|
||||
protocol: "legacy",
|
||||
timeoutMs: 10_000,
|
||||
},
|
||||
{},
|
||||
|
||||
@@ -156,6 +156,7 @@ it("uses the batch through a modern session and preserves the observed callee Ev
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@ const session = async (cappedReferences = false) => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -68,6 +68,7 @@ const session = async (
|
||||
args: [],
|
||||
env: {},
|
||||
mode,
|
||||
protocol: mode === "attached" ? "legacy" : "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -92,6 +92,7 @@ describe("retained headless IDA connection in a long-lived host", () => {
|
||||
args: [supervisor],
|
||||
env: {},
|
||||
mode: "headless" as const,
|
||||
protocol: "modern" as const,
|
||||
timeoutMs: 5000,
|
||||
workspaceRoot,
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ const session = async () => {
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -27,7 +27,7 @@ it.each([
|
||||
JSON.stringify({
|
||||
url: "http://127.0.0.1:13337/mcp",
|
||||
mode: protocol === "modern" ? "headless" : "attached",
|
||||
...(protocol === "native" ? { protocol } : {}),
|
||||
...(protocol === "modern" ? {} : { protocol }),
|
||||
}),
|
||||
);
|
||||
// The absent target stops accepted option sets at admission, before any provider call.
|
||||
|
||||
@@ -33,7 +33,10 @@ const reobservesExternallyMutableState = async (): Promise<void> => {
|
||||
const { root, target } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "registration.json");
|
||||
await writeFile(path, JSON.stringify({ command: "fixture" }));
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "fixture", protocol: "legacy" }),
|
||||
);
|
||||
const config = parseConfig({ REA_IDA_MCP_CONFIG: path });
|
||||
if (!config.ok) throw config.error;
|
||||
const producer = new RecordingIdaMcp(target);
|
||||
@@ -197,7 +200,10 @@ describe("IDA provider selection cancellation", () => {
|
||||
const second = await createIdaTarget();
|
||||
roots.push(first.root, second.root);
|
||||
const path = join(first.root, "registration.json");
|
||||
await writeFile(path, JSON.stringify({ command: "first-server" }));
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "first-server", protocol: "legacy" }),
|
||||
);
|
||||
const config = parseConfig({ REA_IDA_MCP_CONFIG: path });
|
||||
if (!config.ok) throw config.error;
|
||||
const factorySelections: string[] = [];
|
||||
@@ -226,7 +232,10 @@ describe("IDA provider selection cancellation", () => {
|
||||
(await session.execute("procedure_pseudo_code", { procedure: "main" }))
|
||||
.ok,
|
||||
).toBe(true);
|
||||
await writeFile(path, JSON.stringify({ command: "replacement-server" }));
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "replacement-server", protocol: "legacy" }),
|
||||
);
|
||||
cancelWhenPreviousCloses = true;
|
||||
const replacement = await session.open(second.target.path, {
|
||||
signal: controller.signal,
|
||||
@@ -316,7 +325,10 @@ describe("IDA universal Mach-O target identity", () => {
|
||||
};
|
||||
|
||||
it.each([
|
||||
{ profile: "legacy attached", registration: { command: "fixture" } },
|
||||
{
|
||||
profile: "legacy attached",
|
||||
registration: { command: "fixture", protocol: "legacy" },
|
||||
},
|
||||
{
|
||||
profile: "native attached",
|
||||
registration: {
|
||||
@@ -363,7 +375,10 @@ describe("IDA binary_overview execution", () => {
|
||||
const { root, target } = await createIdaTarget();
|
||||
roots.push(root);
|
||||
const path = join(root, "registration.json");
|
||||
await writeFile(path, JSON.stringify({ command: "fixture" }));
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({ command: "fixture", protocol: "legacy" }),
|
||||
);
|
||||
const config = parseConfig({ REA_IDA_MCP_CONFIG: path });
|
||||
if (!config.ok) throw config.error;
|
||||
const producer = new RecordingIdaMcp(target);
|
||||
|
||||
@@ -63,6 +63,7 @@ describe.each(["attached", "headless"] as const)(
|
||||
args: [],
|
||||
env: {},
|
||||
mode,
|
||||
protocol: mode === "attached" ? "legacy" : "modern",
|
||||
workspaceRoot: root,
|
||||
timeoutMs: 1000,
|
||||
},
|
||||
|
||||
Vendored
+1
@@ -128,6 +128,7 @@ export const callGraphSession = async (
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
@@ -279,6 +279,7 @@ it("projects retained workspace descriptors through the real IDA session and cle
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
@@ -375,6 +376,7 @@ it("keeps shared IDA startup owned when the caller leaves during source finaliza
|
||||
args: [],
|
||||
env: {},
|
||||
mode: "headless",
|
||||
protocol: "modern",
|
||||
timeoutMs: 1000,
|
||||
workspaceRoot: root,
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user