On 26 August 2026 the WebMCP spec recorded its first client marked Shipped: the ChatGPT desktop app's built-in browser. In the eight weeks before that, the namespace moved, inputSchema changed type, and executeTool() stopped taking a JSON string. There are now at least three registration shapes in the wild that were each correct at some point this summer, and a real agent on the other end of them. This page asks your browser which one it speaks, seven times, and prints what it threw.
Tools belong to a page, not to the browser. This is the form to write.
If both exist and differ, a tool registered on one is invisible on the other.
Of seven attempted. A shape the spec says should be rejected counts here too, and that is the interesting case.
Rows where the outcome matched what the current spec text requires.
Probes that could not run because there is no WebMCP implementation here to run them against.
The in-page implementation, which is the control. If this is not 7 of 7, the control is broken.
None of these are large. All of them are the kind of change that produces a tool an agent silently declines to call rather than an error anyone sees. The two marked in pink will reject code that was correct a fortnight earlier.
JSON.stringify(schema). It now takes the object directly. Code written to the old IDL is now passing a string where an object is required, and what happens next depends entirely on how forgiving the implementation on the other end is.For most of this year the honest thing to say about WebMCP was that nothing consumed it. Nine origin-trial participants were named, deployment was unconfirmed, and every mainstream agent still parsed the DOM. A page that exposed tools was making a statement about where the web is going, not wiring up an integration.
The ChatGPT desktop app's built-in browser reads these tools, and the spec repo records it as shipped rather than experimental. OpenAI ran a WebMCP Challenge with Chrome, Cloudflare, Shopify, Vercel, Render and Netlify from 25 August to 3 September.
While nothing reads your tools, writing them against a two-month-old draft costs nothing. Once something does, the failure mode is an agent that quietly does not call a tool it can see. No console error, no failed request, no signal on your side at all.
Chromium only, window closing around Chrome 156 in late October. Firefox and Safari have positions open and no timeline. Anyone telling you WebMCP is production infrastructure is a version ahead of the facts.
Twelve lines. It writes the current shape first, falls back through the namespace move, and only falls back to the stringified schema if the object form was refused, so it never volunteers a deprecated shape to an implementation that wanted the current one.
// Current shape first. Only degrade when the implementation refuses it. async function registerCompat(tool) { const mc = document.modelContext || navigator.modelContext; // spec moved 21 Jul 2026 if (!mc?.registerTool) return { ok: false, why: 'no implementation' }; try { await mc.registerTool(tool); // inputSchema as an object (#241) return { ok: true, shape: 'object' }; } catch (e) { // Older implementations typed inputSchema as DOMString. await mc.registerTool({ ...tool, inputSchema: JSON.stringify(tool.inputSchema) }); return { ok: true, shape: 'string', degradedBecause: String(e) }; } } // The thing worth keeping is `degradedBecause`. It is the only place the // drift is visible, and only if you log it somewhere you will read.
Every row attempts a real call and reads the real outcome. None of them infer a capability from a property name, because the presence of registerTool says nothing about which argument shapes it validates. Rows that report never reachable are the honest answer on a browser without WebMCP: no implementation ran, so nothing was learned about it.
The fourth column is a small implementation of the current spec text written in this file. It exists so the battery still demonstrates something where there is no WebMCP, and so a mismatch in your browser's column has something to be a mismatch against. It is labelled as ours everywhere it appears.
Each probe withdraws its tool as soon as it has been measured. Where an implementation has no unregisterTool, it cannot be withdrawn, and the page names every tool it was forced to leave behind instead of implying a finally block solved it. A page that registers tools and walks away has handed an agent a surface nobody is maintaining.
These probes measure what an implementation accepts without throwing. An implementation could accept a stringified schema and then fail to expose the tool correctly to an agent, and this page would not see that. Measuring the agent's side needs the agent.