From ebd5a82c439f6deb3a06062225ce009eb03257cf Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 16 Oct 2026 06:06:07 +0800 Subject: [PATCH] fix(web): read WebMCP inputs from schema properties Remove the fallback that interpreted schema metadata as parameter names. Preserve exact literal property keys, including empty names, without inventing names for missing or dynamic properties. Keep this an observed name projection. Refs #3331, #3765 Co-authored-by: abdooo2235 --- src/domain/webBundleAnalyzer.test.ts | 41 +++++++++++++++++++++++ src/domain/webBundleAnalyzerInspection.ts | 24 ++++++------- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/domain/webBundleAnalyzer.test.ts b/src/domain/webBundleAnalyzer.test.ts index cc137be20e..dfc3ce4cbe 100644 --- a/src/domain/webBundleAnalyzer.test.ts +++ b/src/domain/webBundleAnalyzer.test.ts @@ -1034,6 +1034,47 @@ describe("web metadata key semantics", () => { expect.objectContaining({ name: "valid" }), ]); }); + + it("projects only exact input schema property names", () => { + const result = analyzeCapturedWebBundle( + inspection(` + document.modelContext.registerTool({ + name: "literal", + inputSchema: { + type: "object", + properties: { type: {}, properties: {}, "": {}, [dynamicKey]: {}, ...dynamicProperties } + } + }); + document.modelContext.registerTool({ name: "missing properties", inputSchema: { type: "object" } }); + document.modelContext.registerTool({ name: "dynamic properties", inputSchema: { properties: dynamicProperties } }); + document.modelContext.registerTool({ name: "null properties", inputSchema: { properties: null } }); + document.modelContext.registerTool({ name: "missing schema" }); + document.modelContext.registerTool({ name: "dynamic schema", inputSchema: schema }); + document.modelContext.registerTool({ name: "null schema", inputSchema: null }); + document.modelContext.registerTool({ name: "spread schema", inputSchema: { ...schema } }); + document.modelContext.registerTool({ + name: "computed literal keys", + ["inputSchema"]: { ["properties"]: { [""]: {}, ["literal"]: {}, [dynamicKey]: {} } } + }); + `), + ); + + expect( + result.observations.webmcp_declarations.map( + ({ name, schema_property_names }) => ({ name, schema_property_names }), + ), + ).toEqual([ + { name: "literal", schema_property_names: ["", "properties", "type"] }, + { name: "missing properties", schema_property_names: [] }, + { name: "dynamic properties", schema_property_names: [] }, + { name: "null properties", schema_property_names: [] }, + { name: "missing schema", schema_property_names: [] }, + { name: "dynamic schema", schema_property_names: [] }, + { name: "null schema", schema_property_names: [] }, + { name: "spread schema", schema_property_names: [] }, + { name: "computed literal keys", schema_property_names: ["", "literal"] }, + ]); + }); }); it("retains WebMCP declarations through optional member and call chaining", () => { diff --git a/src/domain/webBundleAnalyzerInspection.ts b/src/domain/webBundleAnalyzerInspection.ts index 9fd7599976..d28bf76e12 100644 --- a/src/domain/webBundleAnalyzerInspection.ts +++ b/src/domain/webBundleAnalyzerInspection.ts @@ -8,7 +8,10 @@ import { traverseJavaScriptAst } from "./javascript/javascriptSemanticTraversal. import { indexJavaScriptBindingsWithReceiversSteps } from "./javascript/javascriptSemanticAnalysis.js"; import { completeSemanticSteps } from "./javascript/javascriptSemanticTraversal.js"; import type { JavaScriptOpenReceiverFact } from "./javascript/javascriptSemanticAnalysis.js"; -import { semanticStaticPropertyName } from "./javascript/javascriptAstValues.js"; +import { + semanticStaticPropertyKey, + semanticStaticPropertyName, +} from "./javascript/javascriptAstValues.js"; import { calleeName, endpointArgument, @@ -315,20 +318,15 @@ const addWebMcpDeclaration = ( const schema = objectValue(declaration, "inputSchema") ?? objectValue(declaration, "input_schema"); - const declaredProperties = t.isObjectExpression(schema) + const properties = t.isObjectExpression(schema) ? objectValue(schema, "properties") : undefined; - const propertyObject = t.isObjectExpression(declaredProperties) - ? declaredProperties - : schema; - const schemaPropertyNames = t.isObjectExpression(propertyObject) - ? propertyObject.properties.flatMap((property) => - t.isObjectProperty(property) - ? [ - semanticStaticPropertyName(property.key, property.computed), - ].filter(Boolean) - : [], - ) + const schemaPropertyNames = t.isObjectExpression(properties) + ? properties.properties.flatMap((property) => { + if (!t.isObjectProperty(property)) return []; + const key = semanticStaticPropertyKey(property.key, property.computed); + return key === null ? [] : [key]; + }) : []; const metadata = { name: name ?? null,