[GH-ISSUE #3331] [Bug] WebMCP schema keywords appear as input field names #1203

Closed
opened 2026-10-15 18:31:58 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @coygeek on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3331

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

analyze-web-bundle reports JSON Schema keywords as WebMCP input property names when a declaration has no literal properties object. A valid schema with only type: "object" produces schema_property_names: ["type"]; a schema whose properties come from an identifier produces ["properties", "type"]. These names describe the schema itself rather than its declared input fields. All three public CLI calls in the reproduction exit with status 0; the literal-properties control correctly reports actual.

Reproduction

  1. Start from a clean checkout at the baseline and install the locked dependencies, then build the runtime. The preparation commands below describe clean-checkout setup; the reported reproduction used an already-built runtime.
git clone https://github.com/morluto/rea.git rea-repro
cd rea-repro
git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. From the repository root, copy the existing local CDP fixture into an isolated directory and run the following script. The script provides all three captured JavaScript inputs inline. Type stripping only prepares the fixture; production analysis uses the built CLI.
work="$(mktemp -d)"
mkdir "$work/home"
node --input-type=module - "$work" <<'JS'
import { readFileSync, writeFileSync, symlinkSync } from "node:fs";
import { stripTypeScriptTypes } from "node:module";
const work = process.argv[2];
for (const name of ["fakeCdpBrowser", "fakeCdpBrowserEvents", "fakeCdpBrowserResults", "fakeCdpBrowserTargets"]) {
  writeFileSync(`${work}/${name}.js`, stripTypeScriptTypes(
    readFileSync(`tests/fixtures/${name}.ts`, "utf8")));
}
writeFileSync(`${work}/package.json`, '{"type":"module"}');
symlinkSync(`${process.cwd()}/node_modules`, `${work}/node_modules`);
JS
cat > "$work/probe.mjs" <<'JS'
import { startFakeCdpBrowser } from "./fakeCdpBrowser.js";
import { spawn } from "node:child_process";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
const work = dirname(fileURLToPath(import.meta.url));
const cases = {
  empty: 'document.modelContext.registerTool({name:"lookup",inputSchema:{type:"object"}});',
  dynamic: 'const fields={actual:{type:"string"}};document.modelContext.registerTool({name:"lookup",inputSchema:{type:"object",properties:fields}});',
  control: 'document.modelContext.registerTool({name:"lookup",inputSchema:{type:"object",properties:{actual:{type:"string"}}}});'
};
for (const [name, scriptSource] of Object.entries(cases)) {
  const fake = await startFakeCdpBrowser({
    commandResult: command => command.method === "Debugger.getScriptSource"
      ? { scriptSource } : undefined
  });
  try {
    const child = spawn(process.execPath, ["scripts/rea.mjs", "analyze-web-bundle",
      fake.endpoint, "allowed-page", "--observation-ms", "1", "--json"],
      { env: { ...process.env, HOME: `${work}/home` } });
    let output = "";
    child.stdout.on("data", chunk => { output += chunk; });
    child.stderr.pipe(process.stderr);
    const exit = await new Promise(resolve => child.on("exit", resolve));
    const result = JSON.parse(output).normalized_result;
    console.log(JSON.stringify({ name, exit,
      declarations: result.observations.webmcp_declarations.map(
        ({ name, schema_property_names }) => ({ name, schema_property_names })) }));
  } finally {
    await fake.close();
  }
}
JS
node "$work/probe.mjs"
  1. Observe the three reduced results shown under Relevant output or evidence. The empty and dynamic cases advertise schema keywords as input fields; the literal control reports actual. Each server binds only to 127.0.0.1 and closes after its CLI call. No external service or browser installation is required.

Expected behavior

schema_property_names describes the literal input property names declared under a JSON Schema's properties. The empty schema has no such names. For an unresolved properties identifier, analysis may report unknown fields, but it must not replace them with type and properties. The literal control reports actual.

docs/browser-observation.md:19 promises static WebMCP declaration analysis. src/domain/webBundleAnalyzer.test.ts:63 supplies the standard JSON Schema shape, and its assertion at src/domain/webBundleAnalyzer.test.ts:100 defines schema_property_names as the input properties, excluding schema keywords.

Impact and scope

Impact level P2, supported. Static bundle analysis returns incorrect WebMCP field inventories for valid schema declarations, which can mislead callers reconstructing a page tool's inputs. The defect affects this bounded projection rather than modifying the page or registering a tool.

Workaround status is unverified. The literal-properties control returns correct names, but changing a target's source is not an available analysis workaround. An alternative static workflow preserving the same field inventory was not verified.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1. Architecture was not recorded in the reproduction evidence.

Target and artifact context

The repository's local HTTP/WebSocket CDP fixture supplies captured JavaScript for the allowed-page target. The inputs contain an empty object schema, a schema with an unresolved properties identifier, and a literal-properties control. The executed public CLI reproduction does not establish real Chrome behavior or execute the captured script.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The public CLI reproduction observed the following reduced results, with all three calls exiting successfully:

{"name":"empty","exit":0,"declarations":[{"name":"lookup","schema_property_names":["type"]}]}
{"name":"dynamic","exit":0,"declarations":[{"name":"lookup","schema_property_names":["properties","type"]}]}
{"name":"control","exit":0,"declarations":[{"name":"lookup","schema_property_names":["actual"]}]}

Evidence provenance is observed; local verification reproduced the behavior, and the reproduction is complete.

Typed outcome or recovery expected

Run all three cases. The empty and dynamic cases must not advertise schema keywords as input fields, and the literal control must still report actual. Preserve the declaration's name, source location, and untrusted status. Unknown dynamic fields must remain unknown rather than being inferred from the schema's own keys.

Analysis or suspected cause

Source-derived reasoning: src/domain/webBundleAnalyzerInspection.ts:312 falls back to the entire schema object when properties is not an object expression, and the following projection returns its keyword names. This fallback explains the observed type and properties entries; the literal-properties control takes the declared-properties branch.

Additional context

  • Related completed issue #2624 concerns deduplication of differing WebMCP metadata and equivalent property sets; this issue concerns incorrect property names within a retained declaration.
  • Related completed issue #2626 concerns optional-chaining declaration sites omitted from the inventory; this issue retains ordinary declaration sites but misidentifies their input fields.
  • Related completed issue #2806 concerns non-URL literals reported as endpoints; this issue concerns JSON Schema keywords reported as WebMCP input fields.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3331 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `analyze-web-bundle` reports JSON Schema keywords as WebMCP input property names when a declaration has no literal `properties` object. A valid schema with only `type: "object"` produces `schema_property_names: ["type"]`; a schema whose properties come from an identifier produces `["properties", "type"]`. These names describe the schema itself rather than its declared input fields. All three public CLI calls in the reproduction exit with status 0; the literal-properties control correctly reports `actual`. ### Reproduction 1. Start from a clean checkout at the baseline and install the locked dependencies, then build the runtime. The preparation commands below describe clean-checkout setup; the reported reproduction used an already-built runtime. ```sh git clone https://github.com/morluto/rea.git rea-repro cd rea-repro git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. From the repository root, copy the existing local CDP fixture into an isolated directory and run the following script. The script provides all three captured JavaScript inputs inline. Type stripping only prepares the fixture; production analysis uses the built CLI. ```sh work="$(mktemp -d)" mkdir "$work/home" node --input-type=module - "$work" <<'JS' import { readFileSync, writeFileSync, symlinkSync } from "node:fs"; import { stripTypeScriptTypes } from "node:module"; const work = process.argv[2]; for (const name of ["fakeCdpBrowser", "fakeCdpBrowserEvents", "fakeCdpBrowserResults", "fakeCdpBrowserTargets"]) { writeFileSync(`${work}/${name}.js`, stripTypeScriptTypes( readFileSync(`tests/fixtures/${name}.ts`, "utf8"))); } writeFileSync(`${work}/package.json`, '{"type":"module"}'); symlinkSync(`${process.cwd()}/node_modules`, `${work}/node_modules`); JS cat > "$work/probe.mjs" <<'JS' import { startFakeCdpBrowser } from "./fakeCdpBrowser.js"; import { spawn } from "node:child_process"; import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; const work = dirname(fileURLToPath(import.meta.url)); const cases = { empty: 'document.modelContext.registerTool({name:"lookup",inputSchema:{type:"object"}});', dynamic: 'const fields={actual:{type:"string"}};document.modelContext.registerTool({name:"lookup",inputSchema:{type:"object",properties:fields}});', control: 'document.modelContext.registerTool({name:"lookup",inputSchema:{type:"object",properties:{actual:{type:"string"}}}});' }; for (const [name, scriptSource] of Object.entries(cases)) { const fake = await startFakeCdpBrowser({ commandResult: command => command.method === "Debugger.getScriptSource" ? { scriptSource } : undefined }); try { const child = spawn(process.execPath, ["scripts/rea.mjs", "analyze-web-bundle", fake.endpoint, "allowed-page", "--observation-ms", "1", "--json"], { env: { ...process.env, HOME: `${work}/home` } }); let output = ""; child.stdout.on("data", chunk => { output += chunk; }); child.stderr.pipe(process.stderr); const exit = await new Promise(resolve => child.on("exit", resolve)); const result = JSON.parse(output).normalized_result; console.log(JSON.stringify({ name, exit, declarations: result.observations.webmcp_declarations.map( ({ name, schema_property_names }) => ({ name, schema_property_names })) })); } finally { await fake.close(); } } JS node "$work/probe.mjs" ``` 3. Observe the three reduced results shown under Relevant output or evidence. The empty and dynamic cases advertise schema keywords as input fields; the literal control reports `actual`. Each server binds only to `127.0.0.1` and closes after its CLI call. No external service or browser installation is required. ### Expected behavior `schema_property_names` describes the literal input property names declared under a JSON Schema's `properties`. The empty schema has no such names. For an unresolved `properties` identifier, analysis may report unknown fields, but it must not replace them with `type` and `properties`. The literal control reports `actual`. `docs/browser-observation.md:19` promises static WebMCP declaration analysis. `src/domain/webBundleAnalyzer.test.ts:63` supplies the standard JSON Schema shape, and its assertion at `src/domain/webBundleAnalyzer.test.ts:100` defines `schema_property_names` as the input properties, excluding schema keywords. ### Impact and scope Impact level P2, supported. Static bundle analysis returns incorrect WebMCP field inventories for valid schema declarations, which can mislead callers reconstructing a page tool's inputs. The defect affects this bounded projection rather than modifying the page or registering a tool. Workaround status is unverified. The literal-properties control returns correct names, but changing a target's source is not an available analysis workaround. An alternative static workflow preserving the same field inventory was not verified. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1. Architecture was not recorded in the reproduction evidence. ### Target and artifact context The repository's local HTTP/WebSocket CDP fixture supplies captured JavaScript for the `allowed-page` target. The inputs contain an empty object schema, a schema with an unresolved `properties` identifier, and a literal-properties control. The executed public CLI reproduction does not establish real Chrome behavior or execute the captured script. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence The public CLI reproduction observed the following reduced results, with all three calls exiting successfully: ```text {"name":"empty","exit":0,"declarations":[{"name":"lookup","schema_property_names":["type"]}]} {"name":"dynamic","exit":0,"declarations":[{"name":"lookup","schema_property_names":["properties","type"]}]} {"name":"control","exit":0,"declarations":[{"name":"lookup","schema_property_names":["actual"]}]} ``` Evidence provenance is observed; local verification reproduced the behavior, and the reproduction is complete. ### Typed outcome or recovery expected Run all three cases. The empty and dynamic cases must not advertise schema keywords as input fields, and the literal control must still report `actual`. Preserve the declaration's name, source location, and untrusted status. Unknown dynamic fields must remain unknown rather than being inferred from the schema's own keys. ### Analysis or suspected cause Source-derived reasoning: `src/domain/webBundleAnalyzerInspection.ts:312` falls back to the entire schema object when `properties` is not an object expression, and the following projection returns its keyword names. This fallback explains the observed `type` and `properties` entries; the literal-properties control takes the declared-properties branch. ### Additional context - Related completed issue [#2624](https://github.com/morluto/rea/issues/2624) concerns deduplication of differing WebMCP metadata and equivalent property sets; this issue concerns incorrect property names within a retained declaration. - Related completed issue [#2626](https://github.com/morluto/rea/issues/2626) concerns optional-chaining declaration sites omitted from the inventory; this issue retains ordinary declaration sites but misidentifies their input fields. - Related completed issue [#2806](https://github.com/morluto/rea/issues/2806) concerns non-URL literals reported as endpoints; this issue concerns JSON Schema keywords reported as WebMCP input fields.
Author
Owner

@z2Ace0107 commented on GitHub (Oct 14, 2026):

I'd like to work on this issue. I reproduced the empty-schema and unresolved-properties cases through the production bundle analyzer on Windows with Node 24.18.0; the literal-properties control reports the correct field name.

I'll keep the fix focused on declared input-property extraction, preserve uncertainty for dynamic properties, and add regression coverage. CLI/MCP verification is still pending.

<!-- gh-comment-id:6171414692 --> @z2Ace0107 commented on GitHub (Oct 14, 2026): I'd like to work on this issue. I reproduced the empty-schema and unresolved-properties cases through the production bundle analyzer on Windows with Node 24.18.0; the literal-properties control reports the correct field name. I'll keep the fix focused on declared input-property extraction, preserve uncertainty for dynamic properties, and add regression coverage. CLI/MCP verification is still pending.
Author
Owner

@Abdooo2235 commented on GitHub (Oct 14, 2026):

@morluto I've opened #3765 to keep JSON Schema keywords out of WebMCP input field names. The fix removes the fallback to the whole schema while preserving declaration metadata and both schema spellings.

The new domain and compiled CLI regressions reproduced the original failure. After the fix, all 50 focused domain/CLI/MCP tests passed, along with repository and documentation checks. Browser coverage uses the local CDP fixture, as noted in the PR.

@z2Ace0107 I noticed your earlier investigation here; linking the implemented fix and regression coverage so we can avoid duplicating further work.

<!-- gh-comment-id:6180943436 --> @Abdooo2235 commented on GitHub (Oct 14, 2026): @morluto I've opened #3765 to keep JSON Schema keywords out of WebMCP input field names. The fix removes the fallback to the whole schema while preserving declaration metadata and both schema spellings. The new domain and compiled CLI regressions reproduced the original failure. After the fix, all 50 focused domain/CLI/MCP tests passed, along with repository and documentation checks. Browser coverage uses the local CDP fixture, as noted in the PR. @z2Ace0107 I noticed your earlier investigation here; linking the implemented fix and regression coverage so we can avoid duplicating further work.
Author
Owner

@morluto commented on GitHub (Oct 16, 2026):

Fixed on main in ebd5a82c4. WebMCP input names now come from literal schema properties; schema keywords no longer become invented parameter names when properties are absent or dynamic.

Analyzer and CLI receiver/schema controls retain literal and empty keys while preserving missing/dynamic uncertainty.

Hosted main CI is still pending; this closure is based on the implemented fix and the scoped regression evidence.

<!-- gh-comment-id:6206031367 --> @morluto commented on GitHub (Oct 16, 2026): Fixed on `main` in [ebd5a82c4](https://github.com/morluto/rea/commit/ebd5a82c439f6deb3a06062225ce009eb03257cf). WebMCP input names now come from literal schema properties; schema keywords no longer become invented parameter names when properties are absent or dynamic. Analyzer and CLI receiver/schema controls retain literal and empty keys while preserving missing/dynamic uncertainty. Hosted main CI is still pending; this closure is based on the implemented fix and the scoped regression evidence.
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#1203
No description provided.