[PR #2905] [CLOSED] feat(mcp): keep compact input schemas within ten nesting levels #5485

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

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2905
Author: @thefolahan
Created: 10/13/2026
Status: ❌ Closed

Base: main ← Head: fix/compact-schema-depth


📝 Commits (4)

  • 35c3900 feat(mcp): keep compact input schemas within ten nesting levels
  • de2c14c fix(mcp): keep depth capping a strict widening for dependent keywords
  • 71c525f fix(mcp): keep capped and reduced compact schemas strictly wider
  • f481e68 fix(mcp): leave root dependency keywords out of capped compact schemas

📊 Changes

6 files changed (+477 additions, -26 deletions)

View changed files

📝 docs/client-configuration.md (+8 -0)
📝 docs/mcp-contracts.md (+18 -2)
📝 src/config/mcpInputSchemaProfile.ts (+1 -1)
📝 src/contracts/compactInputSchema.test.ts (+278 -3)
📝 src/contracts/compactInputSchema.ts (+158 -20)
📝 tests/boundary/mcp/compactInputSchemaMcp.test.ts (+14 -0)

📄 Description

Summary

Fixes #2813. With REA_MCP_INPUT_SCHEMA_PROFILE=compact, every advertised input schema now stays within ten nesting levels, so clients whose model provider rejects deeper schemas (OpenCode in the report) can load REA's whole tool list. The default full profile is unchanged.

Problem and expected behavior

As @ylwl1997 noted on the issue, the compact profile bounded only serialized size, not depth. Measured on current main (7e3a19863) with the issue's own depth function (every JSON object and array level, data included, root at zero), after the compact profile expands references:

Tools Over 10 levels Deepest
full 193 25 31 (compare_web_captures)
compact before 193 17 17 (reconcile_javascript_runtime)
compact after 193 0 10

I left the full profile alone on purpose. #1005 once bounded its depth, and the later refactor in 63bf91405 chose complete producer schemas instead ("complete producer captures can exceed ten structural levels"). The compact profile is already the opt-in for strict clients, so it seemed the right place for this.

Change and scope

compactAdvertisedInputSchema keeps the inline presentation when it is within ten levels. Otherwise it applies capSchemaDepth and reports the new presentation "capped":

  • Every schema level that fits is kept unchanged. A subschema that would pass level ten is advertised as accepting any JSON value, using the explicit type list the profile already uses so Kimi does not infer string. When there is no room for that, the replacement moves up a level.
  • The advertisement only ever accepts more than the canonical schema, so no valid call is rejected by the client. Three keywords need care:
    • oneOf → anyOf when any alternative was widened. A widened alternative can make a value match two alternatives, which oneOf rejects.
    • not is left out rather than widened, because a wider not rejects more.
    • if, together with its then and else, is left out rather than widened, because a wider if applies then to more inputs.
  • The shallow "reduced" fallback is unchanged and still covers schemas that exceed the byte budget.

Exactly the 17 deep tools change, and each keeps its top levels instead of falling back to the shallow form. They also shrink, for example reconcile_javascript_runtime from 14,571 to 4,742 bytes and compare_wasm_interfaces from 14,296 to 4,876. The other 176 compact schemas are byte for byte identical. Canonical server-side validation is untouched, so a call that relies on the elided detail still gets the canonical typed error.

Contract and boundary impact

  • Semantic owner and earliest changed stage: advertised input presentation in src/contracts/compactInputSchema.ts, compact profile only.
  • CLI and MCP/tool-catalog contract: no change to the default catalog. Under compact, 17 input schemas lose detail below level ten. Output schemas, descriptions and annotations are identical.
  • Docs: docs/mcp-contracts.md (compact profile section) and an OpenCode note in docs/client-configuration.md. The profile's configuration error message mentions the depth limit.

Verification

Node 24.21.0 on macOS (Apple silicon):

  • src/contracts/compactInputSchema.test.ts: new depth-cap tests check every case against the original with Ajv 2020. Inputs the original accepts must stay accepted, while shallow structure (required fields, enums, additionalProperties: false) is still enforced. The not and if cases use inputs the original accepts only because a deep part fails to match. I checked that the tests catch regressions by reverting each rule in turn: disabling the oneOf → anyOf conversion fails the unit test and the existing real-catalog test that compact schemas accept every canonical example, and disabling the not/if drop fails both of its cases.
  • tests/boundary/mcp/compactInputSchemaMcp.test.ts: new check that every compact tool schema listed over an MCP transport is at most ten levels deep. The existing budget, validity and canonical-example checks pass.
  • src/contracts, src/config, tests/conformance/contracts, tests/boundary/mcp/toolSchemaValidity.test.ts: 173 passed. docs:check passes.
  • Current main does not compile (src/application/SupportedClients.ts, the new "devin" format missing from the format union), so the catalog build, toolSchemaValidity and docs:check above ran with a local, uncommitted one-word workaround for that line. tsc reports no errors in the changed files.

Not run: OpenCode itself, and the Kimi Code and Copilot client verifiers. @amikishiyev offered to rerun the depth script from the issue against a candidate, which would be a welcome external check.


🔄 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/2905 **Author:** [@thefolahan](https://github.com/thefolahan) **Created:** 10/13/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/compact-schema-depth` --- ### 📝 Commits (4) - [`35c3900`](https://github.com/morluto/rea/commit/35c3900ec0a9e6a384d51e38762ec39117bc8900) feat(mcp): keep compact input schemas within ten nesting levels - [`de2c14c`](https://github.com/morluto/rea/commit/de2c14c001f5bd564bf632abeaabaa532a02b42f) fix(mcp): keep depth capping a strict widening for dependent keywords - [`71c525f`](https://github.com/morluto/rea/commit/71c525f825f4a5ef1f5c7a5c26e2d7ece8bee07e) fix(mcp): keep capped and reduced compact schemas strictly wider - [`f481e68`](https://github.com/morluto/rea/commit/f481e681ca3559d473dcf973febd1f4a13c248ba) fix(mcp): leave root dependency keywords out of capped compact schemas ### 📊 Changes **6 files changed** (+477 additions, -26 deletions) <details> <summary>View changed files</summary> 📝 `docs/client-configuration.md` (+8 -0) 📝 `docs/mcp-contracts.md` (+18 -2) 📝 `src/config/mcpInputSchemaProfile.ts` (+1 -1) 📝 `src/contracts/compactInputSchema.test.ts` (+278 -3) 📝 `src/contracts/compactInputSchema.ts` (+158 -20) 📝 `tests/boundary/mcp/compactInputSchemaMcp.test.ts` (+14 -0) </details> ### 📄 Description ## Summary Fixes #2813. With `REA_MCP_INPUT_SCHEMA_PROFILE=compact`, every advertised input schema now stays within ten nesting levels, so clients whose model provider rejects deeper schemas (OpenCode in the report) can load REA's whole tool list. The default `full` profile is unchanged. ## Problem and expected behavior As @ylwl1997 noted on the issue, the compact profile bounded only serialized size, not depth. Measured on current `main` (`7e3a19863`) with the issue's own depth function (every JSON object and array level, data included, root at zero), after the compact profile expands references: | | Tools | Over 10 levels | Deepest | | --- | ---: | ---: | ---: | | `full` | 193 | 25 | 31 (`compare_web_captures`) | | `compact` before | 193 | 17 | 17 (`reconcile_javascript_runtime`) | | `compact` after | 193 | **0** | **10** | I left the full profile alone on purpose. #1005 once bounded its depth, and the later refactor in `63bf91405` chose complete producer schemas instead ("complete producer captures can exceed ten structural levels"). The compact profile is already the opt-in for strict clients, so it seemed the right place for this. ## Change and scope `compactAdvertisedInputSchema` keeps the inline presentation when it is within ten levels. Otherwise it applies `capSchemaDepth` and reports the new presentation `"capped"`: * Every schema level that fits is kept unchanged. A subschema that would pass level ten is advertised as accepting any JSON value, using the explicit `type` list the profile already uses so Kimi does not infer string. When there is no room for that, the replacement moves up a level. * **The advertisement only ever accepts more than the canonical schema**, so no valid call is rejected by the client. Three keywords need care: * `oneOf` → `anyOf` when any alternative was widened. A widened alternative can make a value match two alternatives, which `oneOf` rejects. * `not` is left out rather than widened, because a wider `not` rejects more. * `if`, together with its `then` and `else`, is left out rather than widened, because a wider `if` applies `then` to more inputs. * The shallow `"reduced"` fallback is unchanged and still covers schemas that exceed the byte budget. Exactly the 17 deep tools change, and each keeps its top levels instead of falling back to the shallow form. They also shrink, for example `reconcile_javascript_runtime` from 14,571 to 4,742 bytes and `compare_wasm_interfaces` from 14,296 to 4,876. The other 176 compact schemas are byte for byte identical. Canonical server-side validation is untouched, so a call that relies on the elided detail still gets the canonical typed error. ## Contract and boundary impact - Semantic owner and earliest changed stage: advertised input presentation in `src/contracts/compactInputSchema.ts`, compact profile only. - CLI and MCP/tool-catalog contract: no change to the default catalog. Under `compact`, 17 input schemas lose detail below level ten. Output schemas, descriptions and annotations are identical. - Docs: `docs/mcp-contracts.md` (compact profile section) and an OpenCode note in `docs/client-configuration.md`. The profile's configuration error message mentions the depth limit. ## Verification Node 24.21.0 on macOS (Apple silicon): - `src/contracts/compactInputSchema.test.ts`: new depth-cap tests check every case against the original with Ajv 2020. Inputs the original accepts must stay accepted, while shallow structure (required fields, enums, `additionalProperties: false`) is still enforced. The `not` and `if` cases use inputs the original accepts only because a deep part fails to match. I checked that the tests catch regressions by reverting each rule in turn: disabling the `oneOf` → `anyOf` conversion fails the unit test and the existing real-catalog test that compact schemas accept every canonical example, and disabling the `not`/`if` drop fails both of its cases. - `tests/boundary/mcp/compactInputSchemaMcp.test.ts`: new check that every compact tool schema listed over an MCP transport is at most ten levels deep. The existing budget, validity and canonical-example checks pass. - `src/contracts`, `src/config`, `tests/conformance/contracts`, `tests/boundary/mcp/toolSchemaValidity.test.ts`: 173 passed. `docs:check` passes. - Current `main` does not compile (`src/application/SupportedClients.ts`, the new `"devin"` format missing from the format union), so the catalog build, `toolSchemaValidity` and `docs:check` above ran with a local, uncommitted one-word workaround for that line. `tsc` reports no errors in the changed files. Not run: OpenCode itself, and the Kimi Code and Copilot client verifiers. @amikishiyev offered to rerun the depth script from the issue against a candidate, which would be a welcome external check. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:07:01 +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#5485
No description provided.