diff --git a/webmcp/declarative/getTools-declarative-schema.https.html b/webmcp/declarative/getTools-declarative-schema.https.html index 66f79afa423bb3..d667084fa570f5 100644 --- a/webmcp/declarative/getTools-declarative-schema.https.html +++ b/webmcp/declarative/getTools-declarative-schema.https.html @@ -35,7 +35,7 @@ required: ["query"] }; - const tool = await waitForFormToolSchemaToMatch(expected_schema_obj); + const tool = await waitForFormToolSchemaToMatch(JSON.stringify(expected_schema_obj)); assert_equals(tool.name, "search_tool", "Tool name matches"); assert_equals(tool.description, "Search the web", "Tool description matches"); diff --git a/webmcp/declarative/toolchange-on-control-add-remove.https.html b/webmcp/declarative/toolchange-on-control-add-remove.https.html index 3612d3ac69ca35..4a794c188fd299 100644 --- a/webmcp/declarative/toolchange-on-control-add-remove.https.html +++ b/webmcp/declarative/toolchange-on-control-add-remove.https.html @@ -17,7 +17,7 @@ const form = document.getElementById('f1'); // Wait for the baseline form schema. - await waitForFormToolSchemaToMatch({"type":"object","properties":{"input_name":{"type":"string"}},"required":[]}); + await waitForFormToolSchemaToMatch(`{"type":"object","properties":{"input_name":{"type":"string"}},"required":[]}`); // Test adding an input const i = document.createElement('input'); @@ -25,12 +25,12 @@ i.name = 'new_input'; i.id = 'new_input'; form.appendChild(i); - await waitForFormToolSchemaToMatch({"type":"object","properties":{"input_name":{"type":"string"},"new_input":{"type":"string"}},"required":[]}); + await waitForFormToolSchemaToMatch(`{"type":"object","properties":{"input_name":{"type":"string"},"new_input":{"type":"string"}},"required":[]}`); // Test removing an input const newInput = document.getElementById('new_input'); form.removeChild(newInput); - await waitForFormToolSchemaToMatch({"type":"object","properties":{"input_name":{"type":"string"}},"required":[]}); + await waitForFormToolSchemaToMatch(`{"type":"object","properties":{"input_name":{"type":"string"}},"required":[]}`); }, "Test that toolchange event fires when controls are added or removed"); diff --git a/webmcp/imperative/exposedTo-cross-origin-child.https.html b/webmcp/imperative/exposedTo-cross-origin-child.https.html index c2fcda8204bc8b..1e9064c4030984 100644 --- a/webmcp/imperative/exposedTo-cross-origin-child.https.html +++ b/webmcp/imperative/exposedTo-cross-origin-child.https.html @@ -44,7 +44,7 @@ assert_true(toolsAreEqual(tool, { name: 'parent_tool_exposed', description: 'Parent tool exposed to child', - inputSchema: { type: 'object', properties: { query: { type: 'string' } } }, + inputSchema: JSON.stringify({ type: 'object', properties: { query: { type: 'string' } } }), origin: self.origin }), 'Tool details should match'); @@ -104,7 +104,7 @@ assert_true(toolsAreEqual(tool, { name: 'iframe_tool_exposed', description: 'Iframe tool exposed to parent', - inputSchema: { type: 'object', properties: { query: { type: 'string' } } }, + inputSchema: JSON.stringify({ type: 'object', properties: { query: { type: 'string' } } }), origin: host_info.HTTPS_REMOTE_ORIGIN }), 'Tool details should match'); diff --git a/webmcp/imperative/exposedTo-defaults-same-origin.https.html b/webmcp/imperative/exposedTo-defaults-same-origin.https.html index 93d0e5203bed28..43f827fb0ca302 100644 --- a/webmcp/imperative/exposedTo-defaults-same-origin.https.html +++ b/webmcp/imperative/exposedTo-defaults-same-origin.https.html @@ -41,7 +41,7 @@ assert_true(toolsAreEqual(tool, { name: 'parent_tool_default', description: 'Parent tool with default exposure', - inputSchema: { type: 'object', properties: { query: { type: 'string' } } }, + inputSchema: JSON.stringify({ type: 'object', properties: { query: { type: 'string' } } }), annotations: { readOnlyHint: true, untrustedContentHint: false }, origin: self.origin }), 'Tool details should match'); @@ -90,7 +90,7 @@ assert_true(toolsAreEqual(tool, { name: 'parent_tool_empty', description: 'Parent tool with empty exposedTo', - inputSchema: { type: 'object', properties: { query: { type: 'string' } } }, + inputSchema: JSON.stringify({ type: 'object', properties: { query: { type: 'string' } } }), origin: self.origin }), 'Tool details should match'); diff --git a/webmcp/imperative/exposedTo-multiple-children.https.html b/webmcp/imperative/exposedTo-multiple-children.https.html index 8f7715716f7db5..0ef7377c017080 100644 --- a/webmcp/imperative/exposedTo-multiple-children.https.html +++ b/webmcp/imperative/exposedTo-multiple-children.https.html @@ -69,7 +69,7 @@ assert_true(toolsAreEqual(tool, { name: 'tool_b_to_c', description: 'Tool B exposed to C', - inputSchema: { type: 'object', properties: { query: { type: 'string' } } }, + inputSchema: JSON.stringify({ type: 'object', properties: { query: { type: 'string' } } }), origin: origin_b }), 'Tool details should match'); diff --git a/webmcp/imperative/getTools-imperative-schema.https.html b/webmcp/imperative/getTools-imperative-schema.https.html index 37e4b5bc2b2590..4526326d936ab1 100644 --- a/webmcp/imperative/getTools-imperative-schema.https.html +++ b/webmcp/imperative/getTools-imperative-schema.https.html @@ -41,23 +41,25 @@ assert_equals(registeredTool.description, "Search the web", "Tool description matches registered tool"); assert_true(!!registeredTool.inputSchema, "inputSchema should be non-empty"); - assert_equals(registeredTool.inputSchema.type, "object", "Schema should be an object"); + const schema = JSON.parse(registeredTool.inputSchema); + + assert_equals(schema.type, "object", "Schema should be an object"); // 'query' property validation (required string) - assert_true("query" in registeredTool.inputSchema.properties, "Schema should contain 'query' property"); - assert_equals(registeredTool.inputSchema.properties.query.type, "string", "Query property type should be string"); - assert_equals(registeredTool.inputSchema.properties.query.description, "The search query", "Query property description is correct"); - assert_array_equals(registeredTool.inputSchema.required, ["query"], "Query should be a required property"); + assert_true("query" in schema.properties, "Schema should contain 'query' property"); + assert_equals(schema.properties.query.type, "string", "Query property type should be string"); + assert_equals(schema.properties.query.description, "The search query", "Query property description is correct"); + assert_array_equals(schema.required, ["query"], "Query should be a required property"); // 'limit' property validation (optional number) - assert_true("limit" in registeredTool.inputSchema.properties, "Schema should contain 'limit' property"); - assert_equals(registeredTool.inputSchema.properties.limit.type, "number", "Limit property type should be number"); - assert_equals(registeredTool.inputSchema.properties.limit.description, "Max results count", "Limit property description is correct"); + assert_true("limit" in schema.properties, "Schema should contain 'limit' property"); + assert_equals(schema.properties.limit.type, "number", "Limit property type should be number"); + assert_equals(schema.properties.limit.description, "Max results count", "Limit property description is correct"); // 'safe_search' property validation (optional boolean) - assert_true("safe_search" in registeredTool.inputSchema.properties, "Schema should contain 'safe_search' property"); - assert_equals(registeredTool.inputSchema.properties.safe_search.type, "boolean", "safe_search property type should be boolean"); - assert_equals(registeredTool.inputSchema.properties.safe_search.description, "Enable safe search filtering", "safe_search property description is correct"); + assert_true("safe_search" in schema.properties, "Schema should contain 'safe_search' property"); + assert_equals(schema.properties.safe_search.type, "boolean", "safe_search property type should be boolean"); + assert_equals(schema.properties.safe_search.description, "Enable safe search filtering", "safe_search property description is correct"); }, "Test that getTools() returns the correct inputSchema for an imperative tool"); diff --git a/webmcp/imperative/resources/helpers.js b/webmcp/imperative/resources/helpers.js index 63c1ff5a64c3a0..426548ca93348e 100644 --- a/webmcp/imperative/resources/helpers.js +++ b/webmcp/imperative/resources/helpers.js @@ -20,11 +20,8 @@ function toolsAreEqual(actual, expected) { if (actual.description !== expected.description) { return `descriptions are unequal: ${actual.description} !== ${expected.description}`; } - if (JSON.stringify(actual.inputSchema) !== - JSON.stringify(expected.inputSchema)) { - return `inputSchemas are unequal: ${ - JSON.stringify( - actual.inputSchema)} !== ${JSON.stringify(expected.inputSchema)}`; + if (actual.inputSchema !== expected.inputSchema) { + return `inputSchemas are unequal: ${actual.inputSchemas} !== ${expected.inputSchemas}`; } if (actual.origin !== expected.origin) { return `origins are unequal: ${actual.origin} !== ${expected.origin}`; diff --git a/webmcp/resources/helpers.js b/webmcp/resources/helpers.js index d02d1facd41d6c..aae7894532ed3f 100644 --- a/webmcp/resources/helpers.js +++ b/webmcp/resources/helpers.js @@ -18,23 +18,17 @@ async function waitForTool(name) { }); } -// Wait for the active WebMCP tool's input schema to match the expected schema. +// Wait for the active WebMCP tool's input schema to match the expected schema string. async function waitForFormToolSchemaToMatch(expected_schema) { - const isMatch = (tool) => { - if (!tool) - return false; - return JSON.stringify(tool.inputSchema) === JSON.stringify(expected_schema); - }; - const [tool] = await document.modelContext.getTools(); - if (isMatch(tool)) { + if (tool && tool.inputSchema === expected_schema) { return tool; } return new Promise(resolve => { const ac = new AbortController(); document.modelContext.addEventListener('toolchange', async e => { const [tool] = await document.modelContext.getTools(); - if (isMatch(tool)) { + if (tool && tool.inputSchema === expected_schema) { resolve(tool); ac.abort(); }