[GH-ISSUE #5823] [Bug] Native IDA rejects valid metadata replies with omitted or variant JSON media types #3522

Closed
opened 2026-10-31 00:41:27 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 21, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5823

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

A local fixture using REA's production MCP SDK transport and IDA session client reproduced a metadata-read failure. With mode: "attached" and protocol: "native", both health and procedure pseudocode succeeded when the resource declared mimeType: "application/json". Changing only that declaration to "application/json; charset=utf-8" or "Application/JSON", or omitting it, made both operations fail with unreadable_output at contents.0.mimeType.

All four resource replies passed the pinned SDK's ReadResourceResultSchema. Each contained the same valid JSON metadata, resource URI and matching input hash. The fixture advertised the required native tool catalog; rejection happened before any analysis tool call.

This is an executed synthetic-provider reproduction. No IDA engine or real native plugin was run, and no public CLI invocation was executed.

Reproduction

From the source checkout, with its locked dependencies installed:

  1. Save the following temporary test as src/ida/repro.test.ts.
  2. Run npm run test:local -- src/ida/repro.test.ts.
  3. Observe the bare-type control succeeding and the other three cases returning unreadable_output. The assertions encode the current failure, so a passing test confirms this report. Delete the test afterward.

The test uses the existing target and producer fixtures, an ephemeral loopback HTTP endpoint, and the production connection/session implementation. It was run once successfully with all four table cases after correcting an incomplete lookup response in the initial test fixture.

import { createServer } from "node:http";
import { rm } from "node:fs/promises";
import { it, expect } from "vitest";
import { ReadResourceResultSchema } from "@modelcontextprotocol/core";
import { createIdaTarget, RecordingIdaMcp } from "../../tests/fixtures/idaMcp.js";
import { createIdaMcpConnection } from "./IdaMcpConnection.js";
import { IdaSessionClient } from "./IdaSessionClient.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";

it.each(["application/json", undefined, "application/json; charset=utf-8", "Application/JSON"])(
  "native metadata media type: %s", async (mimeType) => {
    const { root, target } = await createIdaTarget();
    const methods: string[] = [];
    const producer = new RecordingIdaMcp(target);
    producer.overrideCall = (name) => name === "decompile" ? { addr: "0x1000", ok: true, pseudocode: "int main(void) { return 0; }", lines: 1 } : undefined;
    const resource = { contents: [{
      uri: "ida://idb/metadata",
      ...(mimeType === undefined ? {} : { mimeType }),
      text: JSON.stringify({ path: "fixture.i64", sha256: target.sha256, base: "0x1000", module: "fixture" }),
    }] };
    expect(ReadResourceResultSchema.safeParse(resource).success).toBe(true);
    const server = createServer(async (request, response) => {
      if (request.method !== "POST") { response.writeHead(405).end(); return; }
      let body = "";
      for await (const chunk of request) body += String(chunk);
      const input = JSON.parse(body);
      methods.push(input.method);
      if (input.id === undefined) { response.writeHead(202).end(); return; }
      const result = input.method === "initialize"
        ? { protocolVersion: input.params.protocolVersion, capabilities: { tools: {}, resources: {} }, serverInfo: { name: "fixture", version: "1" } }
        : input.method === "tools/list"
          ? { tools: ["list_funcs", "lookup_funcs", "find_regex", "decompile", "disasm", "callees", "xrefs_to"].map((name) => ({ name, inputSchema: { type: "object", properties: {} } })) }
          : input.method === "resources/read"
            ? resource
            : { content: [{ type: "text", text: JSON.stringify(await producer.call(input.params.name, input.params.arguments ?? {})) }] };
      response.writeHead(200, { "Content-Type": "application/json" });
      response.end(JSON.stringify({ jsonrpc: "2.0", id: input.id, result }));
    });
    await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
    const address = server.address();
    if (!address || typeof address === "string") throw new Error("Missing port");
    const config = { url: `http://127.0.0.1:${address.port}/mcp`, headers: {}, mode: "attached" as const, protocol: "native" as const, timeoutMs: 2000 };
    const connection = createIdaMcpConnection(config, {});
    const client = new IdaSessionClient(config, target, connection);
    try {
      for (const operation of ["health", "procedure_pseudo_code"] as const) {
        const result = await client.execute(operation, operation === "health" ? {} : { procedure: "0x1000" });
        if (mimeType === "application/json") {
          expect(result.ok).toBe(true);
          console.log(JSON.stringify({ mimeType, operation, ok: result.ok }));
        } else {
          expect(result.ok).toBe(false);
          if (result.ok) throw new Error("Expected failure");
          const error = projectAnalysisError(result.error);
          expect(error.code).toBe("unreadable_output");
          expect(result.error.message).toContain("contents.0.mimeType");
          console.log(JSON.stringify({ mimeType: mimeType ?? "(omitted)", operation, ok: result.ok, code: error.code, reason: result.error.message }));
        }
      }
      expect(methods).toContain("resources/read");
      expect(methods.includes("tools/call")).toBe(mimeType === "application/json");
    } finally {
      expect((await client.close()).ok).toBe(true);
      await new Promise<void>((resolve, reject) => {
        server.close((error) => error ? reject(error) : resolve());
        server.closeAllConnections();
      });
      await rm(root, { recursive: true, force: true });
    }
  },
);

Expected behavior

A JSON metadata resource with the required recorded input identity should survive omission of the optional resource mimeType, and ordinary parameter/case variants of the JSON media type should not invalidate an otherwise identical observation. Keep JSON parsing and target-identity checks strict.

The documented native profile requires resources/read for ida://idb/metadata and the recorded SHA-256; it does not require an exact media-type spelling. REA's boundary invariants require preserving omission/uncertainty and format-aware normalization. The reproduction independently checks the optional-field contract against the installed, pinned SDK schema.

This report does not establish a requirement to accept arbitrary non-JSON types or every +json resource type.

Impact and scope

P2: a bounded compatibility failure prevents metadata observation and the requested pseudocode operation for these resource declarations. All three variants failed both exercised operations; the control succeeded at both. Session close succeeded in every case, and the target was not executed or modified.

Source tracing shows that other admitted native operations also begin with the same metadata read. Operations rejected by earlier input or capability checks are outside that statement. Legacy and headless modes take different metadata paths.

Real-producer frequency is unknown: this does not establish that a released native plugin emits any affected variant. Returning bare application/json worked in the fixture; whether affected users can change their producer's declaration is unverified.

REA version

rea-agents 6.7.0; source revision a5f982de8d

Runtime and platform

Node.js v24.22.0; macOS 27.0.1 arm64; Vitest 5.0.3; pinned MCP SDK 2.3.1; synthetic HTTP producer, no IDA engine.

Target and artifact context

Repository-generated x86_64 ELF fixture with matching metadata SHA-256; attached native profile over a local Streamable HTTP fixture.

Interface

Other

Earliest failure stage

Transport or result projection

Relevant output or evidence

Trimmed output from the final focused run:

{"mimeType":"application/json","operation":"health","ok":true}
{"mimeType":"application/json","operation":"procedure_pseudo_code","ok":true}
{"mimeType":"(omitted)","operation":"health","ok":false,"code":"unreadable_output","reason":"Invalid analysis output for health: IDA MCP returned malformed or unsupported producer data: contents.0.mimeType: Invalid input: expected \"application/json\""}
{"mimeType":"application/json; charset=utf-8","operation":"health","ok":false,"code":"unreadable_output","reason":"Invalid analysis output for health: IDA MCP returned malformed or unsupported producer data: contents.0.mimeType: Invalid input: expected \"application/json\""}
{"mimeType":"Application/JSON","operation":"health","ok":false,"code":"unreadable_output","reason":"Invalid analysis output for health: IDA MCP returned malformed or unsupported producer data: contents.0.mimeType: Invalid input: expected \"application/json\""}
Test Files  1 passed (1)
     Tests  4 passed (4)

Each non-control case also produced unreadable_output for procedure_pseudo_code, as asserted in the reproduction. Every SDK-schema acceptance and successful-close assertion passed.

The resource validator adds mimeType: z.literal("application/json") after the SDK read. The session observation path invokes that resource before parsing its identity. The operation path observes metadata before health or analysis dispatch, and the error conversion wraps this Zod failure as an analysis-output error.

Supported-entry reachability was checked in source: the decompile command requests procedure_pseudo_code, and the selected provider constructs this session with its production connection.

Typed outcome or recovery expected

Return the normal metadata-backed health or analysis result for these otherwise unchanged replies. Retain strict failure for malformed payloads and mismatched target identity. The internal error already identifies contents.0.mimeType; the defect is refusing these resource representations, rather than a separate claim that the schema failure has no explanation.

Analysis or suspected cause

Source-derived: the second validation compares the media-type declaration as a required literal string. It rejects the optional-field representation accepted by the pinned SDK and does not normalize the declared JSON essence. Correcting this boundary need not change analysis, database identity or lifecycle authority.

Additional context

No response

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5823 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior A local fixture using REA's production MCP SDK transport and IDA session client reproduced a metadata-read failure. With `mode: "attached"` and `protocol: "native"`, both health and procedure pseudocode succeeded when the resource declared `mimeType: "application/json"`. Changing only that declaration to `"application/json; charset=utf-8"` or `"Application/JSON"`, or omitting it, made both operations fail with `unreadable_output` at `contents.0.mimeType`. All four resource replies passed the pinned SDK's `ReadResourceResultSchema`. Each contained the same valid JSON metadata, resource URI and matching input hash. The fixture advertised the required native tool catalog; rejection happened before any analysis tool call. This is an executed synthetic-provider reproduction. No IDA engine or real native plugin was run, and no public CLI invocation was executed. ### Reproduction From the source checkout, with its locked dependencies installed: 1. Save the following temporary test as `src/ida/repro.test.ts`. 2. Run `npm run test:local -- src/ida/repro.test.ts`. 3. Observe the bare-type control succeeding and the other three cases returning `unreadable_output`. The assertions encode the current failure, so a passing test confirms this report. Delete the test afterward. The test uses the existing target and producer fixtures, an ephemeral loopback HTTP endpoint, and the production connection/session implementation. It was run once successfully with all four table cases after correcting an incomplete lookup response in the initial test fixture. ```ts import { createServer } from "node:http"; import { rm } from "node:fs/promises"; import { it, expect } from "vitest"; import { ReadResourceResultSchema } from "@modelcontextprotocol/core"; import { createIdaTarget, RecordingIdaMcp } from "../../tests/fixtures/idaMcp.js"; import { createIdaMcpConnection } from "./IdaMcpConnection.js"; import { IdaSessionClient } from "./IdaSessionClient.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; it.each(["application/json", undefined, "application/json; charset=utf-8", "Application/JSON"])( "native metadata media type: %s", async (mimeType) => { const { root, target } = await createIdaTarget(); const methods: string[] = []; const producer = new RecordingIdaMcp(target); producer.overrideCall = (name) => name === "decompile" ? { addr: "0x1000", ok: true, pseudocode: "int main(void) { return 0; }", lines: 1 } : undefined; const resource = { contents: [{ uri: "ida://idb/metadata", ...(mimeType === undefined ? {} : { mimeType }), text: JSON.stringify({ path: "fixture.i64", sha256: target.sha256, base: "0x1000", module: "fixture" }), }] }; expect(ReadResourceResultSchema.safeParse(resource).success).toBe(true); const server = createServer(async (request, response) => { if (request.method !== "POST") { response.writeHead(405).end(); return; } let body = ""; for await (const chunk of request) body += String(chunk); const input = JSON.parse(body); methods.push(input.method); if (input.id === undefined) { response.writeHead(202).end(); return; } const result = input.method === "initialize" ? { protocolVersion: input.params.protocolVersion, capabilities: { tools: {}, resources: {} }, serverInfo: { name: "fixture", version: "1" } } : input.method === "tools/list" ? { tools: ["list_funcs", "lookup_funcs", "find_regex", "decompile", "disasm", "callees", "xrefs_to"].map((name) => ({ name, inputSchema: { type: "object", properties: {} } })) } : input.method === "resources/read" ? resource : { content: [{ type: "text", text: JSON.stringify(await producer.call(input.params.name, input.params.arguments ?? {})) }] }; response.writeHead(200, { "Content-Type": "application/json" }); response.end(JSON.stringify({ jsonrpc: "2.0", id: input.id, result })); }); await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve)); const address = server.address(); if (!address || typeof address === "string") throw new Error("Missing port"); const config = { url: `http://127.0.0.1:${address.port}/mcp`, headers: {}, mode: "attached" as const, protocol: "native" as const, timeoutMs: 2000 }; const connection = createIdaMcpConnection(config, {}); const client = new IdaSessionClient(config, target, connection); try { for (const operation of ["health", "procedure_pseudo_code"] as const) { const result = await client.execute(operation, operation === "health" ? {} : { procedure: "0x1000" }); if (mimeType === "application/json") { expect(result.ok).toBe(true); console.log(JSON.stringify({ mimeType, operation, ok: result.ok })); } else { expect(result.ok).toBe(false); if (result.ok) throw new Error("Expected failure"); const error = projectAnalysisError(result.error); expect(error.code).toBe("unreadable_output"); expect(result.error.message).toContain("contents.0.mimeType"); console.log(JSON.stringify({ mimeType: mimeType ?? "(omitted)", operation, ok: result.ok, code: error.code, reason: result.error.message })); } } expect(methods).toContain("resources/read"); expect(methods.includes("tools/call")).toBe(mimeType === "application/json"); } finally { expect((await client.close()).ok).toBe(true); await new Promise<void>((resolve, reject) => { server.close((error) => error ? reject(error) : resolve()); server.closeAllConnections(); }); await rm(root, { recursive: true, force: true }); } }, ); ``` ### Expected behavior A JSON metadata resource with the required recorded input identity should survive omission of the optional resource `mimeType`, and ordinary parameter/case variants of the JSON media type should not invalidate an otherwise identical observation. Keep JSON parsing and target-identity checks strict. The [documented native profile](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/docs/ida-provider.md#L57-L71) requires `resources/read` for `ida://idb/metadata` and the recorded SHA-256; it does not require an exact media-type spelling. REA's [boundary invariants](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/AGENTS.md#L19-L25) require preserving omission/uncertainty and format-aware normalization. The reproduction independently checks the optional-field contract against the installed, pinned SDK schema. This report does not establish a requirement to accept arbitrary non-JSON types or every `+json` resource type. ### Impact and scope P2: a bounded compatibility failure prevents metadata observation and the requested pseudocode operation for these resource declarations. All three variants failed both exercised operations; the control succeeded at both. Session close succeeded in every case, and the target was not executed or modified. Source tracing shows that other admitted native operations also begin with the same metadata read. Operations rejected by earlier input or capability checks are outside that statement. Legacy and headless modes take different metadata paths. Real-producer frequency is unknown: this does not establish that a released native plugin emits any affected variant. Returning bare `application/json` worked in the fixture; whether affected users can change their producer's declaration is unverified. ### REA version rea-agents 6.7.0; source revision a5f982de8dbe4b092cc20ab89d492b7e7028b758 ### Runtime and platform Node.js v24.22.0; macOS 27.0.1 arm64; Vitest 5.0.3; pinned MCP SDK 2.3.1; synthetic HTTP producer, no IDA engine. ### Target and artifact context Repository-generated x86_64 ELF fixture with matching metadata SHA-256; attached native profile over a local Streamable HTTP fixture. ### Interface Other ### Earliest failure stage Transport or result projection ### Relevant output or evidence Trimmed output from the final focused run: ```text {"mimeType":"application/json","operation":"health","ok":true} {"mimeType":"application/json","operation":"procedure_pseudo_code","ok":true} {"mimeType":"(omitted)","operation":"health","ok":false,"code":"unreadable_output","reason":"Invalid analysis output for health: IDA MCP returned malformed or unsupported producer data: contents.0.mimeType: Invalid input: expected \"application/json\""} {"mimeType":"application/json; charset=utf-8","operation":"health","ok":false,"code":"unreadable_output","reason":"Invalid analysis output for health: IDA MCP returned malformed or unsupported producer data: contents.0.mimeType: Invalid input: expected \"application/json\""} {"mimeType":"Application/JSON","operation":"health","ok":false,"code":"unreadable_output","reason":"Invalid analysis output for health: IDA MCP returned malformed or unsupported producer data: contents.0.mimeType: Invalid input: expected \"application/json\""} Test Files 1 passed (1) Tests 4 passed (4) ``` Each non-control case also produced `unreadable_output` for `procedure_pseudo_code`, as asserted in the reproduction. Every SDK-schema acceptance and successful-close assertion passed. The [resource validator](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/ida/IdaMcpConnection.ts#L478-L497) adds `mimeType: z.literal("application/json")` after the SDK read. The [session observation path](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/ida/IdaSessionClient.ts#L658-L679) invokes that resource before parsing its identity. The [operation path](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/ida/IdaSessionClient.ts#L295-L307) observes metadata before health or analysis dispatch, and the [error conversion](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/ida/IdaSessionClient.ts#L428-L434) wraps this Zod failure as an analysis-output error. Supported-entry reachability was checked in source: the [decompile command](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/cli/coreBinaryCommands.ts#L157-L215) requests `procedure_pseudo_code`, and the selected provider [constructs this session with its production connection](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/ida/IdaProvider.ts#L389-L399). ### Typed outcome or recovery expected Return the normal metadata-backed health or analysis result for these otherwise unchanged replies. Retain strict failure for malformed payloads and mismatched target identity. The internal error already identifies `contents.0.mimeType`; the defect is refusing these resource representations, rather than a separate claim that the schema failure has no explanation. ### Analysis or suspected cause Source-derived: the second validation compares the media-type declaration as a required literal string. It rejects the optional-field representation accepted by the pinned SDK and does not normalize the declared JSON essence. Correcting this boundary need not change analysis, database identity or lifecycle authority. ### Additional context _No response_
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

The owning resource decoder now accepts omitted mimeType and otherwise parses MIME essence with Node MIMEType, admitting JSON case/parameter variants while preserving exact resource URI, one-content cardinality, text and JSON parsing. The regression covers precisely undefined, application/json, Application/JSON and application/json; charset=utf-8 and separately rejects wrong URI, text/plain and malformed JSON.

Implementation: IdaMcpConnection.ts, IdaMcpConnection.test.ts.

The relevant regression checks in IdaMcpConnection.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380774621 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. The owning resource decoder now accepts omitted mimeType and otherwise parses MIME essence with Node MIMEType, admitting JSON case/parameter variants while preserving exact resource URI, one-content cardinality, text and JSON parsing. The regression covers precisely undefined, application/json, Application/JSON and application/json; charset=utf-8 and separately rejects wrong URI, text/plain and malformed JSON. Implementation: [IdaMcpConnection.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/ida/IdaMcpConnection.ts#L564), [IdaMcpConnection.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/ida/IdaMcpConnection.test.ts#L529). The relevant regression checks in [IdaMcpConnection.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/ida/IdaMcpConnection.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3522
No description provided.