[PR #7107] [MERGED] fix(mcp): advertise reduced array elements with Gemini-compatible item types #7647

Closed
opened 2026-11-03 13:39:21 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/7107
Author: @kaoru0822-kitauji
Created: 10/25/2026
Status: ✅ Merged
Merged: 10/25/2026
Merged by: @kaoru0822-kitauji

Base: main ← Head: fix/5201-gemini-schema-shapes


📝 Commits (1)

  • 10b8d60 fix(mcp): advertise reduced array elements with Gemini-compatible item types

📊 Changes

5 files changed (+420 additions, -55 deletions)

View changed files

📝 docs/mcp-contracts.md (+10 -0)
📝 src/contracts/compactInputSchema.test.ts (+186 -12)
📝 src/contracts/compactInputSchema.ts (+143 -9)
📝 tests/boundary/mcp/compactInputSchemaMcp.test.ts (+37 -34)
📝 tests/fixtures/mcpClientSchemaConstraints.ts (+44 -0)

📄 Description

What broke: On latest main, the default compact input profile still advertises 46 array item schemas in 30 tools as {"type":["null","boolean","object","array","number","string"],"items":{}}. Gemini rejects items beside anything but the single array type, which gives the reported properties[functions].items.items: field predicate failed: $type == Type.ARRAY.
Fix: Reduced compact array properties now advertise the element types their canonical item schemas accept ({"type":"object"} for functions, actions and static_comparisons). The compact catalog test now rejects items beside a non-array type, as well as untyped item unions.
Risk: This changes only the advertised compact input schemas, and the canonical server-side validation stays the same. The advertisement for the 30 affected tools now rejects elements of the wrong type that it used to accept, which the canonical schema already rejected. No live Gemini or gateway request was run.

Summary

Fixes #5201
Fixes #7103

The remaining Gemini failure in #5201 (the last comment, on functions, actions, static_comparisons) comes from the compact profile's reduced projection. That profile is the default and was the one the reporter selected. The reduced projection gave every array property an "any JSON value" item schema made of a six-member type list with nested items: {}. Gemini's declaration validator only accepts items on a schema whose type is ARRAY. The error path properties[functions].items.items points at exactly that node. The empty-string const/enum trigger in the issue body is already fixed on main: the scan found 0 of them in either profile.

Problem and expected behavior

  • Trigger (seen in a real tools/list over stdio on main 160d821ac, compact profile): build_call_path.functions = {"type":"array","items":{"type":[6 types],"items":{}}}. The same shape appears in capture_browser_scenario.actions, correlate_static_and_runtime.static_comparisons and 43 other array properties.
  • Invariant: no compact advertisement places items beside a type other than the single array type, and no array uses an untyped item union. Every canonical example stays accepted.

Change and scope

  • src/contracts/compactInputSchema.ts (the single owner of the compact projection, used by the CLI/MCP server and catalog identity):
    • arrayItemAlternatives collects the item schemas of every array alternative and resolves $ref. It treats tuples (prefixItems), boolean/absent items and unresolved references as unknown.
    • reducedItemSchema advertises the detected element types:
      • A single type becomes {type: T}.
      • Nested arrays recurse up to depth 3.
      • Several non-array types become a type list without items.
      • Elements that are unknown, tuples, or arrays mixed with other types fall back to {}. That schema accepts every element and still satisfies clients that require items.
    • reducedPropertyTypes: a reduced property that may be an array or another type (for example a nullable array) is advertised as typed anyOf branches, so items never sits beside a type list. The current catalog has no such property.
    • In the inline projection, not: {} keeps the empty schema. Before, it was widened to the any-value list (inspect_python_pyc .else.properties.view.not), which put items beside a type list for no client benefit.
  • The superseded any-value item presentation in the reduced projection is removed. jsonValueSchema is still used for inline unconstrained values at value positions, because of the Kimi type-inference rationale. No current compact tool advertises one, and the new catalog assertion fails if a future tool would.
  • Not included: the full profile. It is opt-in and keeps complete nested guidance. On main it still has 322 untyped item unions and 6 integer const values (compare_web_captures, fingerprint_version/projection_version = 2). This PR does not make full Gemini-compatible.
  • Contribution scope: ordinary fix within existing boundaries; maintainer scope decision not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: compactAdvertisedInputSchema reduced and inline presentation. Consumers are the MCP tools/list registration (EvidenceMcpServerCore) and the compact catalog digest (catalogIdentity). Neither consumer needed changes.

  • Contribution scope fit and linked maintainer decision: not applicable (bug fix to an existing advertisement).

  • CLI and MCP/tool-catalog contract: compact advertised input schemas change for 30 tools, and the compact tools_sha256 changes with them. Canonical input validation, output schemas, descriptions and the full profile are unchanged.

  • Provider, bridge, target-format, or platform compatibility: Gemini-backed clients and gateways, inferred from the reported error path. Copilot's "array type must have items" constraint still holds because every array keeps a truthy items.

  • Evidence, artifact, provenance, or reconstruction contract: none.

  • Target selection, authority, upstream behavior, and cleanup details: not applicable.

  • Generated metadata, package, or installation impact: none committed. docs:check and metadata:check pass.

  • Documentation or usability only; no target interaction.

Evidence and regression coverage

  • Tests added or updated:

    • tests/fixtures/mcpClientSchemaConstraints.ts: new strictGatewayItemSchemaErrors. It reports every items beside a non-array type and every untyped item union in schema positions, and ignores literal data such as default/examples. It replaces the local untypedUnionItems walker in the compact boundary test.
    • tests/boundary/mcp/compactInputSchemaMcp.test.ts: the catalog-wide compact test asserts that the function finds nothing across all advertised tools. A fixture self-test reproduces the exact #5201 shape and error path.
    • src/contracts/compactInputSchema.test.ts: reduced-item cases, each checking that canonical-valid values stay accepted:
      • string, object-union (the reported actions shape), referenced, nested-array, and mixed scalar/object elements;
      • arrays mixed with other element types, tuples, and nullable arrays;
      • the depth bound;
      • inline not: {} parity with the canonical schema.
  • Base reproduction: real rea mcp stdio initialize → tools/list, recursive walk of every inputSchema, 229 tools:

    Shape compact before compact after full (unchanged)
    items beside a non-array type 46 (30 tools) 0 0
    untyped oneOf/anyOf items 0 0 322
    empty-string const/enum 0 0 0
    non-string const/enum 0 0 6

    Source-derived, not executed: on the base revision the new catalog assertion would flag those same 46 compact paths, because the fixture matches the scanned shape.

  • User-visible MCP output (compact): build_call_path.functions, capture_browser_scenario.actions and correlate_static_and_runtime.static_comparisons are now {"type":"array","items":{"type":"object"}}.

  • Catalog size (compact, sum of serialized inputSchema bytes): 121,537 → 118,947 (−2,590 bytes, −2.1%). Full tools/list result: 3,504,973 → 3,502,383 bytes. Token counts were not measured.

  • Remaining proof gaps:

    • No live Gemini API, Antigravity or magpie gateway request was run. Compatibility is derived from the reported error path and Gemini's documented items/ARRAY rule.
    • Kimi and Copilot behavior was not re-verified with real clients. Elements now carry an explicit single type wherever the canonical schema has one; {} is used only for unknown elements, and the current catalog has none.

Validation performed

  • Real stdio tools/list scan of the built server, both profiles, before and after: results in the table above.
  • npm run test:focused -- the 24 MCP boundary files that exercise the compact profile, plus toolSchemaValidity, toolInputReferences and compactInputSchema: 27 files, 291 tests passed. After the final test refactor, compactInputSchema, compactInputSchemaMcp and toolSchemaValidity were rerun: 3 files, 72 tests passed.
  • npm run check:fast: passed.
  • npm run format:check: passed.
  • npm run docs:check: passed (including readmeConsistency, 9 tests).
  • npm run metadata:check: passed.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. The canonical contract is unchanged.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: advertisement-only change; no new I/O.

Author readiness

Human owner: @kaoru0822-kitauji

  • I reviewed the final diff and relevant existing code, understand the change and its 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.

🤖 Generated with Claude Code


🔄 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/7107 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/25/2026 **Status:** ✅ Merged **Merged:** 10/25/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `fix/5201-gemini-schema-shapes` --- ### 📝 Commits (1) - [`10b8d60`](https://github.com/morluto/rea/commit/10b8d6043f7e13321033023801df5768e70efff6) fix(mcp): advertise reduced array elements with Gemini-compatible item types ### 📊 Changes **5 files changed** (+420 additions, -55 deletions) <details> <summary>View changed files</summary> 📝 `docs/mcp-contracts.md` (+10 -0) 📝 `src/contracts/compactInputSchema.test.ts` (+186 -12) 📝 `src/contracts/compactInputSchema.ts` (+143 -9) 📝 `tests/boundary/mcp/compactInputSchemaMcp.test.ts` (+37 -34) 📝 `tests/fixtures/mcpClientSchemaConstraints.ts` (+44 -0) </details> ### 📄 Description **What broke:** On latest main, the default `compact` input profile still advertises 46 array item schemas in 30 tools as `{"type":["null","boolean","object","array","number","string"],"items":{}}`. Gemini rejects `items` beside anything but the single `array` type, which gives the reported `properties[functions].items.items: field predicate failed: $type == Type.ARRAY`. **Fix:** Reduced compact array properties now advertise the element types their canonical item schemas accept (`{"type":"object"}` for `functions`, `actions` and `static_comparisons`). The compact catalog test now rejects `items` beside a non-array type, as well as untyped item unions. **Risk:** This changes only the advertised compact input schemas, and the canonical server-side validation stays the same. The advertisement for the 30 affected tools now rejects elements of the wrong type that it used to accept, which the canonical schema already rejected. No live Gemini or gateway request was run. ## Summary Fixes #5201 Fixes #7103 The remaining Gemini failure in #5201 (the last comment, on `functions`, `actions`, `static_comparisons`) comes from the compact profile's reduced projection. That profile is the default and was the one the reporter selected. The reduced projection gave every array property an "any JSON value" item schema made of a six-member type list with nested `items: {}`. Gemini's declaration validator only accepts `items` on a schema whose type is `ARRAY`. The error path `properties[functions].items.items` points at exactly that node. The empty-string `const`/`enum` trigger in the issue body is already fixed on main: the scan found 0 of them in either profile. ## Problem and expected behavior - Trigger (seen in a real `tools/list` over stdio on main `160d821ac`, compact profile): `build_call_path.functions = {"type":"array","items":{"type":[6 types],"items":{}}}`. The same shape appears in `capture_browser_scenario.actions`, `correlate_static_and_runtime.static_comparisons` and 43 other array properties. - Invariant: no compact advertisement places `items` beside a type other than the single `array` type, and no array uses an untyped item union. Every canonical example stays accepted. ## Change and scope - `src/contracts/compactInputSchema.ts` (the single owner of the compact projection, used by the CLI/MCP server and catalog identity): - `arrayItemAlternatives` collects the item schemas of every array alternative and resolves `$ref`. It treats tuples (`prefixItems`), boolean/absent items and unresolved references as unknown. - `reducedItemSchema` advertises the detected element types: - A single type becomes `{type: T}`. - Nested arrays recurse up to depth 3. - Several non-array types become a type list without `items`. - Elements that are unknown, tuples, or arrays mixed with other types fall back to `{}`. That schema accepts every element and still satisfies clients that require `items`. - `reducedPropertyTypes`: a reduced property that may be an array or another type (for example a nullable array) is advertised as typed `anyOf` branches, so `items` never sits beside a type list. The current catalog has no such property. - In the inline projection, `not: {}` keeps the empty schema. Before, it was widened to the any-value list (`inspect_python_pyc .else.properties.view.not`), which put `items` beside a type list for no client benefit. - The superseded any-value item presentation in the reduced projection is removed. `jsonValueSchema` is still used for inline unconstrained values at value positions, because of the Kimi type-inference rationale. No current compact tool advertises one, and the new catalog assertion fails if a future tool would. - Not included: the `full` profile. It is opt-in and keeps complete nested guidance. On main it still has 322 untyped item unions and 6 integer `const` values (`compare_web_captures`, `fingerprint_version`/`projection_version` = 2). This PR does not make `full` Gemini-compatible. - Contribution scope: ordinary fix within existing boundaries; maintainer scope decision not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: `compactAdvertisedInputSchema` reduced and inline presentation. Consumers are the MCP `tools/list` registration (`EvidenceMcpServerCore`) and the compact catalog digest (`catalogIdentity`). Neither consumer needed changes. - Contribution scope fit and linked maintainer decision: not applicable (bug fix to an existing advertisement). - CLI and MCP/tool-catalog contract: compact advertised input schemas change for 30 tools, and the compact `tools_sha256` changes with them. Canonical input validation, output schemas, descriptions and the `full` profile are unchanged. - Provider, bridge, target-format, or platform compatibility: Gemini-backed clients and gateways, inferred from the reported error path. Copilot's "array type must have items" constraint still holds because every array keeps a truthy `items`. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details: not applicable. - Generated metadata, package, or installation impact: none committed. `docs:check` and `metadata:check` pass. - [x] Documentation or usability only; no target interaction. ## Evidence and regression coverage - Tests added or updated: - `tests/fixtures/mcpClientSchemaConstraints.ts`: new `strictGatewayItemSchemaErrors`. It reports every `items` beside a non-`array` type and every untyped item union in schema positions, and ignores literal data such as `default`/`examples`. It replaces the local `untypedUnionItems` walker in the compact boundary test. - `tests/boundary/mcp/compactInputSchemaMcp.test.ts`: the catalog-wide compact test asserts that the function finds nothing across all advertised tools. A fixture self-test reproduces the exact #5201 shape and error path. - `src/contracts/compactInputSchema.test.ts`: reduced-item cases, each checking that canonical-valid values stay accepted: - string, object-union (the reported `actions` shape), referenced, nested-array, and mixed scalar/object elements; - arrays mixed with other element types, tuples, and nullable arrays; - the depth bound; - inline `not: {}` parity with the canonical schema. - Base reproduction: real `rea mcp` stdio `initialize` → `tools/list`, recursive walk of every `inputSchema`, 229 tools: | Shape | compact before | compact after | full (unchanged) | |---|---|---|---| | `items` beside a non-`array` type | 46 (30 tools) | 0 | 0 | | untyped `oneOf`/`anyOf` items | 0 | 0 | 322 | | empty-string `const`/`enum` | 0 | 0 | 0 | | non-string `const`/`enum` | 0 | 0 | 6 | Source-derived, not executed: on the base revision the new catalog assertion would flag those same 46 compact paths, because the fixture matches the scanned shape. - User-visible MCP output (compact): `build_call_path.functions`, `capture_browser_scenario.actions` and `correlate_static_and_runtime.static_comparisons` are now `{"type":"array","items":{"type":"object"}}`. - Catalog size (compact, sum of serialized `inputSchema` bytes): 121,537 → 118,947 (−2,590 bytes, −2.1%). Full `tools/list` result: 3,504,973 → 3,502,383 bytes. Token counts were not measured. - Remaining proof gaps: - No live Gemini API, Antigravity or magpie gateway request was run. Compatibility is derived from the reported error path and Gemini's documented `items`/`ARRAY` rule. - Kimi and Copilot behavior was not re-verified with real clients. Elements now carry an explicit single type wherever the canonical schema has one; `{}` is used only for unknown elements, and the current catalog has none. ## Validation performed - Real stdio `tools/list` scan of the built server, both profiles, before and after: results in the table above. - `npm run test:focused --` the 24 MCP boundary files that exercise the compact profile, plus `toolSchemaValidity`, `toolInputReferences` and `compactInputSchema`: 27 files, 291 tests passed. After the final test refactor, `compactInputSchema`, `compactInputSchemaMcp` and `toolSchemaValidity` were rerun: 3 files, 72 tests passed. - `npm run check:fast`: passed. - `npm run format:check`: passed. - `npm run docs:check`: passed (including `readmeConsistency`, 9 tests). - `npm run metadata:check`: passed. ## Compatibility, safety, and release - Breaking changes or migration steps: none. The canonical contract is unchanged. - Real Hopper/Ghidra, browser, or OS coverage: not applicable. - Package or release metadata impact: none. - Security, privacy, process, or containment review: advertisement-only change; no new I/O. ## Author readiness Human owner: @kaoru0822-kitauji - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-03 13:39:21 +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#7647
No description provided.