[PR #7054] [CLOSED] feat(mcp): advertise bounded output schemas in a compact profile #7609

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

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/7054
Author: @akshad-exe
Created: 10/25/2026
Status: ❌ Closed

Base: main ← Head: feat/mcp-compact-output-schema


📝 Commits (3)

  • 5598880 feat(mcp): advertise bounded output schemas in a compact profile
  • 1a901ed test(mcp): validate compact output advertisements as clients receive them
  • 8ab20ab test(mcp): cover canonical rejection behind a reduced output advertisement

📊 Changes

20 files changed (+1474 additions, -771 deletions)

View changed files

📝 README.md (+6 -0)
📝 docs/client-configuration.md (+7 -4)
📝 docs/mcp-contracts.md (+29 -13)
📝 scripts/record-catalog-digests.mjs (+1 -1)
📝 src/catalogIdentity.ts (+37 -7)
📝 src/config/mcpOutputSchemaProfile.ts (+22 -2)
📝 src/contracts/compactInputSchema.ts (+21 -600)
➕ src/contracts/compactJsonSchema.ts (+597 -0)
➕ src/contracts/compactOutputSchema.test.ts (+171 -0)
➕ src/contracts/compactOutputSchema.ts (+95 -0)
📝 src/recordedCatalogDigests.ts (+1 -1)
📝 src/server/DeferredEvidenceMcpServer.ts (+2 -1)
📝 src/server/EvidenceMcpServer.ts (+5 -7)
📝 src/server/EvidenceMcpServerCore.ts (+52 -14)
📝 src/server/createServer.ts (+17 -15)
📝 tests/boundary/mcp/mcpDoctor.test.ts (+60 -50)
📝 tests/boundary/mcp/outputSchemaProfile.test.ts (+238 -9)
📝 tests/boundary/mcp/toolSchemaValidity.test.ts (+7 -47)
➕ tests/conformance/contracts/mcpOutputSchemaProfile.test.ts (+43 -0)
➕ tests/fixtures/mcpAdvertisedSchemaValidity.ts (+63 -0)

📄 Description

Summary

Adds the opt-in REA_MCP_OUTPUT_SCHEMA_PROFILE=compact profile that #2512
proposed next to off. It bounds every advertised output schema to 4,096 bytes
so a client with a one-megabyte stdio line cap can finish tools/list, while
REA keeps validating each result against the canonical contract and still
returns complete structuredContent. full stays the default and off is
unchanged. Fixes #2512.

Problem and expected behavior

REA 6.4.0's 170-tool tools/list is a single 1,954,727-byte line, 76% of it
outputSchema. Hosts that reject a line over 1 MiB (pi-web-ui 0.104.4) treat
the frame as a protocol error and load 0 tools; REA_MCP_INPUT_SCHEMA_PROFILE=compact
(the precedent from #1484) only
reduces it to 1,772,799 bytes. The issue asks for a switch that trims or omits
the advertised output schemas and brings the 170-tool frame under 1 MB.

Change and scope

  • REA_MCP_OUTPUT_SCHEMA_PROFILE accepts full (default), compact, or off;
    compact bounds each advertised output schema to 4,096 bytes.
  • A contract that fits keeps its complete validation structure. An oversized one
    advertises root property names, reference-resolved first-level types and
    guidance, and the canonical required/additionalProperties/minProperties
    keywords, with nested detail elided. Local references are inlined and the
    content-bound $id/$anchor are dropped, so the advertised document is
    self-contained and does not claim the canonical contract's identity.
  • The projection only widens: a valid canonical result always satisfies it. The
    registered canonical schema, result validation, and delivered output are
    unchanged. A client that validates against the advertisement can accept a
    result the canonical contract rejects, which is the same trade-off the
    compact input schema profile
    already documents.
  • The shared projection primitives moved to src/contracts/compactJsonSchema.ts
    (the input profile's behavior and its 27 tests are unchanged), a compact
    digest is recorded per input profile, and rea mcp doctor verifies the
    selected advertisement through production stdio and both CLI inventories.
  • Each projection is memoised by the SDK-converted schema object, so repeated
    tools/list responses reuse it.
  • Not included: tools/list pagination (the alternative the issue mentions),
    any change to result delivery or response budgets, and any change to the
    default profile.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/config/mcpOutputSchemaProfile.ts
    selects the profile at configuration/composition; src/contracts/compactOutputSchema.ts
    owns the projection; src/server/EvidenceMcpServerCore.ts applies it to the
    client-facing tools/list result only.
  • Contribution scope fit and linked maintainer decision: within the
    contribution scope.
    No new provider, target execution, injection, mutation, or protection-recovery
    capability. The values and behavior are the ones proposed in #2512; a separate
    maintainer scope decision is not applicable.
  • CLI and MCP/tool-catalog contract: new accepted environment value and a
    distinct catalog identity for the advertised form; unchanged tool set,
    unchanged canonical schemas, unchanged results. Existing full/off
    identities and digests are unchanged.
  • Provider, bridge, target-format, or platform compatibility: none.
  • Evidence, artifact, provenance, or reconstruction contract: unchanged.
  • Target selection, authority, upstream behavior, and cleanup details: not
    applicable.
  • Generated metadata: none committed; recordedCatalogDigests is regenerated at
    build time. Authored docs updated in README.md, docs/mcp-contracts.md
    (Output schemas), and
    docs/client-configuration.md.

Expected effects:

  • 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/compactOutputSchema.test.ts (projection,
    identity removal, widening, budget guarantee, determinism);
    tests/boundary/mcp/outputSchemaProfile.test.ts (full/compact/off over real MCP
    transports, canonical validation still rejects invalid output, per-schema and
    whole-frame size bounds across the full contract catalog, every advertised
    compact schema validated after SDK conversion, and a reduced advertisement that
    accepts a value the canonical contract rejects);
    tests/conformance/contracts/mcpOutputSchemaProfile.test.ts (parser and
    selection); tests/boundary/mcp/mcpDoctor.test.ts (compact through production
    stdio, both CLI inventories, and the recorded digest module).
  • Base reproduction: captured 6.4.0's real tools/list over stdio and applied
    the new projection to those exact advertised schemas (below).
  • User-visible MCP output: tools/list result size and per-schema bound, plus
    rea mcp doctor reporting the compact catalog as aligned.
  • Remaining proof gaps: no live run against pi-web-ui or another 1 MiB-capped
    host; the frame measurements come from REA's own stdio server. Windows/macOS
    were not exercised.

Measured tools/list from the built branch CLI on current main (229 tools):

input output bytes ≈tokens
full full 4,233,822 1,295,371
compact (default) full 3,505,014 1,079,144
full compact 1,314,854 382,501
compact (default) compact 590,841 168,100
compact (default) off 304,392 84,295

One concrete case: inspect_analysis_view's 381,121-byte output contract is
advertised at 1,327 bytes with all 17 root properties and their first-level
types retained.

The issue's 170-tool 6.4.0 catalog, projected from its captured wire schemas:

input output bytes ≈tokens
full full 1,954,693 595,271
full compact 659,170 195,733
compact full 1,772,772 538,029
compact compact 477,249 138,491

Success criterion: the 170-tool frame is under 1 MB with compact even when
input schemas stay full. With full input on the current 229-tool catalog the
frame stays above 1 MiB, so the docs pair compact output with the default
compact input.

Repeated tools/list in one session: 163 ms per response before memoising the
projections, 11 ms after (the first listing pays the one-time projection).

Representative output with REA_MCP_OUTPUT_SCHEMA_PROFILE=compact
(rea mcp doctor --json, production stdio):

{"healthy":true,"adapter":"production-stdio",
 "inventory":{"tools":{"expected":229,"observed":229,"missing":[],"unexpected":[],"duplicates":[]}},
 "identity":{"expected_catalog_digest":"f7b31b04626254d986da64304427233142dbc18ee9bcb7fb0bc26e882136166c",
             "observed_catalog_digest":"f7b31b04626254d986da64304427233142dbc18ee9bcb7fb0bc26e882136166c"},
 "checks":[{"name":"initialize","ok":true},{"name":"inventory:tools","ok":true},
           {"name":"server-identity","ok":true},{"name":"request-flow","ok":true}]}

The complete structure survives when it fits; resume_frida_session (183 B,
verbatim):

{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object",
 "properties":{"state":{"type":"string","const":"running"}},
 "required":["state"],"additionalProperties":false}

comment exceeds the budget, so it advertises the reduced root shape: 17
required root properties, nested detail elided, and no content-bound $id.

Validation performed

  • npm run check:fast — typecheck and lint pass on the rebased branch.
  • npm run test:focused — 84 passed across
    src/contracts/compactOutputSchema.test.ts,
    src/contracts/compactInputSchema.test.ts,
    tests/boundary/mcp/outputSchemaProfile.test.ts,
    tests/boundary/mcp/mcpDoctor.test.ts,
    tests/conformance/contracts/mcpOutputSchemaProfile.test.ts, and
    tests/boundary/mcp/serverIdentity.test.ts.
  • npm run verify:test-discovery — all 2,468 test files discovered exactly once.
  • npm run docs:check — pass.
  • Existing profile identities: the recorded digest module was byte-identical
    between a be2811e41 build and this branch for full/full, full/off,
    compact/full, and compact/off. The projection only rewrites the catalog
    when the output profile is not full, so those four profiles keep the
    unchanged code path.
  • Real stdio probes against the built CLI for every profile pair in the tables
    above, and against npx rea-agents@6.4.0 mcp for the issue's catalog.
  • The complete npm run check:pr suite had not finished locally when this PR
    opened, and CI has not executed yet: GitHub holds pull_request workflows
    from first-time fork contributors until a maintainer approves the run. The
    focused runs above are the evidence available at this point; treat the full
    suite as pending, not passing.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. full remains the default and the
    four existing profile identities are unchanged; compact is a new opt-in.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable; no provider or
    target interaction.
  • Package or release metadata impact: none beyond the documented environment
    variable.
  • Security, privacy, process, or containment review: the profile only changes
    what the server advertises; it adds no execution, injection, network, or
    filesystem behavior. Result validation and response budgets are unchanged.

Author readiness

Human owner: @akshad-exe

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

🔄 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/7054 **Author:** [@akshad-exe](https://github.com/akshad-exe) **Created:** 10/25/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `feat/mcp-compact-output-schema` --- ### 📝 Commits (3) - [`5598880`](https://github.com/morluto/rea/commit/55988809afca59d3a6b0cbdbe5e2b431ffe07571) feat(mcp): advertise bounded output schemas in a compact profile - [`1a901ed`](https://github.com/morluto/rea/commit/1a901edecbffb51d7f4f49f9d661e89a728ce749) test(mcp): validate compact output advertisements as clients receive them - [`8ab20ab`](https://github.com/morluto/rea/commit/8ab20ab8f7e484269b92cdd767bafdb036409bc5) test(mcp): cover canonical rejection behind a reduced output advertisement ### 📊 Changes **20 files changed** (+1474 additions, -771 deletions) <details> <summary>View changed files</summary> 📝 `README.md` (+6 -0) 📝 `docs/client-configuration.md` (+7 -4) 📝 `docs/mcp-contracts.md` (+29 -13) 📝 `scripts/record-catalog-digests.mjs` (+1 -1) 📝 `src/catalogIdentity.ts` (+37 -7) 📝 `src/config/mcpOutputSchemaProfile.ts` (+22 -2) 📝 `src/contracts/compactInputSchema.ts` (+21 -600) ➕ `src/contracts/compactJsonSchema.ts` (+597 -0) ➕ `src/contracts/compactOutputSchema.test.ts` (+171 -0) ➕ `src/contracts/compactOutputSchema.ts` (+95 -0) 📝 `src/recordedCatalogDigests.ts` (+1 -1) 📝 `src/server/DeferredEvidenceMcpServer.ts` (+2 -1) 📝 `src/server/EvidenceMcpServer.ts` (+5 -7) 📝 `src/server/EvidenceMcpServerCore.ts` (+52 -14) 📝 `src/server/createServer.ts` (+17 -15) 📝 `tests/boundary/mcp/mcpDoctor.test.ts` (+60 -50) 📝 `tests/boundary/mcp/outputSchemaProfile.test.ts` (+238 -9) 📝 `tests/boundary/mcp/toolSchemaValidity.test.ts` (+7 -47) ➕ `tests/conformance/contracts/mcpOutputSchemaProfile.test.ts` (+43 -0) ➕ `tests/fixtures/mcpAdvertisedSchemaValidity.ts` (+63 -0) </details> ### 📄 Description ## Summary Adds the opt-in `REA_MCP_OUTPUT_SCHEMA_PROFILE=compact` profile that #2512 proposed next to `off`. It bounds every advertised output schema to 4,096 bytes so a client with a one-megabyte stdio line cap can finish `tools/list`, while REA keeps validating each result against the canonical contract and still returns complete `structuredContent`. `full` stays the default and `off` is unchanged. Fixes #2512. ## Problem and expected behavior REA 6.4.0's 170-tool `tools/list` is a single 1,954,727-byte line, 76% of it `outputSchema`. Hosts that reject a line over 1 MiB (pi-web-ui 0.104.4) treat the frame as a protocol error and load 0 tools; `REA_MCP_INPUT_SCHEMA_PROFILE=compact` (the [precedent from #1484](https://github.com/morluto/rea/issues/1484)) only reduces it to 1,772,799 bytes. The issue asks for a switch that trims or omits the advertised output schemas and brings the 170-tool frame under 1 MB. ## Change and scope - `REA_MCP_OUTPUT_SCHEMA_PROFILE` accepts `full` (default), `compact`, or `off`; `compact` bounds each advertised output schema to 4,096 bytes. - A contract that fits keeps its complete validation structure. An oversized one advertises root property names, reference-resolved first-level types and guidance, and the canonical `required`/`additionalProperties`/`minProperties` keywords, with nested detail elided. Local references are inlined and the content-bound `$id`/`$anchor` are dropped, so the advertised document is self-contained and does not claim the canonical contract's identity. - The projection only widens: a valid canonical result always satisfies it. The registered canonical schema, result validation, and delivered output are unchanged. A client that validates against the advertisement can accept a result the canonical contract rejects, which is the same trade-off the [compact input schema profile](docs/mcp-contracts.md#compact-input-schema-profile) already documents. - The shared projection primitives moved to `src/contracts/compactJsonSchema.ts` (the input profile's behavior and its 27 tests are unchanged), a compact digest is recorded per input profile, and `rea mcp doctor` verifies the selected advertisement through production stdio and both CLI inventories. - Each projection is memoised by the SDK-converted schema object, so repeated `tools/list` responses reuse it. - Not included: `tools/list` pagination (the alternative the issue mentions), any change to result delivery or response budgets, and any change to the default profile. ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/config/mcpOutputSchemaProfile.ts` selects the profile at configuration/composition; `src/contracts/compactOutputSchema.ts` owns the projection; `src/server/EvidenceMcpServerCore.ts` applies it to the client-facing `tools/list` result only. - Contribution scope fit and linked maintainer decision: within the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). No new provider, target execution, injection, mutation, or protection-recovery capability. The values and behavior are the ones proposed in #2512; a separate maintainer scope decision is not applicable. - CLI and MCP/tool-catalog contract: new accepted environment value and a distinct catalog identity for the advertised form; unchanged tool set, unchanged canonical schemas, unchanged results. Existing `full`/`off` identities and digests are unchanged. - Provider, bridge, target-format, or platform compatibility: none. - Evidence, artifact, provenance, or reconstruction contract: unchanged. - Target selection, authority, upstream behavior, and cleanup details: not applicable. - Generated metadata: none committed; `recordedCatalogDigests` is regenerated at build time. Authored docs updated in `README.md`, `docs/mcp-contracts.md` ([Output schemas](docs/mcp-contracts.md#output-schemas)), and `docs/client-configuration.md`. Expected effects: - [ ] 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. - [x] 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/compactOutputSchema.test.ts` (projection, identity removal, widening, budget guarantee, determinism); `tests/boundary/mcp/outputSchemaProfile.test.ts` (full/compact/off over real MCP transports, canonical validation still rejects invalid output, per-schema and whole-frame size bounds across the full contract catalog, every advertised compact schema validated after SDK conversion, and a reduced advertisement that accepts a value the canonical contract rejects); `tests/conformance/contracts/mcpOutputSchemaProfile.test.ts` (parser and selection); `tests/boundary/mcp/mcpDoctor.test.ts` (compact through production stdio, both CLI inventories, and the recorded digest module). - Base reproduction: captured 6.4.0's real `tools/list` over stdio and applied the new projection to those exact advertised schemas (below). - User-visible MCP output: `tools/list` result size and per-schema bound, plus `rea mcp doctor` reporting the compact catalog as aligned. - Remaining proof gaps: no live run against pi-web-ui or another 1 MiB-capped host; the frame measurements come from REA's own stdio server. Windows/macOS were not exercised. Measured `tools/list` from the built branch CLI on current `main` (229 tools): | input | output | bytes | ≈tokens | | --- | --- | ---: | ---: | | full | full | 4,233,822 | 1,295,371 | | compact (default) | full | 3,505,014 | 1,079,144 | | full | compact | 1,314,854 | 382,501 | | compact (default) | compact | 590,841 | 168,100 | | compact (default) | off | 304,392 | 84,295 | One concrete case: `inspect_analysis_view`'s 381,121-byte output contract is advertised at 1,327 bytes with all 17 root properties and their first-level types retained. The issue's 170-tool 6.4.0 catalog, projected from its captured wire schemas: | input | output | bytes | ≈tokens | | --- | --- | ---: | ---: | | full | full | 1,954,693 | 595,271 | | full | compact | 659,170 | 195,733 | | compact | full | 1,772,772 | 538,029 | | compact | compact | 477,249 | 138,491 | Success criterion: the 170-tool frame is under 1 MB with `compact` even when input schemas stay `full`. With full input on the current 229-tool catalog the frame stays above 1 MiB, so the docs pair `compact` output with the default compact input. Repeated `tools/list` in one session: 163 ms per response before memoising the projections, 11 ms after (the first listing pays the one-time projection). Representative output with `REA_MCP_OUTPUT_SCHEMA_PROFILE=compact` (`rea mcp doctor --json`, production stdio): ```json {"healthy":true,"adapter":"production-stdio", "inventory":{"tools":{"expected":229,"observed":229,"missing":[],"unexpected":[],"duplicates":[]}}, "identity":{"expected_catalog_digest":"f7b31b04626254d986da64304427233142dbc18ee9bcb7fb0bc26e882136166c", "observed_catalog_digest":"f7b31b04626254d986da64304427233142dbc18ee9bcb7fb0bc26e882136166c"}, "checks":[{"name":"initialize","ok":true},{"name":"inventory:tools","ok":true}, {"name":"server-identity","ok":true},{"name":"request-flow","ok":true}]} ``` The complete structure survives when it fits; `resume_frida_session` (183 B, verbatim): ```json {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object", "properties":{"state":{"type":"string","const":"running"}}, "required":["state"],"additionalProperties":false} ``` `comment` exceeds the budget, so it advertises the reduced root shape: 17 required root properties, nested detail elided, and no content-bound `$id`. ## Validation performed - `npm run check:fast` — typecheck and lint pass on the rebased branch. - `npm run test:focused` — 84 passed across `src/contracts/compactOutputSchema.test.ts`, `src/contracts/compactInputSchema.test.ts`, `tests/boundary/mcp/outputSchemaProfile.test.ts`, `tests/boundary/mcp/mcpDoctor.test.ts`, `tests/conformance/contracts/mcpOutputSchemaProfile.test.ts`, and `tests/boundary/mcp/serverIdentity.test.ts`. - `npm run verify:test-discovery` — all 2,468 test files discovered exactly once. - `npm run docs:check` — pass. - Existing profile identities: the recorded digest module was byte-identical between a `be2811e41` build and this branch for `full/full`, `full/off`, `compact/full`, and `compact/off`. The projection only rewrites the catalog when the output profile is not `full`, so those four profiles keep the unchanged code path. - Real stdio probes against the built CLI for every profile pair in the tables above, and against `npx rea-agents@6.4.0 mcp` for the issue's catalog. - The complete `npm run check:pr` suite had not finished locally when this PR opened, and CI has not executed yet: GitHub holds `pull_request` workflows from first-time fork contributors until a maintainer approves the run. The focused runs above are the evidence available at this point; treat the full suite as pending, not passing. ## Compatibility, safety, and release - Breaking changes or migration steps: none. `full` remains the default and the four existing profile identities are unchanged; `compact` is a new opt-in. - Real Hopper/Ghidra, browser, or OS coverage: not applicable; no provider or target interaction. - Package or release metadata impact: none beyond the documented environment variable. - Security, privacy, process, or containment review: the profile only changes what the server advertises; it adds no execution, injection, network, or filesystem behavior. Result validation and response budgets are unchanged. ## Author readiness Human owner: @akshad-exe - [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. --- <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:05 +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#7609
No description provided.