[PR #5203] [CLOSED] fix(mcp): advertise empty-string literals as typed strings #6811

Closed
opened 2026-11-01 21:43:12 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5203
Author: @carcraze
Created: 10/19/2026
Status: ❌ Closed

Base: main ← Head: fix/empty-string-literal-advertisement


📝 Commits (10+)

  • e67890a fix(mcp): advertise empty-string literals as typed strings
  • 5cdee93 test(mcp): share the advertised-schema constraint walk
  • e7e8930 fix(mcp): keep mixed enum member types when widening empty strings
  • c3c7deb fix(mcp): drop negative constraints that reference widened literals
  • df7c1c6 fix(mcp): carry the reference cycle guard across normalization
  • 47a6742 fix(mcp): relax referenced unions and resolve array refs
  • 9ca46bf fix(mcp): cover referenced contains groups and encoded ref tokens
  • c395352 fix(mcp): decode fragments before splitting and normalize referenced targets
  • 25e7a96 fix(mcp): consolidate local reference resolution and target normalization
  • 0cc371b fix(mcp): keep nested resources opaque to outer reference resolution

📊 Changes

5 files changed (+135 additions, -65 deletions)

View changed files

📝 src/contracts/inputSchemaPresentation.ts (+24 -4)
📝 src/contracts/jsonSchemaKeywords.ts (+1 -0)
📝 src/contracts/toolInputReferences.test.ts (+59 -0)
📝 tests/boundary/mcp/toolSchemaValidity.test.ts (+14 -23)
📝 tests/fixtures/mcpClientSchemaConstraints.ts (+37 -38)

📄 Description

Summary

Fixes #5201.

REA still advertises one empty-string const (trace_application_feature.seed.value). Clients that render const as a one-member enum send enum: [""] to Gemini, which rejects empty enum members and fails the whole tool catalog. The advertised projection now presents empty-string literals as type: string; canonical Zod validation is unchanged.

Problem and expected behavior

Trigger: connect REA 6.7.0 over MCP to a Gemini-backed client (reported with opencode 1.18.32). trace_application_feature.seed projects value.const: "" in anyOf[1/2/3]; the client renders enum: [""]; Google returns ...enum[0]: cannot be empty, rejecting every tool.

Expected: every advertised input schema survives every supported dialect, as established for dependentRequired (#2832) and boolean literals (#4025).

Change and scope

Merged main (46cc5ce05 and 88e37806) and retained the remaining literal-widening and client-constraint edge cases for #5201:

  • src/contracts/inputSchemaPresentation.ts: preserve every primitive member type via literalSchemaType when widening a mixed enum that contains "" (presenting anyOf of primitive types rather than an untyped schema).
  • src/contracts/jsonSchemaKeywords.ts: include contentSchema in schemaChildren so schema-valued contentSchema subschemas are traversed during literal normalization and reference checks.
  • tests/fixtures/mcpClientSchemaConstraints.ts & tests/boundary/mcp/toolSchemaValidity.test.ts: share schemaChildEntries so array-valued schemaChildren (such as tuple items) and contentSchema are checked by unsupportedInputLiteralSchemaError, and consume the shared jsonSchemaKeywords registry in localInputReferences.
  • src/contracts/toolInputReferences.test.ts: regression coverage for mixed-enum widening, contentSchema literal widening, and the recorded applicationFeatureSeedSchema trace seed shape.

Contract and boundary impact

  • Semantic owner and earliest changed stage: MCP input-schema advertisement (presentInputJsonSchema); no runtime validation change.
  • CLI and MCP/tool-catalog contract: advertised schemas only; canonical contracts unchanged; the generated test catalog is ignored and regenerated by CI.
  • Provider, bridge, target-format, or platform compatibility: none.
  • 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 (generated catalog is ignored; no package metadata change).

Expected effects:

  • Documentation or usability only; no target interaction.

Evidence and regression coverage

  • Tests added or updated: emptyStringLiteralSchemaError in the catalog-wide check used by both profile suites; unit tests for const: "", enum: ["", ...], oneOf relaxation, and the recorded trace seed.
  • Base reproduction or other evidence: stdio tools/list scan of rea-agents@6.7.0 finds exactly 1 tool / 3 empty-string consts; the client error path matches seed -> any_of[1/2/3] -> value -> enum[0].
  • User-visible CLI/MCP output: the patched local server scans clean — tools=207 tools_with_hits=0 total_hits=0 in both full and compact.
  • Remaining proof gaps: not executed against the live Google API from this environment; the schema-level evidence and loopback/client-constraint coverage validate the advertised shapes.

Validation performed

  • npx vitest run src/contracts/toolInputReferences.test.ts — 37 passed
  • npx vitest run tests/boundary/mcp/compactInputSchemaMcp.test.ts tests/boundary/mcp/toolSchemaValidity.test.ts — 28 passed
  • npm run check:fast (tsc --noEmit, oxlint, verify:module-boundaries) & npm run format:check — clean (0 errors)
  • npm run compile — passed; node scripts/generate-mcp-tool-catalog.mjs — passed
  • stdio tools/list scan of the patched server — 0 non-string/empty-string consts or enums in both profiles
  • End to end: a Gemini-family CLI wired to the patched server loads the full tool catalog and invokes REA tools successfully

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: none.

Author readiness

Human owner: @carcraze

  • 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.

Draft while the human owner completes these declarations. AI assistance is disclosed; the declarations are the author's to complete.

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.

🔄 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/5203 **Author:** [@carcraze](https://github.com/carcraze) **Created:** 10/19/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/empty-string-literal-advertisement` --- ### 📝 Commits (10+) - [`e67890a`](https://github.com/morluto/rea/commit/e67890a3e0c115e43702e5ba9153bd9571606f99) fix(mcp): advertise empty-string literals as typed strings - [`5cdee93`](https://github.com/morluto/rea/commit/5cdee9391c6cd3d695e54fe53115138165c98fb3) test(mcp): share the advertised-schema constraint walk - [`e7e8930`](https://github.com/morluto/rea/commit/e7e8930b39da97a3dc114125a02e06ad7c6d7b45) fix(mcp): keep mixed enum member types when widening empty strings - [`c3c7deb`](https://github.com/morluto/rea/commit/c3c7debcc15f22b3a45abcab960636f65672486e) fix(mcp): drop negative constraints that reference widened literals - [`df7c1c6`](https://github.com/morluto/rea/commit/df7c1c6da5af02fbe2ac54acccc5e8f6c01c6c10) fix(mcp): carry the reference cycle guard across normalization - [`47a6742`](https://github.com/morluto/rea/commit/47a674298d0cb2a2ecb2f5c62ebe9936cde49507) fix(mcp): relax referenced unions and resolve array refs - [`9ca46bf`](https://github.com/morluto/rea/commit/9ca46bf783c55eea74655c2ca6138eb84293f42d) fix(mcp): cover referenced contains groups and encoded ref tokens - [`c395352`](https://github.com/morluto/rea/commit/c395352cec077b645015d37d35303ea0d1fe1bee) fix(mcp): decode fragments before splitting and normalize referenced targets - [`25e7a96`](https://github.com/morluto/rea/commit/25e7a96c4b265bde64c046cf4d34c2ccf8fbd2ea) fix(mcp): consolidate local reference resolution and target normalization - [`0cc371b`](https://github.com/morluto/rea/commit/0cc371b0c55f0397e272f0cfb59612bbf7193a5e) fix(mcp): keep nested resources opaque to outer reference resolution ### 📊 Changes **5 files changed** (+135 additions, -65 deletions) <details> <summary>View changed files</summary> 📝 `src/contracts/inputSchemaPresentation.ts` (+24 -4) 📝 `src/contracts/jsonSchemaKeywords.ts` (+1 -0) 📝 `src/contracts/toolInputReferences.test.ts` (+59 -0) 📝 `tests/boundary/mcp/toolSchemaValidity.test.ts` (+14 -23) 📝 `tests/fixtures/mcpClientSchemaConstraints.ts` (+37 -38) </details> ### 📄 Description ## Summary Fixes #5201. REA still advertises one empty-string `const` (`trace_application_feature.seed.value`). Clients that render `const` as a one-member `enum` send `enum: [""]` to Gemini, which rejects empty enum members and fails the whole tool catalog. The advertised projection now presents empty-string literals as `type: string`; canonical Zod validation is unchanged. ## Problem and expected behavior Trigger: connect REA 6.7.0 over MCP to a Gemini-backed client (reported with opencode 1.18.32). `trace_application_feature.seed` projects `value.const: ""` in `anyOf[1/2/3]`; the client renders `enum: [""]`; Google returns `...enum[0]: cannot be empty`, rejecting every tool. Expected: every advertised input schema survives every supported dialect, as established for `dependentRequired` (#2832) and boolean literals (#4025). ## Change and scope Merged `main` (`46cc5ce05` and `88e37806`) and retained the remaining literal-widening and client-constraint edge cases for #5201: - `src/contracts/inputSchemaPresentation.ts`: preserve every primitive member type via `literalSchemaType` when widening a mixed `enum` that contains `""` (presenting `anyOf` of primitive types rather than an untyped schema). - `src/contracts/jsonSchemaKeywords.ts`: include `contentSchema` in `schemaChildren` so schema-valued `contentSchema` subschemas are traversed during literal normalization and reference checks. - `tests/fixtures/mcpClientSchemaConstraints.ts` & `tests/boundary/mcp/toolSchemaValidity.test.ts`: share `schemaChildEntries` so array-valued `schemaChildren` (such as tuple `items`) and `contentSchema` are checked by `unsupportedInputLiteralSchemaError`, and consume the shared `jsonSchemaKeywords` registry in `localInputReferences`. - `src/contracts/toolInputReferences.test.ts`: regression coverage for mixed-enum widening, `contentSchema` literal widening, and the recorded `applicationFeatureSeedSchema` trace seed shape. ## Contract and boundary impact - Semantic owner and earliest changed stage: MCP input-schema advertisement (`presentInputJsonSchema`); no runtime validation change. - CLI and MCP/tool-catalog contract: advertised schemas only; canonical contracts unchanged; the generated test catalog is ignored and regenerated by CI. - Provider, bridge, target-format, or platform compatibility: none. - 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 (generated catalog is ignored; no package metadata change). Expected effects: - [x] Documentation or usability only; no target interaction. ## Evidence and regression coverage - Tests added or updated: `emptyStringLiteralSchemaError` in the catalog-wide check used by both profile suites; unit tests for `const: ""`, `enum: ["", ...]`, `oneOf` relaxation, and the recorded trace seed. - Base reproduction or other evidence: stdio `tools/list` scan of `rea-agents@6.7.0` finds exactly 1 tool / 3 empty-string consts; the client error path matches `seed -> any_of[1/2/3] -> value -> enum[0]`. - User-visible CLI/MCP output: the patched local server scans clean — `tools=207 tools_with_hits=0 total_hits=0` in both `full` and `compact`. - Remaining proof gaps: not executed against the live Google API from this environment; the schema-level evidence and loopback/client-constraint coverage validate the advertised shapes. ## Validation performed - `npx vitest run src/contracts/toolInputReferences.test.ts` — 37 passed - `npx vitest run tests/boundary/mcp/compactInputSchemaMcp.test.ts tests/boundary/mcp/toolSchemaValidity.test.ts` — 28 passed - `npm run check:fast` (`tsc --noEmit`, `oxlint`, `verify:module-boundaries`) & `npm run format:check` — clean (0 errors) - `npm run compile` — passed; `node scripts/generate-mcp-tool-catalog.mjs` — passed - stdio `tools/list` scan of the patched server — 0 non-string/empty-string consts or enums in both profiles - End to end: a Gemini-family CLI wired to the patched server loads the full tool catalog and invokes REA tools successfully ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: not applicable. - Package or release metadata impact: none. - Security, privacy, process, or containment review: none. ## Author readiness Human owner: @carcraze - [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. Draft while the human owner completes these declarations. AI assistance is disclosed; the declarations are the author's to complete. ## 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. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:43:12 +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#6811
No description provided.