[PR #4147] [CLOSED] fix(mcp): advertise tool inputs without shared $defs #6087

Closed
opened 2026-10-31 01:11:45 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/4147
Author: @akushonkamen
Created: 10/15/2026
Status: ❌ Closed

Base: main ← Head: fix/4058-inline-input-schema


📝 Commits (2)

  • 1415731 fix(mcp): advertise tool inputs without shared $defs
  • 84d255f test(contracts): scan schema map values in the advertised-reference guard

📊 Changes

3 files changed (+174 additions, -176 deletions)

View changed files

📝 src/contracts/inputSchemaPresentation.ts (+4 -67)
📝 src/contracts/toolInputReferences.test.ts (+152 -81)
📝 src/contracts/toolSchemaMetadata.ts (+18 -28)

📄 Description

Summary

Fixes #4058. Calling compare_web_screenshots through Claude Desktop's MCP bridge failed before execution with expected object, received string, because the advertised inputSchema published shared subschemas as $defs + $ref, and the bridge — which does not resolve schema-local references — serialized the object arguments as strings. Published tool inputs are now always advertised fully inlined, so every parameter carries its complete structure on the wire and no client needs reference resolution to call a tool.

Problem and expected behavior

  • Trigger: any client whose schema handling does not resolve JSON Schema $ref against $defs (Claude Desktop's bridge, per the issue), calling a tool whose advertised input shares a subschema between parameters.
  • Violated invariant: an advertised input schema should be self-contained — a client must be able to validate and send arguments from the advertised schema alone.
  • Root cause: toolInputSchemaWithMetadata (src/contracts/toolSchemaMetadata.ts:60-69 at b3b5c8b13) projected inputs with libraryOptions: { reused: "ref" }. webScreenshotArtifactSchema is shared by before and after, so compare_web_screenshots advertised properties.before = { "$ref": "#/$defs/__schema0", ... } with $defs __schema0..__schema5 (probe at b3b5c8b13). A catalog-wide scan on the unfixed tree counted 169 published tools advertising $defs on their inputs in draft-2020-12, including the flat capture_web_screenshot. compare_web_captures is reported failing through the same bridge, but its advertised input carries no shared definitions at all (verified in draft-2020-12 and draft-07), so the reported failure there is not explained by this mechanism and is left unchanged.

Change and scope

  • toolInputSchemaWithMetadata (src/contracts/toolSchemaMetadata.ts:56-66) now projects advertised inputs with reused: "inline": each parameter ships its complete schema. Inlining also serves budget-limited reference resolvers (#1632): with no references on the wire, no reference-expansion budget can degrade a field to unknown.
  • Output schemas are unchanged: they keep shared $defs and $id digests (src/contracts/toolSchemaMetadata.ts:105 still projects outputs with reused: "ref"), and callers can still request the shared form explicitly through libraryOptions.
  • The input-only reference-expansion budget helpers (inputSchemaWithinReferenceExpansionBudget, countLocalReferenceExpansions, advertisedReferenceExpansionBudget) are removed with their last caller; a repo-wide search finds no remaining references.
  • Regression coverage: src/contracts/toolInputReferences.test.ts now walks every entry in TOOL_CONTRACTS under both dialects (draft-2020-12 and draft-07) and asserts that no advertised input schema contains $ref or $defs. This is also the enumeration the issue asked for: the catalog-wide scan/test lists every affected tool mechanically.
  • Not included: a server-side JSON.parse fallback for stringified objects (floated in the issue). The schema is now self-contained, so accepting strings would mask contract violations instead of fixing the advertisement; that safety net can be added separately if a maintainer wants it.

Contribution scope: a bug fix within the existing static, read-only boundary; no new providers or capabilities; linked maintainer scope decision not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: MCP tool-catalog input-schema projection (src/contracts/toolSchemaMetadata.ts, shared projection in src/contracts/inputSchemaPresentation.ts), consumed at the MCP server boundary.
  • Contribution scope fit and linked maintainer decision: ordinary fix within existing boundaries; not applicable.
  • CLI and MCP/tool-catalog contract: every published tool's advertised inputSchema no longer contains $ref/$defs in either dialect; output schemas and explicit libraryOptions sharing are unchanged.
  • Provider, bridge, target-format, or platform compatibility: MCP clients that do not resolve schema-local references (Claude Desktop bridge) can now validate and send object arguments; no target-format impact.
  • Evidence, artifact, provenance, or reconstruction contract: not applicable.
  • Target selection, authority, upstream behavior, and cleanup details: not applicable.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none. The local gitignored .cache/mcp-tool-catalog.json was regenerated via npm run mcp-catalog:generate; no build outputs are committed.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: src/contracts/toolInputReferences.test.ts — catalog-wide assertions (20 tests). Against base a40ee741c with the new test file: 7 failed | 13 passed (red); with the fix: 20 passed (green). The same red/green split was observed at the previous base b3b5c8b13 (7/13 → 20/20).
  • Base reproduction or other evidence: a vitest probe at unfixed b3b5c8b13 showed the advertised input of compare_web_screenshots containing $defs __schema0..__schema5 with properties.before = { "$ref": "#/$defs/__schema0", ... }, and the directory-level scan counted 169 tools advertising $defs (draft-2020-12), including both compare_* tools named in the issue and the flat capture_web_screenshot (executed before the fix; recorded in the issue claim comment).
  • User-visible CLI/MCP output (if applicable): after the fix, the generated catalog advertises compare_web_screenshots with an inputSchema containing no $defs and no $ref; properties.before is the complete object schema (type/properties/required/description, with inlined bytes, data_base64, media_type, …), checked here against .cache/mcp-tool-catalog.json regenerated from this tree.
  • Remaining proof gaps: no live Claude Desktop session was driven against the fixed server (bridge behavior is taken from the issue report); compare_web_captures' reported failure is not explained by shared definitions and is unchanged.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • timeout 300 npx vitest run src/contracts — 12 files, 107 passed | 3 skipped (110 tests).
  • npx vitest run src/contracts/toolInputReferences.test.ts — 20 passed on this branch; 7 failed | 13 passed when the same test file runs against base a40ee741c.
  • npx vitest run src/server/EvidenceMcpServer.test.ts — 1 file, 2 passed.
  • npx vitest run --project mcp-boundary — 139 files (137 passed | 2 skipped), 770 passed | 5 skipped. The first run after rebasing onto current main failed 15 tests across 7 files because the local gitignored .cache/mcp-tool-catalog.json predated main's #4095 description changes; after npm run mcp-catalog:generate the same suite passes fully.
  • npm run typecheck — clean.
  • npm run check:fast — passed.
  • npx oxlint src/contracts/inputSchemaPresentation.ts src/contracts/toolInputReferences.test.ts src/contracts/toolSchemaMetadata.ts — Found 0 warnings and 0 errors.
  • npx oxfmt --check src/contracts/inputSchemaPresentation.ts src/contracts/toolInputReferences.test.ts src/contracts/toolSchemaMetadata.ts — all matched files use the correct format.
  • npm run verify:test-discovery — 1347 test files discovered exactly once.

Compatibility, safety, and release

  • Breaking changes or migration steps: none for conforming clients. Advertised inputs remain valid draft-2020-12/draft-07 and become self-contained; consumers that preferred shared definitions can still request them explicitly via libraryOptions. The removed helpers were input-side projection internals with no remaining references.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable — pure schema projection, covered by the mcp-boundary suite.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: none — no target interaction.

Author readiness

Human owner:

  • I reviewed the final diff and relevant existing code, understand the change and edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Prepared with AI assistance (autonomous contributor agent run); the work is disclosed as AI-assisted in the issue claim comment and awaits human-owner review.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/4147 **Author:** [@akushonkamen](https://github.com/akushonkamen) **Created:** 10/15/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/4058-inline-input-schema` --- ### 📝 Commits (2) - [`1415731`](https://github.com/morluto/rea/commit/141573181b87488d723a109adfa8b9d2a1e686d3) fix(mcp): advertise tool inputs without shared $defs - [`84d255f`](https://github.com/morluto/rea/commit/84d255f0bc8cd73ea23db9ab04a11d0bd05f57bd) test(contracts): scan schema map values in the advertised-reference guard ### 📊 Changes **3 files changed** (+174 additions, -176 deletions) <details> <summary>View changed files</summary> 📝 `src/contracts/inputSchemaPresentation.ts` (+4 -67) 📝 `src/contracts/toolInputReferences.test.ts` (+152 -81) 📝 `src/contracts/toolSchemaMetadata.ts` (+18 -28) </details> ### 📄 Description ## Summary Fixes #4058. Calling `compare_web_screenshots` through Claude Desktop's MCP bridge failed before execution with `expected object, received string`, because the advertised `inputSchema` published shared subschemas as `$defs` + `$ref`, and the bridge — which does not resolve schema-local references — serialized the object arguments as strings. Published tool inputs are now always advertised fully inlined, so every parameter carries its complete structure on the wire and no client needs reference resolution to call a tool. ## Problem and expected behavior - Trigger: any client whose schema handling does not resolve JSON Schema `$ref` against `$defs` (Claude Desktop's bridge, per the issue), calling a tool whose advertised input shares a subschema between parameters. - Violated invariant: an advertised input schema should be self-contained — a client must be able to validate and send arguments from the advertised schema alone. - Root cause: `toolInputSchemaWithMetadata` (`src/contracts/toolSchemaMetadata.ts:60-69` at `b3b5c8b13`) projected inputs with `libraryOptions: { reused: "ref" }`. `webScreenshotArtifactSchema` is shared by `before` and `after`, so `compare_web_screenshots` advertised `properties.before = { "$ref": "#/$defs/__schema0", ... }` with `$defs __schema0..__schema5` (probe at `b3b5c8b13`). A catalog-wide scan on the unfixed tree counted 169 published tools advertising `$defs` on their inputs in draft-2020-12, including the flat `capture_web_screenshot`. `compare_web_captures` is reported failing through the same bridge, but its advertised input carries no shared definitions at all (verified in draft-2020-12 and draft-07), so the reported failure there is not explained by this mechanism and is left unchanged. ## Change and scope - `toolInputSchemaWithMetadata` (`src/contracts/toolSchemaMetadata.ts:56-66`) now projects advertised inputs with `reused: "inline"`: each parameter ships its complete schema. Inlining also serves budget-limited reference resolvers (#1632): with no references on the wire, no reference-expansion budget can degrade a field to unknown. - Output schemas are unchanged: they keep shared `$defs` and `$id` digests (`src/contracts/toolSchemaMetadata.ts:105` still projects outputs with `reused: "ref"`), and callers can still request the shared form explicitly through `libraryOptions`. - The input-only reference-expansion budget helpers (`inputSchemaWithinReferenceExpansionBudget`, `countLocalReferenceExpansions`, `advertisedReferenceExpansionBudget`) are removed with their last caller; a repo-wide search finds no remaining references. - Regression coverage: `src/contracts/toolInputReferences.test.ts` now walks every entry in `TOOL_CONTRACTS` under both dialects (draft-2020-12 and draft-07) and asserts that no advertised input schema contains `$ref` or `$defs`. This is also the enumeration the issue asked for: the catalog-wide scan/test lists every affected tool mechanically. - Not included: a server-side `JSON.parse` fallback for stringified objects (floated in the issue). The schema is now self-contained, so accepting strings would mask contract violations instead of fixing the advertisement; that safety net can be added separately if a maintainer wants it. Contribution scope: a bug fix within the existing static, read-only boundary; no new providers or capabilities; linked maintainer scope decision not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: MCP tool-catalog input-schema projection (`src/contracts/toolSchemaMetadata.ts`, shared projection in `src/contracts/inputSchemaPresentation.ts`), consumed at the MCP server boundary. - Contribution scope fit and linked maintainer decision: ordinary fix within existing boundaries; not applicable. - CLI and MCP/tool-catalog contract: every published tool's advertised `inputSchema` no longer contains `$ref`/`$defs` in either dialect; output schemas and explicit `libraryOptions` sharing are unchanged. - Provider, bridge, target-format, or platform compatibility: MCP clients that do not resolve schema-local references (Claude Desktop bridge) can now validate and send object arguments; no target-format impact. - Evidence, artifact, provenance, or reconstruction contract: not applicable. - Target selection, authority, upstream behavior, and cleanup details: not applicable. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. The local gitignored `.cache/mcp-tool-catalog.json` was regenerated via `npm run mcp-catalog:generate`; no build outputs are committed. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `src/contracts/toolInputReferences.test.ts` — catalog-wide assertions (20 tests). Against base `a40ee741c` with the new test file: **7 failed | 13 passed** (red); with the fix: **20 passed** (green). The same red/green split was observed at the previous base `b3b5c8b13` (7/13 → 20/20). - Base reproduction or other evidence: a vitest probe at unfixed `b3b5c8b13` showed the advertised input of `compare_web_screenshots` containing `$defs __schema0..__schema5` with `properties.before = { "$ref": "#/$defs/__schema0", ... }`, and the directory-level scan counted 169 tools advertising `$defs` (draft-2020-12), including both `compare_*` tools named in the issue and the flat `capture_web_screenshot` (executed before the fix; recorded in the issue claim comment). - User-visible CLI/MCP output (if applicable): after the fix, the generated catalog advertises `compare_web_screenshots` with an `inputSchema` containing no `$defs` and no `$ref`; `properties.before` is the complete object schema (`type`/`properties`/`required`/`description`, with inlined `bytes`, `data_base64`, `media_type`, …), checked here against `.cache/mcp-tool-catalog.json` regenerated from this tree. - Remaining proof gaps: no live Claude Desktop session was driven against the fixed server (bridge behavior is taken from the issue report); `compare_web_captures`' reported failure is not explained by shared definitions and is unchanged. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `timeout 300 npx vitest run src/contracts` — 12 files, 107 passed | 3 skipped (110 tests). - `npx vitest run src/contracts/toolInputReferences.test.ts` — 20 passed on this branch; 7 failed | 13 passed when the same test file runs against base `a40ee741c`. - `npx vitest run src/server/EvidenceMcpServer.test.ts` — 1 file, 2 passed. - `npx vitest run --project mcp-boundary` — 139 files (137 passed | 2 skipped), 770 passed | 5 skipped. The first run after rebasing onto current `main` failed 15 tests across 7 files because the local gitignored `.cache/mcp-tool-catalog.json` predated `main`'s #4095 description changes; after `npm run mcp-catalog:generate` the same suite passes fully. - `npm run typecheck` — clean. - `npm run check:fast` — passed. - `npx oxlint src/contracts/inputSchemaPresentation.ts src/contracts/toolInputReferences.test.ts src/contracts/toolSchemaMetadata.ts` — Found 0 warnings and 0 errors. - `npx oxfmt --check src/contracts/inputSchemaPresentation.ts src/contracts/toolInputReferences.test.ts src/contracts/toolSchemaMetadata.ts` — all matched files use the correct format. - `npm run verify:test-discovery` — 1347 test files discovered exactly once. ## Compatibility, safety, and release - Breaking changes or migration steps: none for conforming clients. Advertised inputs remain valid draft-2020-12/draft-07 and become self-contained; consumers that preferred shared definitions can still request them explicitly via `libraryOptions`. The removed helpers were input-side projection internals with no remaining references. - Real Hopper/Ghidra, browser, or OS coverage: not applicable — pure schema projection, covered by the mcp-boundary suite. - Package or release metadata impact: none. - Security, privacy, process, or containment review: none — no target interaction. ## Author readiness Human owner: <!-- @GitHub-handle --> - [ ] I reviewed the final diff and relevant existing code, understand the change and edge cases, and take responsibility for the complete PR, including AI-assisted work. - [ ] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [ ] I declare this PR ready for initial maintainer review. ## Before merge - [ ] This PR addresses a concrete problem or an agreed enhancement. - [ ] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [ ] The PR has one focused outcome and the title follows `type(scope): outcome`. - [ ] Related issue is linked, or the reason for not linking one is stated above. - [ ] Tests cover changed observable behavior and meaningful failure paths. - [ ] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [ ] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Prepared with AI assistance (autonomous contributor agent run); the work is disclosed as AI-assisted in the issue claim comment and awaits human-owner review. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:11:45 +00:00
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#6087
No description provided.