Skip to content

fix(sdk): repair tool schemas via cross-tool $defs pool with transitive closure - #368

Open
bensynapse wants to merge 1 commit into
google-labs-code:mainfrom
bensynapse:fix/dynamic-schema-repair-pool
Open

fix(sdk): repair tool schemas via cross-tool $defs pool with transitive closure#368
bensynapse wants to merge 1 commit into
google-labs-code:mainfrom
bensynapse:fix/dynamic-schema-repair-pool

Conversation

@bensynapse

Copy link
Copy Markdown

Problem

The hosted Stitch MCP server (https://stitch.googleapis.com/mcp) emits a tools/list in which upload_design_md's outputSchema references #/$defs/ScreenInstance but ships no $defs block (see #367). Clients that eagerly compile tool schemas — AJV inside @modelcontextprotocol/sdk, and strict clients such as kimi-code — fail schema registration and drop all 15 tools, making the integration unusable.

The SDK's existing repair (WELL_KNOWN_DEFS stubs, #353/#355) prevents the crash, but only by injecting hardcoded partial shapes that have drifted from the backend:

  • SelectedScreenInstance stub has screenId/instanceId; the backend's real shape is id/sourceScreen with required: ["id", "sourceScreen"].
  • ScreenInstance stub is missing isResized, needsLayout, textContent, the recursive variantScreenInstance property, and the TEXT_INSTANCE enum member.
  • File stub omits the backend's userFeedback → #/$defs/UserFeedback ref — one-shot stub injection cannot represent defs that introduce new refs.
  • Any future dangling def not on the hardcoded list crashes again.

Fix

Repair dynamically from the response itself, stubs only as fallback:

  1. collectDefPool(tools) (new, exported): harvests every $defs entry across all input/output schemas in the tools/list response. The backend defines each entity properly in at least one tool's schema (e.g. create_project's outputSchema has the real ScreenInstance), so the pool captures the authoritative shapes.
  2. repairSchema(schema, defPool?): resolves missing defs from the pool first (real backend definitions, deep-cloned so schemas share no mutable state), falling back to WELL_KNOWN_DEFS. Iterates to a fixpoint so injected defs that introduce new refs (File → UserFeedback) are fully repaired. Signature is backward compatible.
  3. Fallback stubs synced with the live backend shapes (captured from tools/list on 2026-08-21) so the fallback path is faithful too.

Verification

  • 21 schema-repair unit tests (11 new): pool harvest, pool-beats-stub, transitive closure, stub fallback, no-overwrite, deep-copy isolation, and a regression test modeling the exact upload_design_md shape from MCP tools/list emits unresolvable $ref in outputSchema (upload_design_md dangling, ScreenInstance recursive) — clients drop all tools #367.
  • Full package suite: 198/198 passing; tsc --noEmit clean; prettier clean.
  • Real-world check: applied repairToolSchemas to a live tools/list captured from the hosted endpoint today — all 15 tools' schemas fully resolve after repair (previously upload_design_md's outputSchema was unresolvable). This also unblocks strict non-SDK clients (kimi-code) when the SDK/proxy layer sits in front.

Mitigates #367 — the hosted server still needs the server-side schema fix, but this makes every SDK and proxy consumer immune to it.

@google-cla

google-cla Bot commented Aug 21, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@bensynapse

Copy link
Copy Markdown
Author

@googlebot rescan

@bensynapse bensynapse closed this Aug 21, 2026
@bensynapse bensynapse reopened this Aug 21, 2026
…ve closure

The hosted MCP server's upload_design_md outputSchema references
#/$defs/ScreenInstance without shipping $defs (google-labs-code#367), and strict MCP
clients drop the entire tool list when AJV fails to compile it. The
existing repair only injected hardcoded stub definitions that had
drifted from the backend (wrong SelectedScreenInstance shape, missing
ScreenInstance members, unrepresentable File -> UserFeedback ref).

- collectDefPool() harvests every $defs entry across the tools/list
  response so repair can inject the backend's real definitions
- repairSchema() resolves from the pool first, falls back to stubs,
  and iterates to a fixpoint so defs introducing new refs (e.g. File
  -> UserFeedback) are fully repaired
- Fallback stubs synced with the live backend shapes (2026-08-21)

Verified against a live tools/list capture: all 15 tools fully
resolve after repair. 198/198 package tests pass.
@bensynapse
bensynapse force-pushed the fix/dynamic-schema-repair-pool branch from db79ef6 to 9ba4128 Compare August 21, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant