[GH-ISSUE #4058] [Bug] compare_web_screenshots fails in Claude Desktop: $ref params passed as strings #1606

Closed
opened 2026-10-16 21:07:53 +00:00 by bsakaguchi · 7 comments
Owner

Originally created by @anupamsahoo on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4058

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

Calling compare_web_screenshots from Claude through the Claude Desktop MCP bridge fails before execution. The before and after arguments are sent as JSON objects but REA receives them as strings:

Input validation error: Invalid arguments for tool compare_web_screenshots:
before: Invalid input: expected object, received string,
after: Invalid input: expected object, received string

Claude also reported compare_web_captures as unusable ("problem with that connector"). I did not call it directly.

Reproduction

  1. Register REA in claude_desktop_config.json: "rea": { "command": "npx", "args": ["-y", "rea-agents@latest", "mcp"] }
  2. Fully quit and restart Claude Desktop.
  3. From a Claude session linked to the computer, call compare_web_screenshots with two valid 1x1 PNG artifacts as objects: { "media_type": "image/png", "bytes": 69, "sha256": "", "data_base64": "" }
  4. Observe the validation error above.

Reproduced 1/1 times.

Expected behavior

The tool accepts the PNG artifact objects and returns local pixel metrics (changed-pixel ratio and channel deltas), as described in the tool's own description.

Impact and scope

compare_web_screenshots cannot be used from Claude Desktop. compare_web_captures is likely affected too, since it reportedly fails the same way, but I did not verify that directly. Tools with flat scalar params work over the same bridge (open_binary and binary_overview with Ghidra succeeded). No effect on evidence integrity, because the request is rejected before execution. Other clients are untested.

REA version

6.5.0

Runtime and platform

Node v22.22.0; macOS 27.0.1 arm64; Ghidra 12.1.4; Claude Desktop (latest)

Target and artifact context

Two 1x1 PNG screenshot artifacts (synthetic test images); not applicable otherwise

Interface

MCP

Earliest failure stage

Request parsing or validation

Relevant output or evidence

MCP request actually run: compare_web_screenshots with before/after as JSON objects ({media_type, bytes, sha256, data_base64}).
Relevant result: "Input validation error ... before: Invalid input: expected object, received string, after: Invalid input: expected object, received string"
Earlier, through a different Claude Desktop tool path, the same call got past argument parsing and was rejected with "Invalid PNG chunk checksum" (my test PNG was malformed). So object arguments did arrive on that path.
Missing evidence: the raw JSON-RPC payload sent by the bridge was not captured.

Typed outcome or recovery expected

Either a successful comparison result, or a typed error naming the client-side serialization problem rather than a generic type mismatch.

Analysis or suspected cause

Hypothesis, not confirmed: in the published tool schema, before/after are declared via "$ref": "#/$defs/__schema0" (and channel_threshold via __schema5). The Claude Desktop bridge appears not to resolve $ref, so it serializes the values as strings.

Proposed fixes:

  • Inline the param schemas in the published MCP tool definitions instead of using $ref/$defs.
  • Or, defensively, JSON.parse string values for object-typed params before validation.

Additional context

Possibly related: #3408. Native analysis (Ghidra 12.1.4) works normally over the same bridge.

Originally created by @anupamsahoo on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4058 ### 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 Calling `compare_web_screenshots` from Claude through the Claude Desktop MCP bridge fails before execution. The `before` and `after` arguments are sent as JSON objects but REA receives them as strings: Input validation error: Invalid arguments for tool compare_web_screenshots: before: Invalid input: expected object, received string, after: Invalid input: expected object, received string Claude also reported `compare_web_captures` as unusable ("problem with that connector"). I did not call it directly. ### Reproduction 1. Register REA in claude_desktop_config.json: "rea": { "command": "npx", "args": ["-y", "rea-agents@latest", "mcp"] } 2. Fully quit and restart Claude Desktop. 3. From a Claude session linked to the computer, call `compare_web_screenshots` with two valid 1x1 PNG artifacts as objects: { "media_type": "image/png", "bytes": 69, "sha256": "<sha256>", "data_base64": "<base64>" } 4. Observe the validation error above. Reproduced 1/1 times. ### Expected behavior The tool accepts the PNG artifact objects and returns local pixel metrics (changed-pixel ratio and channel deltas), as described in the tool's own description. ### Impact and scope compare_web_screenshots cannot be used from Claude Desktop. compare_web_captures is likely affected too, since it reportedly fails the same way, but I did not verify that directly. Tools with flat scalar params work over the same bridge (open_binary and binary_overview with Ghidra succeeded). No effect on evidence integrity, because the request is rejected before execution. Other clients are untested. ### REA version 6.5.0 ### Runtime and platform Node v22.22.0; macOS 27.0.1 arm64; Ghidra 12.1.4; Claude Desktop (latest) ### Target and artifact context Two 1x1 PNG screenshot artifacts (synthetic test images); not applicable otherwise ### Interface MCP ### Earliest failure stage Request parsing or validation ### Relevant output or evidence MCP request actually run: compare_web_screenshots with before/after as JSON objects ({media_type, bytes, sha256, data_base64}). Relevant result: "Input validation error ... before: Invalid input: expected object, received string, after: Invalid input: expected object, received string" Earlier, through a different Claude Desktop tool path, the same call got past argument parsing and was rejected with "Invalid PNG chunk checksum" (my test PNG was malformed). So object arguments did arrive on that path. Missing evidence: the raw JSON-RPC payload sent by the bridge was not captured. ### Typed outcome or recovery expected Either a successful comparison result, or a typed error naming the client-side serialization problem rather than a generic type mismatch. ### Analysis or suspected cause Hypothesis, not confirmed: in the published tool schema, `before`/`after` are declared via "$ref": "#/$defs/__schema0" (and `channel_threshold` via __schema5). The Claude Desktop bridge appears not to resolve $ref, so it serializes the values as strings. Proposed fixes: - Inline the param schemas in the published MCP tool definitions instead of using $ref/$defs. - Or, defensively, JSON.parse string values for object-typed params before validation. ### Additional context Possibly related: #3408. Native analysis (Ghidra 12.1.4) works normally over the same bridge.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Thanks for the detailed repro. The $ref/$defs hypothesis looks plausible — Claude Desktop's MCP bridge not resolving $ref and passing objects as strings would exactly produce this validation error, and flat scalar tools working over the same bridge supports that. Inlining the param schemas in the published tool definitions seems like the most compatible fix; a defensive JSON.parse for object-typed params before validation would also help other clients with the same limitation. Probably worth checking compare_web_captures and any other tool that uses $ref'd object params.

<!-- gh-comment-id:6189364970 --> @XinwangTao commented on GitHub (Oct 15, 2026): Thanks for the detailed repro. The $ref/$defs hypothesis looks plausible — Claude Desktop's MCP bridge not resolving $ref and passing objects as strings would exactly produce this validation error, and flat scalar tools working over the same bridge supports that. Inlining the param schemas in the published tool definitions seems like the most compatible fix; a defensive JSON.parse for object-typed params before validation would also help other clients with the same limitation. Probably worth checking compare_web_captures and any other tool that uses $ref'd object params.
Author
Owner

@anupamsahoo commented on GitHub (Oct 15, 2026):

Thanks for confirming. A few follow-ups:

  1. Agreed on inlining as the primary fix. The JSON.parse fallback would be a nice safety net for other clients with the same limitation.
  2. To scope it: is there a quick way to list every published tool whose inputSchema uses $ref/$defs? Something like a check in scripts/generate-mcp-tool-catalog.mjs could catch regressions in CI.
  3. Possibly related: #3408 ("Array and object tool parameters rejected…"). It may be the same root cause.
<!-- gh-comment-id:6191176794 --> @anupamsahoo commented on GitHub (Oct 15, 2026): Thanks for confirming. A few follow-ups: 1. Agreed on inlining as the primary fix. The `JSON.parse` fallback would be a nice safety net for other clients with the same limitation. 2. To scope it: is there a quick way to list every published tool whose `inputSchema` uses `$ref`/`$defs`? Something like a check in `scripts/generate-mcp-tool-catalog.mjs` could catch regressions in CI. 3. Possibly related: #3408 ("Array and object tool parameters rejected…"). It may be the same root cause.
Author
Owner

@akushonkamen commented on GitHub (Oct 15, 2026):

Confirmed the root cause locally: toolInputSchemaWithMetadata in src/contracts/toolSchemaMetadata.ts:60-75 projects tool input schemas with libraryOptions: { reused: "ref" }, and since webScreenshotArtifactSchema (src/domain/webScreenshot.ts:15-36) is shared by both before and after, the advertised inputSchema for compare_web_screenshots / compare_web_captures ships + for the object params. I reproduced this with a vitest probe: the advertised schema for compare_web_screenshots contains .__schema0..\__schema4 with properties.before = { "$ref": "#/$defs/__schema0" }, while flat scalar tools like capture_web_screenshot have no `` — matching the observed behavior exactly.

I'm working on a fix: inline reused subschemas in the advertised input schema (keeping output-side ref sharing), plus a regression test asserting no published input schema contains /.

Note: this contribution was prepared with AI assistance, reviewed and tested by me.

<!-- gh-comment-id:6192194519 --> @akushonkamen commented on GitHub (Oct 15, 2026): Confirmed the root cause locally: `toolInputSchemaWithMetadata` in `src/contracts/toolSchemaMetadata.ts:60-75` projects tool input schemas with `libraryOptions: { reused: "ref" }`, and since `webScreenshotArtifactSchema` (`src/domain/webScreenshot.ts:15-36`) is shared by both `before` and `after`, the advertised `inputSchema` for `compare_web_screenshots` / `compare_web_captures` ships `` + `` for the object params. I reproduced this with a vitest probe: the advertised schema for `compare_web_screenshots` contains `.__schema0..\__schema4` with `properties.before = { "$ref": "#/$defs/__schema0" }`, while flat scalar tools like `capture_web_screenshot` have no `` — matching the observed behavior exactly. I'm working on a fix: inline reused subschemas in the advertised input schema (keeping output-side ref sharing), plus a regression test asserting no published input schema contains ``/``. Note: this contribution was prepared with AI assistance, reviewed and tested by me.
Author
Owner

@akushonkamen commented on GitHub (Oct 15, 2026):

The fix is up as #4147 (branch fix/4058-inline-input-schema, rebased onto current main). Beyond what the claim comment above already covers, two notes:

  • To keep every advertised input self-contained, the input-side reference-expansion budget helpers (inputSchemaWithinReferenceExpansionBudget, countLocalReferenceExpansions, advertisedReferenceExpansionBudget) are removed along with their last caller; output schemas keep / sharing, and libraryOptions can still request the shared form.
  • Post-rebase validation: the new catalog-wide test fails 7/13 on base a40ee741c and passes 20/20 with the fix; src/contracts 107 passed | 3 skipped; --project mcp-boundary 770 passed | 5 skipped (after regenerating the local gitignored .cache/mcp-tool-catalog.json, which had predated main's #4095 description changes); typecheck, check:fast, oxlint/oxfmt on the three changed files, and verify:test-discovery (1347 files, once each) all clean. Details and remaining proof gaps (no live Claude Desktop run; compare_web_captures unexplained by this mechanism) are in the PR body.
<!-- gh-comment-id:6194556824 --> @akushonkamen commented on GitHub (Oct 15, 2026): The fix is up as #4147 (branch `fix/4058-inline-input-schema`, rebased onto current `main`). Beyond what the claim comment above already covers, two notes: - To keep every advertised input self-contained, the input-side reference-expansion budget helpers (`inputSchemaWithinReferenceExpansionBudget`, `countLocalReferenceExpansions`, `advertisedReferenceExpansionBudget`) are removed along with their last caller; output schemas keep ``/`` sharing, and `libraryOptions` can still request the shared form. - Post-rebase validation: the new catalog-wide test fails 7/13 on base `a40ee741c` and passes 20/20 with the fix; `src/contracts` 107 passed | 3 skipped; `--project mcp-boundary` 770 passed | 5 skipped (after regenerating the local gitignored `.cache/mcp-tool-catalog.json`, which had predated main's #4095 description changes); typecheck, `check:fast`, oxlint/oxfmt on the three changed files, and `verify:test-discovery` (1347 files, once each) all clean. Details and remaining proof gaps (no live Claude Desktop run; `compare_web_captures` unexplained by this mechanism) are in the PR body.
Author
Owner

@matbrgz commented on GitHub (Oct 16, 2026):

Duplicate of #3408.

Both issues report the exact same root failure mode in Claude Desktop's MCP integration: complex tool arguments (nested objects, $ref parameter schemas, and arrays) are passed or received as serialized JSON strings rather than parsed objects, causing:

Input validation error: Invalid arguments for tool ...: expected object, received string

@morluto: could you please review and consider closing this issue as a duplicate of earlier issue #3408 to consolidate tracking and testing for Claude Desktop MCP parameter serialization?

<!-- gh-comment-id:6205870437 --> @matbrgz commented on GitHub (Oct 16, 2026): Duplicate of #3408. Both issues report the exact same root failure mode in Claude Desktop's MCP integration: complex tool arguments (nested objects, `$ref` parameter schemas, and arrays) are passed or received as serialized JSON strings rather than parsed objects, causing: ``` Input validation error: Invalid arguments for tool ...: expected object, received string ``` @morluto: could you please review and consider closing this issue as a duplicate of earlier issue #3408 to consolidate tracking and testing for Claude Desktop MCP parameter serialization?
Author
Owner

@XinwangTao commented on GitHub (Oct 16, 2026):

The $ref/shared-$defs theory matches the symptom well: flat scalar params survive the Claude Desktop bridge, but nested object params referenced through $defs get flattened into strings during client-side resolution. Inlining the tool input schema (advertise inputs without shared $defs, per #4147) should let Claude pass the objects through intact. I'd also suggest adding a contract test with a nested-object tool over the same bridge so this regression is caught before release. Thanks for the detailed repro — very useful.

<!-- gh-comment-id:6206328749 --> @XinwangTao commented on GitHub (Oct 16, 2026): The $ref/shared-$defs theory matches the symptom well: flat scalar params survive the Claude Desktop bridge, but nested object params referenced through $defs get flattened into strings during client-side resolution. Inlining the tool input schema (advertise inputs without shared $defs, per #4147) should let Claude pass the objects through intact. I'd also suggest adding a contract test with a nested-object tool over the same bridge so this regression is caught before release. Thanks for the detailed repro — very useful.
Author
Owner

@morluto commented on GitHub (Oct 18, 2026):

Closing this report as a duplicate of #3408: both report complex MCP object/array arguments arriving as JSON strings and failing the canonical input schema. The reproduction and screenshot details remain preserved here. Connector registration failures without that validation error, including #4973, remain separate and open; this closure does not claim they are fixed.

<!-- gh-comment-id:6256020286 --> @morluto commented on GitHub (Oct 18, 2026): <!-- rea-backlog-reconciliation-2026-10-19-4058 --> Closing this report as a duplicate of #3408: both report complex MCP object/array arguments arriving as JSON strings and failing the canonical input schema. The reproduction and screenshot details remain preserved here. Connector registration failures without that validation error, including #4973, remain separate and open; this closure does not claim they are fixed.
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#1606
No description provided.