diff --git a/docs/providers/ida-provider.md b/docs/providers/ida-provider.md index e2dfbf0f3c..de117bd135 100644 --- a/docs/providers/ida-provider.md +++ b/docs/providers/ida-provider.md @@ -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", diff --git a/src/doctorRuntime.test.ts b/src/doctorRuntime.test.ts index cba0c3febc..f61cd9a187 100644 --- a/src/doctorRuntime.test.ts +++ b/src/doctorRuntime.test.ts @@ -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 { diff --git a/src/ida/IdaCalleeRelationships.test.ts b/src/ida/IdaCalleeRelationships.test.ts index 9fddc44125..3230fffd28 100644 --- a/src/ida/IdaCalleeRelationships.test.ts +++ b/src/ida/IdaCalleeRelationships.test.ts @@ -227,6 +227,7 @@ describe("headless IDA callee edges on x86 targets", () => { args: [], env: {}, mode: "attached", + protocol: "legacy", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/src/ida/IdaConfiguration.test.ts b/src/ida/IdaConfiguration.test.ts index 0b537f08c2..46459057fd 100644 --- a/src/ida/IdaConfiguration.test.ts +++ b/src/ida/IdaConfiguration.test.ts @@ -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 }[] = []; 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 }[] = []; 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 }[] = []; 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); +}); diff --git a/src/ida/IdaConfiguration.ts b/src/ida/IdaConfiguration.ts index 0b399e3c43..6df4066984 100644 --- a/src/ida/IdaConfiguration.ts +++ b/src/ida/IdaConfiguration.ts @@ -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; @@ -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 = ( diff --git a/src/ida/IdaContainment.test.ts b/src/ida/IdaContainment.test.ts index 2c43e0c450..18bcdd0ed3 100644 --- a/src/ida/IdaContainment.test.ts +++ b/src/ida/IdaContainment.test.ts @@ -127,6 +127,7 @@ const session = async ( args: [], env: {}, mode, + protocol: mode === "attached" ? "legacy" : "modern", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/src/ida/IdaDiagnosticRedactor.test.ts b/src/ida/IdaDiagnosticRedactor.test.ts index 1574413b50..60fba9733b 100644 --- a/src/ida/IdaDiagnosticRedactor.test.ts +++ b/src/ida/IdaDiagnosticRedactor.test.ts @@ -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( diff --git a/src/ida/IdaJsonDiagnostic.test.ts b/src/ida/IdaJsonDiagnostic.test.ts index 3929551d33..c3a9d084a0 100644 --- a/src/ida/IdaJsonDiagnostic.test.ts +++ b/src/ida/IdaJsonDiagnostic.test.ts @@ -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( diff --git a/src/ida/IdaMcpConnection.test.ts b/src/ida/IdaMcpConnection.test.ts index 6b3ee2a8ac..a9b2e06bc5 100644 --- a/src/ida/IdaMcpConnection.test.ts +++ b/src/ida/IdaMcpConnection.test.ts @@ -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, }, {}, diff --git a/src/ida/IdaMcpConnection.ts b/src/ida/IdaMcpConnection.ts index a0d0f8855f..ecf80415a1 100644 --- a/src/ida/IdaMcpConnection.ts +++ b/src/ida/IdaMcpConnection.ts @@ -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, ); diff --git a/src/ida/IdaMcpEnvironment.test.ts b/src/ida/IdaMcpEnvironment.test.ts index 5e7a746d0c..15a66af875 100644 --- a/src/ida/IdaMcpEnvironment.test.ts +++ b/src/ida/IdaMcpEnvironment.test.ts @@ -61,6 +61,7 @@ input.on('line', (line) => { args: [script], env: registrationEnvironment, mode: "attached", + protocol: "legacy", timeoutMs: 5000, }, selected, diff --git a/src/ida/IdaNativeProtocol.test.ts b/src/ida/IdaNativeProtocol.test.ts index abae786f44..d474abe5e0 100644 --- a/src/ida/IdaNativeProtocol.test.ts +++ b/src/ida/IdaNativeProtocol.test.ts @@ -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 () => { diff --git a/src/ida/IdaOperationFailures.test.ts b/src/ida/IdaOperationFailures.test.ts index e107c4b622..dd2712ddcb 100644 --- a/src/ida/IdaOperationFailures.test.ts +++ b/src/ida/IdaOperationFailures.test.ts @@ -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, }, diff --git a/src/ida/IdaProvider.test.ts b/src/ida/IdaProvider.test.ts index bccd4d26c6..694e886b6a 100644 --- a/src/ida/IdaProvider.test.ts +++ b/src/ida/IdaProvider.test.ts @@ -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; diff --git a/src/ida/IdaReferenceCoverage.test.ts b/src/ida/IdaReferenceCoverage.test.ts index 18fcf2beb5..7859f4ffa7 100644 --- a/src/ida/IdaReferenceCoverage.test.ts +++ b/src/ida/IdaReferenceCoverage.test.ts @@ -78,6 +78,7 @@ describe("headless IDA reference coverage", () => { args: [], env: {}, mode: "attached", + protocol: "legacy", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/src/ida/IdaSessionClient.test.ts b/src/ida/IdaSessionClient.test.ts index d0a15d47a6..86ef28d0c7 100644 --- a/src/ida/IdaSessionClient.test.ts +++ b/src/ida/IdaSessionClient.test.ts @@ -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( diff --git a/src/ida/IdaStartupFailures.test.ts b/src/ida/IdaStartupFailures.test.ts index f7be07f3f2..1d56a62817 100644 --- a/src/ida/IdaStartupFailures.test.ts +++ b/src/ida/IdaStartupFailures.test.ts @@ -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, }, diff --git a/src/ida/IdaWorkspaceOwnership.test.ts b/src/ida/IdaWorkspaceOwnership.test.ts index 7478abbfe3..5b48d126bc 100644 --- a/src/ida/IdaWorkspaceOwnership.test.ts +++ b/src/ida/IdaWorkspaceOwnership.test.ts @@ -35,6 +35,7 @@ const clientFor = (root: string, target: BinaryTarget) => { args: [], env: {}, mode: "headless", + protocol: "modern", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/src/ida/IdaWorkspaceThinning.test.ts b/src/ida/IdaWorkspaceThinning.test.ts index cafb2eb857..99f91929d2 100644 --- a/src/ida/IdaWorkspaceThinning.test.ts +++ b/src/ida/IdaWorkspaceThinning.test.ts @@ -278,6 +278,7 @@ describe("IDA workspace admission identity", () => { args: [], env: {}, mode: "headless", + protocol: "modern", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/tests/acceptance/providers/idaEndpointFailure.test.ts b/tests/acceptance/providers/idaEndpointFailure.test.ts index 3fb8e2b90c..667fc795fe 100644 --- a/tests/acceptance/providers/idaEndpointFailure.test.ts +++ b/tests/acceptance/providers/idaEndpointFailure.test.ts @@ -38,6 +38,7 @@ for (const encodedCredential of [false, true]) registration, JSON.stringify({ url: endpoint, + protocol: "legacy", headers: { Authorization: "Bearer fixture-secret" }, }), ); diff --git a/tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts b/tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts index e9f2a777a0..d06eaa754b 100644 --- a/tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts +++ b/tests/acceptance/setup/registrationRuntimeEnvironmentCli.test.ts @@ -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, }), diff --git a/tests/boundary/cli/idaDoctorRegistration.test.ts b/tests/boundary/cli/idaDoctorRegistration.test.ts index 856449d2a4..9f5ff8c1b9 100644 --- a/tests/boundary/cli/idaDoctorRegistration.test.ts +++ b/tests/boundary/cli/idaDoctorRegistration.test.ts @@ -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)( diff --git a/tests/boundary/idaStartupDiagnostics.test.ts b/tests/boundary/idaStartupDiagnostics.test.ts index 9776caf662..cd69873abd 100644 --- a/tests/boundary/idaStartupDiagnostics.test.ts +++ b/tests/boundary/idaStartupDiagnostics.test.ts @@ -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, }, {}, diff --git a/tests/boundary/mcp/idaReadinessFeatureTrace.test.ts b/tests/boundary/mcp/idaReadinessFeatureTrace.test.ts index 430b29d2f3..4444a01b22 100644 --- a/tests/boundary/mcp/idaReadinessFeatureTrace.test.ts +++ b/tests/boundary/mcp/idaReadinessFeatureTrace.test.ts @@ -38,6 +38,7 @@ it.each([2, 3])( args: [], env: {}, mode: "headless", + protocol: "modern", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/tests/boundary/mcp/idaToolEffects.test.ts b/tests/boundary/mcp/idaToolEffects.test.ts index 2d514f56d2..6169e4ae92 100644 --- a/tests/boundary/mcp/idaToolEffects.test.ts +++ b/tests/boundary/mcp/idaToolEffects.test.ts @@ -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; diff --git a/tests/boundary/mcp/idaTransport.test.ts b/tests/boundary/mcp/idaTransport.test.ts index 73e8684aac..08c2830434 100644 --- a/tests/boundary/mcp/idaTransport.test.ts +++ b/tests/boundary/mcp/idaTransport.test.ts @@ -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, }, {}, diff --git a/tests/boundary/providers/ida/calleeLookupBatch.test.ts b/tests/boundary/providers/ida/calleeLookupBatch.test.ts index ac078e2faf..d56817b5ba 100644 --- a/tests/boundary/providers/ida/calleeLookupBatch.test.ts +++ b/tests/boundary/providers/ida/calleeLookupBatch.test.ts @@ -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, }, diff --git a/tests/boundary/providers/ida/containment.test.ts b/tests/boundary/providers/ida/containment.test.ts index 75eb78f424..4013526095 100644 --- a/tests/boundary/providers/ida/containment.test.ts +++ b/tests/boundary/providers/ida/containment.test.ts @@ -39,6 +39,7 @@ const session = async (cappedReferences = false) => { args: [], env: {}, mode: "headless", + protocol: "modern", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/tests/boundary/providers/ida/functionIdentity.test.ts b/tests/boundary/providers/ida/functionIdentity.test.ts index 237f883619..307ab4afe9 100644 --- a/tests/boundary/providers/ida/functionIdentity.test.ts +++ b/tests/boundary/providers/ida/functionIdentity.test.ts @@ -68,6 +68,7 @@ const session = async ( args: [], env: {}, mode, + protocol: mode === "attached" ? "legacy" : "modern", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/tests/boundary/providers/ida/retainedConnectionRetry.test.ts b/tests/boundary/providers/ida/retainedConnectionRetry.test.ts index 4f90928424..034657701d 100644 --- a/tests/boundary/providers/ida/retainedConnectionRetry.test.ts +++ b/tests/boundary/providers/ida/retainedConnectionRetry.test.ts @@ -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, }; diff --git a/tests/boundary/providers/ida/selectorProvenance.test.ts b/tests/boundary/providers/ida/selectorProvenance.test.ts index 40e92ae1cb..4e3818bfac 100644 --- a/tests/boundary/providers/ida/selectorProvenance.test.ts +++ b/tests/boundary/providers/ida/selectorProvenance.test.ts @@ -38,6 +38,7 @@ const session = async () => { args: [], env: {}, mode: "headless", + protocol: "modern", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/tests/boundary/providers/ida/verifierRequirements.test.ts b/tests/boundary/providers/ida/verifierRequirements.test.ts index c7e7e32fbf..143ca97f39 100644 --- a/tests/boundary/providers/ida/verifierRequirements.test.ts +++ b/tests/boundary/providers/ida/verifierRequirements.test.ts @@ -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. diff --git a/tests/composition/idaProvider.test.ts b/tests/composition/idaProvider.test.ts index eda322f510..4a38c64315 100644 --- a/tests/composition/idaProvider.test.ts +++ b/tests/composition/idaProvider.test.ts @@ -33,7 +33,10 @@ const reobservesExternallyMutableState = async (): Promise => { 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); diff --git a/tests/conformance/ida/providerContracts.test.ts b/tests/conformance/ida/providerContracts.test.ts index 0e21b03dec..77e8287eee 100644 --- a/tests/conformance/ida/providerContracts.test.ts +++ b/tests/conformance/ida/providerContracts.test.ts @@ -63,6 +63,7 @@ describe.each(["attached", "headless"] as const)( args: [], env: {}, mode, + protocol: mode === "attached" ? "legacy" : "modern", workspaceRoot: root, timeoutMs: 1000, }, diff --git a/tests/fixtures/idaCallGraph.ts b/tests/fixtures/idaCallGraph.ts index b8e44d28ea..46ec621bab 100644 --- a/tests/fixtures/idaCallGraph.ts +++ b/tests/fixtures/idaCallGraph.ts @@ -128,6 +128,7 @@ export const callGraphSession = async ( args: [], env: {}, mode: "headless", + protocol: "modern", timeoutMs: 1000, workspaceRoot: root, }, diff --git a/tests/process-global/idaWorkspaceCleanup.test.ts b/tests/process-global/idaWorkspaceCleanup.test.ts index ce6f9c5ee4..c6aa21714d 100644 --- a/tests/process-global/idaWorkspaceCleanup.test.ts +++ b/tests/process-global/idaWorkspaceCleanup.test.ts @@ -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, },