[GH-ISSUE #5950] [Bug] match_native_functions loses malformed Evidence field paths in MCP and CLI #3620

Closed
opened 2026-10-31 00:42:26 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

MCP or agent integration

Observed behavior

match_native_functions rejects malformed inline Evidence but loses the field that needs correction. Through the registered MCP server, replacing each of left, right, left_observations[0], or right_observations[0] with "not-evidence" returned invalid_request with one root issue (path: []). The diagnostic did not identify which supplied field contained the string.

The built match-native-functions CLI rejected malformed left with the same error code but omitted details.issues entirely. Both adapters returned inspect_input_contract recovery, asking the caller to correct listed fields without listing the failing field. A corrected MCP request succeeded.

Reproduction

With dependencies and build artifacts present at the revision below, save the following test as tests/boundary/mcp/nativeMatchingInputDiagnostics.test.ts and run:

./node_modules/.bin/vitest run tests/boundary/mcp/nativeMatchingInputDiagnostics.test.ts --maxWorkers 1

The executed test used the real SDK client with InMemoryTransport and the registered server. Inventory Evidence came from the repository's synthetic fixture. No native analysis engine or live binary was used; the session fixture throws if a provider starts.

import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { expect, it } from "vitest";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { nativeMatchingInventory } from "../../fixtures/nativeFunctionMatching.js";
import { createServer } from "../../../src/server/createServer.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { matchNativeFunctionsInputSchema } from "../../../src/contracts/native/functionMatchingContracts.js";
import { evidenceInputSchema } from "../../../src/domain/evidenceReference.js";

it("loses the field path for malformed inline native matching inputs", async () => {
  let providerLaunches = 0;
  const session = createTestBinarySession(() => {
    providerLaunches++;
    throw new Error("No provider should launch");
  });
  const server = createServer({ kind: "session", session });
  const client = new Client({ name: "native-matching-input-test", version: "1" });
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
  const left = nativeMatchingInventory("1", [{ address: "0x10", value: "main" }]);
  const right = nativeMatchingInventory("2", [{ address: "0x20", value: "main" }]);
  try {
    await server.connect(serverTransport);
    await client.connect(clientTransport);
    expect(evidenceInputSchema.safeParse("not-evidence").success).toBe(false);
    for (const field of ["left", "right", "left_observations", "right_observations"] as const) {
      const input = { left, right, detail: "complete", [field]: field.endsWith("observations") ? ["not-evidence"] : "not-evidence" };
      expect(matchNativeFunctionsInputSchema.safeParse(input).success).toBe(true);
      const result = await client.callTool({ name: "match_native_functions", arguments: input });
      const diagnostic = parseMcpToolError(result);
      console.log(JSON.stringify({ field, diagnostic }));
      expect(diagnostic.error).toMatchObject({
        code: "invalid_request", category: "invalid_input",
        details: { issues: [{ path: [], reason: "invalid_value" }] },
      });
    }
    const corrected = await client.callTool({ name: "match_native_functions", arguments: { left, right, detail: "complete" } });
    expect(corrected.isError).not.toBe(true);
    expect(parseEvidence(corrected.structuredContent).normalized_result).toMatchObject({ summary: { matched: 1 } });
    expect(providerLaunches).toBe(0);
    await client.ping();
  } finally {
    await client.close();
    await server.close();
    await session.close();
  }
});

One selected test passed, confirming the current incorrect diagnostic for all four locations and a successful corrected-input control. This is a reproduction test that asserts the observed behavior.

The following built CLI request was also executed and exited with status 1:

node scripts/rea.mjs match-native-functions '{"left":"not-evidence","right":{"kind":"retained-evidence","evidence_id":"ev_1111111111111111111111111111111111111111111111111111111111111111"},"detail":"complete"}' --json

detail: complete avoids the separate summary-output requirement. The malformed left input fails before the right reference is resolved.

Expected behavior

Preserve the originating field and observation index when deferred Evidence validation fails: ["left"], ["right"], ["left_observations", 0], or ["right_observations", 0]. Return actionable input issues consistently through MCP and CLI while retaining admission budgeting before recursive Evidence parsing.

The native matching contract describes those fields as Evidence or exact references. The standard input issue projection preserves paths for contract failures, and documented input recovery tells callers to correct the listed fields.

Impact and scope

P2: callers lose structured field attribution for correctable malformed Evidence, and CLI/MCP diagnostics disagree. MCP coverage executed all four locations with a string value; CLI coverage executed malformed left. Other invalid shapes and CLI fields were not separately executed. Correcting both inventory inputs succeeded through MCP with one match and zero provider launches. No false successful match, corruption, or target execution was observed.

REA version

rea-agents 6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e.

Runtime and platform

macOS 27.0.1 arm64; Node.js v26.12.0; Vitest 5.0.3. The repository pins Node 24.18.0; that runtime was not separately tested.

Target and artifact context

Synthetic authenticated list_procedures Evidence containing one named function on each side; no live binary target. The CLI request uses a structurally valid retained-reference placeholder on the unvisited right side.

Interface

MCP

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Each malformed MCP location returned isError: true and canonical text diagnostics with this relevant subset:

{
  "error": {
    "code": "invalid_request",
    "category": "invalid_input",
    "recovery": {
      "kind": "inspect_input_contract",
      "operation": "match_native_functions"
    },
    "details": {
      "operation": "match_native_functions",
      "issues": [{
        "path": [],
        "reason": "invalid_value",
        "message": "[\n  {\n    \"expected\": \"object\",\n    \"code\": \"invalid_type\",\n    \"path\": [],\n    \"message\": \"Invalid input: expected object, received string\"\n  }\n]"
      }]
    }
  }
}

The built CLI returned:

{
  "code": "invalid_request",
  "category": "invalid_input",
  "message": "Analysis input is invalid. Check the arguments and try again.",
  "retryable": false,
  "remediation": {
    "action": "Read the declared input schema or CLI usage for match_native_functions; if using MCP and the catalog may be stale, call tools/list. Correct the listed fields, then retry. Do not repeat the unchanged call."
  },
  "recovery": {
    "kind": "inspect_input_contract",
    "operation": "match_native_functions"
  }
}

The corrected MCP control returned successful Evidence with normalized_result.summary.matched: 1; the connection remained responsive to ping.

Typed outcome or recovery expected

invalid_request / invalid_input, with details.issues naming the rejected input field and observation index, plus a useful type/value reason. Existing inspect_input_contract recovery can remain.

Analysis or suspected cause

Source-derived: the contract deliberately borrows inline values until representation admission, using z.unknown().nonoptional() while advertising the Evidence/reference union. The test confirms malformed string values pass that parser and fail the Evidence union.

In the MCP application path, resolve parses each admitted value without retaining its input path. The catch replaces the parse failure with a root issue. In the CLI path, the bundle loader parses admitted inline values, and the outer catch creates an input error without issues.

Carrying the selected field/index through deferred parsing would preserve both admission ordering and diagnostic attribution; eager recursive parsing before budgeting is not required to correct the behavior.

Additional context

Distinct from #5932: that report concerns compare_functions classifying authenticated wrong-operation Evidence as an integrity failure. Here, match_native_functions receives structurally malformed inline data and already returns an input error, but omits its field location. The production handlers and failing branches differ.

The relevant matching contract, service, bundle loader, and adapter registration lines are unchanged at fetched origin/main revision 25e1a24a3ed3671a0148757555f18d16c29d936a. That later source was inspected, not executed.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5950 ### 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 MCP or agent integration ### Observed behavior `match_native_functions` rejects malformed inline Evidence but loses the field that needs correction. Through the registered MCP server, replacing each of `left`, `right`, `left_observations[0]`, or `right_observations[0]` with `"not-evidence"` returned `invalid_request` with one root issue (`path: []`). The diagnostic did not identify which supplied field contained the string. The built `match-native-functions` CLI rejected malformed `left` with the same error code but omitted `details.issues` entirely. Both adapters returned `inspect_input_contract` recovery, asking the caller to correct listed fields without listing the failing field. A corrected MCP request succeeded. ### Reproduction With dependencies and build artifacts present at the revision below, save the following test as `tests/boundary/mcp/nativeMatchingInputDiagnostics.test.ts` and run: ```sh ./node_modules/.bin/vitest run tests/boundary/mcp/nativeMatchingInputDiagnostics.test.ts --maxWorkers 1 ``` The executed test used the real SDK client with `InMemoryTransport` and the registered server. Inventory Evidence came from the repository's synthetic fixture. No native analysis engine or live binary was used; the session fixture throws if a provider starts. ```ts import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { expect, it } from "vitest"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { nativeMatchingInventory } from "../../fixtures/nativeFunctionMatching.js"; import { createServer } from "../../../src/server/createServer.js"; import { parseEvidence } from "../../../src/domain/evidence.js"; import { matchNativeFunctionsInputSchema } from "../../../src/contracts/native/functionMatchingContracts.js"; import { evidenceInputSchema } from "../../../src/domain/evidenceReference.js"; it("loses the field path for malformed inline native matching inputs", async () => { let providerLaunches = 0; const session = createTestBinarySession(() => { providerLaunches++; throw new Error("No provider should launch"); }); const server = createServer({ kind: "session", session }); const client = new Client({ name: "native-matching-input-test", version: "1" }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const left = nativeMatchingInventory("1", [{ address: "0x10", value: "main" }]); const right = nativeMatchingInventory("2", [{ address: "0x20", value: "main" }]); try { await server.connect(serverTransport); await client.connect(clientTransport); expect(evidenceInputSchema.safeParse("not-evidence").success).toBe(false); for (const field of ["left", "right", "left_observations", "right_observations"] as const) { const input = { left, right, detail: "complete", [field]: field.endsWith("observations") ? ["not-evidence"] : "not-evidence" }; expect(matchNativeFunctionsInputSchema.safeParse(input).success).toBe(true); const result = await client.callTool({ name: "match_native_functions", arguments: input }); const diagnostic = parseMcpToolError(result); console.log(JSON.stringify({ field, diagnostic })); expect(diagnostic.error).toMatchObject({ code: "invalid_request", category: "invalid_input", details: { issues: [{ path: [], reason: "invalid_value" }] }, }); } const corrected = await client.callTool({ name: "match_native_functions", arguments: { left, right, detail: "complete" } }); expect(corrected.isError).not.toBe(true); expect(parseEvidence(corrected.structuredContent).normalized_result).toMatchObject({ summary: { matched: 1 } }); expect(providerLaunches).toBe(0); await client.ping(); } finally { await client.close(); await server.close(); await session.close(); } }); ``` One selected test passed, confirming the current incorrect diagnostic for all four locations and a successful corrected-input control. This is a reproduction test that asserts the observed behavior. The following built CLI request was also executed and exited with status 1: ```sh node scripts/rea.mjs match-native-functions '{"left":"not-evidence","right":{"kind":"retained-evidence","evidence_id":"ev_1111111111111111111111111111111111111111111111111111111111111111"},"detail":"complete"}' --json ``` `detail: complete` avoids the separate summary-output requirement. The malformed left input fails before the right reference is resolved. ### Expected behavior Preserve the originating field and observation index when deferred Evidence validation fails: `["left"]`, `["right"]`, `["left_observations", 0]`, or `["right_observations", 0]`. Return actionable input issues consistently through MCP and CLI while retaining admission budgeting before recursive Evidence parsing. The [native matching contract](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/contracts/native/functionMatchingContracts.ts#L4-L28) describes those fields as Evidence or exact references. The [standard input issue projection](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/inputIssueProjection.ts#L28-L99) preserves paths for contract failures, and [documented input recovery](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/docs/mcp-contracts.md#L92-L112) tells callers to correct the listed fields. ### Impact and scope P2: callers lose structured field attribution for correctable malformed Evidence, and CLI/MCP diagnostics disagree. MCP coverage executed all four locations with a string value; CLI coverage executed malformed `left`. Other invalid shapes and CLI fields were not separately executed. Correcting both inventory inputs succeeded through MCP with one match and zero provider launches. No false successful match, corruption, or target execution was observed. ### REA version `rea-agents` 6.7.0; source revision `52415975942a4b9c53a817915b313ccc404f280e`. ### Runtime and platform macOS 27.0.1 arm64; Node.js v26.12.0; Vitest 5.0.3. The repository pins Node 24.18.0; that runtime was not separately tested. ### Target and artifact context Synthetic authenticated `list_procedures` Evidence containing one named function on each side; no live binary target. The CLI request uses a structurally valid retained-reference placeholder on the unvisited right side. ### Interface MCP ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Each malformed MCP location returned `isError: true` and canonical text diagnostics with this relevant subset: ```json { "error": { "code": "invalid_request", "category": "invalid_input", "recovery": { "kind": "inspect_input_contract", "operation": "match_native_functions" }, "details": { "operation": "match_native_functions", "issues": [{ "path": [], "reason": "invalid_value", "message": "[\n {\n \"expected\": \"object\",\n \"code\": \"invalid_type\",\n \"path\": [],\n \"message\": \"Invalid input: expected object, received string\"\n }\n]" }] } } } ``` The built CLI returned: ```json { "code": "invalid_request", "category": "invalid_input", "message": "Analysis input is invalid. Check the arguments and try again.", "retryable": false, "remediation": { "action": "Read the declared input schema or CLI usage for match_native_functions; if using MCP and the catalog may be stale, call tools/list. Correct the listed fields, then retry. Do not repeat the unchanged call." }, "recovery": { "kind": "inspect_input_contract", "operation": "match_native_functions" } } ``` The corrected MCP control returned successful Evidence with `normalized_result.summary.matched: 1`; the connection remained responsive to `ping`. ### Typed outcome or recovery expected `invalid_request` / `invalid_input`, with `details.issues` naming the rejected input field and observation index, plus a useful type/value reason. Existing `inspect_input_contract` recovery can remain. ### Analysis or suspected cause Source-derived: [the contract](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/contracts/native/functionMatchingContracts.ts#L4-L28) deliberately borrows inline values until representation admission, using `z.unknown().nonoptional()` while advertising the Evidence/reference union. The test confirms malformed string values pass that parser and fail the Evidence union. In the MCP application path, [resolve parses each admitted value without retaining its input path](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/native/NativeFunctionMatchingService.ts#L64-L97). The [catch replaces the parse failure with a root issue](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/native/NativeFunctionMatchingService.ts#L192-L209). In the CLI path, [the bundle loader parses admitted inline values](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/native/NativeMatchingBundleSources.ts#L62-L77), and [the outer catch creates an input error without issues](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/native/NativeFunctionMatchingService.ts#L239-L248). Carrying the selected field/index through deferred parsing would preserve both admission ordering and diagnostic attribution; eager recursive parsing before budgeting is not required to correct the behavior. ### Additional context Distinct from #5932: that report concerns `compare_functions` classifying authenticated wrong-operation Evidence as an integrity failure. Here, `match_native_functions` receives structurally malformed inline data and already returns an input error, but omits its field location. The production handlers and failing branches differ. The relevant matching contract, service, bundle loader, and adapter registration lines are unchanged at fetched `origin/main` revision `25e1a24a3ed3671a0148757555f18d16c29d936a`. That later source was inspected, not executed.
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#3620
No description provided.