# WebMCP fix guide: origin-trial token, form labels, Claude directory readiness A plain Markdown guide for three findings from the [Mega Analyzer](https://jwatte.com/tools/mega-analyzer/), the [WebMCP Readiness Checker](https://jwatte.com/tools/webmcp-readiness-checker/) and the [Agent Endpoint Inspector](https://jwatte.com/tools/agent-endpoint-inspector/). Work through it yourself or hand it to an AI coding assistant. Each section follows the same four steps: diagnose, decide, fix, verify. Replace `https://example.com` with your site. Every command here only reads. None of them runs a tool, submits a form or changes your site. Background and the reasoning behind each rule: Facts checked October 16, 2026. The WebMCP origin trial runs in Chrome 149 to 162 and ends March 29, 2027. --- ## 1. WebMCP origin-trial token ### Diagnose Does the page run WebMCP at all? Look for `registerTool` or `modelContext` in the page or in the script that registers your tools: ```sh curl -s https://example.com/ | grep -o -i -E 'registerTool|modelContext|]+webmcp[^>]*>' | sort | uniq -c ``` If nothing matches, your site has no WebMCP tools and needs no token. Stop here. Is a token sent? Check the header first, then the head of the page: ```sh curl -s -D - -o /dev/null https://example.com/ | grep -i '^origin-trial' curl -s https://example.com/ | grep -i -o ']*origin-trial[^>]*>' ``` Decode a token to see what it says. Save this as `check-token.mjs` and run `node check-token.mjs https://example.com` (Node 20 or later): ```js // Decodes a Chrome origin-trial token and verifies its signature with Chrome's public key. import { webcrypto as crypto } from 'node:crypto'; const [token, page] = process.argv.slice(2); const b = Buffer.from(token, 'base64'); const len = b.readUInt32BE(65); const payload = JSON.parse(b.subarray(69, 69 + len).toString('utf8')); const key = await crypto.subtle.importKey('raw', Buffer.from('fMS4mpO6buLQ/QMd+zJmxzty/VQ6B1EUZqoCU04zoRU=', 'base64'), { name: 'Ed25519' }, false, ['verify']); const signed = Buffer.concat([b.subarray(0, 1), b.subarray(65, 69 + len)]); const sigOk = await crypto.subtle.verify('Ed25519', key, b.subarray(1, 65), signed); const pageOrigin = new URL(page).origin, tokOrigin = new URL(payload.origin).origin; const host = new URL(page).hostname, tokHost = new URL(payload.origin).hostname; const originOk = pageOrigin === tokOrigin || (payload.isSubdomain === true && host.endsWith('.' + tokHost)); console.log({ version: b[0], ...payload, expires: new Date(payload.expiry * 1000).toISOString(), signature: sigOk, originMatches: originOk }); ``` ### Decide | What you see | Meaning | Do | |---|---|---| | No token anywhere | Tools are inert in ordinary Chrome | Register and add a token (Fix) | | `feature` is not `WebMCP` | A token for another trial | Register WebMCP; you may keep both tokens | | `originMatches: false` | Token minted for another address (often www vs bare) | Register the exact origin, or tick "match all subdomains" | | `expires` is in the past | Trial ended or token not renewed | Renew in the console | | `signature: false` | Token damaged in copy and paste | Copy it again from the console | | Meta tag found but not inside `` | Chrome ignores it | Move it into the head, or use the header | ### Fix 1. Go to the [Chrome Origin Trials console](https://developer.chrome.com/origintrials/#/trials/active), find **WebMCP**, press **Register**. 2. Enter your origin exactly as visitors see it, e.g. `https://example.com`. Tick **"I need a token to match all subdomains of the origin"** if both `example.com` and `www.example.com` serve pages. 3. Choose the lowest expected usage band unless you're a large site, tick the four acknowledgements, press Register, copy the token. 4. Add it to every HTML page. Use the header where your host allows it, since it can't end up in the wrong place: | Host | Where | |---|---| | Netlify, Cloudflare Pages | `_headers` file: a `/*` line, then ` Origin-Trial: TOKEN` | | Netlify (toml) | `[[headers]]` `for = "/*"` with `[headers.values]` `Origin-Trial = "TOKEN"` | | Vercel | `vercel.json` `headers`: source `/(.*)`, header `Origin-Trial` | | Apache | `Header always set Origin-Trial "TOKEN"` | | nginx | `add_header Origin-Trial "TOKEN" always;` | | WordPress, Squarespace, Wix, Shopify | Header injection is often unavailable, so put `` in the site-wide head code area | Several tokens can share one header, separated by commas, or sit in several meta tags. Chrome needs only one valid token per page. ### Verify Re-run the two diagnose commands and the decoder. Then run the [Mega Analyzer](https://jwatte.com/tools/mega-analyzer/) on your home page: the row "WebMCP origin-trial token" should pass and show the expiry date. Put the expiry date in your calendar. --- ## 2. Declarative WebMCP form labels ### Diagnose List your forms and the attributes on them: ```sh curl -s https://example.com/contact/ | grep -o -i -E ']*>|tool(name|description|paramdescription|autosubmit)="[^"]*"' ``` ### Decide - A form people fill in (contact, booking, quote, service request, application) should carry `toolname` and `tooldescription`, and every visible named field a `toolparamdescription`. - Skip search boxes, hidden copies of forms (some hosts keep one for spam detection) and spam-trap fields. - If a form has `toolname` but no `tooldescription`, Chrome ignores it. Add the description. - `toolautosubmit` lets the assistant send the form by itself. Leave it off any form that sends a request, books, pays or shares personal details. ### Fix ```html
``` Writing tips: - `toolname`: lowercase letters, digits and underscores, starting with a verb (`request_service`, `start_reservation`, `ask_a_question`). - `tooldescription`: one sentence saying what the form does and who sends it. - `toolparamdescription`: what belongs in the field and the format you want. - For radio buttons or checkboxes that share a name, one description on any of them covers the group. These attributes have no effect outside Chrome's trial, so they're safe to add everywhere. The declarative API left the draft spec on October 8, 2026 for rework while Chrome keeps shipping it, so check the names again before the trial ends. ### Verify Run the Mega Analyzer. The row "Declarative WebMCP form labels" passes when every user-facing form is complete. In Chrome DevTools, Lighthouse's experimental **Agentic browsing** category also counts covered forms (Chrome 150 or later, origin-trial token in place). --- ## 3. MCP tools ready for the Claude Connectors Directory This applies only if your site runs an MCP server (a URL such as `https://example.com/mcp`). To list your tools, follow "Before you start" in the [agent endpoint fix guide](https://jwatte.com/downloads/agent-endpoint-fix-guide.md), or use the [Agent Endpoint Inspector](https://jwatte.com/tools/agent-endpoint-inspector/), which lists them without running any. ### Decide Each tool needs: - a `title` (set it at the top level and in `annotations.title`; Anthropic's checklist says "title" without saying which); - `readOnlyHint: true` for a tool that only reads, or `destructiveHint: true` for one that changes or deletes something (state both explicitly, as booleans); - a `name` of 64 characters or fewer. ### Fix ```json { "name": "get_rates", "title": "Get room rates", "description": "Nightly and weekly rates as the site publishes them, and whether tax is included.", "inputSchema": { "type": "object", "properties": {}, "additionalProperties": false }, "annotations": { "title": "Get room rates", "readOnlyHint": true, "destructiveHint": false, "openWorldHint": false } } ``` Two more things while you're in the server: - An MCP endpoint should answer `GET` with `405 Method Not Allowed` **and** an `Allow` header listing what it accepts (`Allow: POST, OPTIONS`). A bare 405 leaves clients guessing. - If you also register WebMCP tools in the page, give each one a `title` and `annotations: { readOnlyHint: true }` where it only reads. WebMCP has its own hint set (`readOnlyHint`, `untrustedContentHint`, `consequentialHint`); `destructiveHint` belongs to MCP servers. Listing prerequisites beyond the tools: a paid Claude plan to submit, https with the Streamable HTTP transport, OAuth 2.0 only if tools act on a customer's account (none for public information), a privacy policy URL, a documentation URL, a support contact and an icon. Before submitting you confirm that you ran every tool, through MCP Inspector or as a custom connector in Claude. ### Verify Run the Agent Endpoint Inspector or the Mega Analyzer. The row "MCP tools ready for the Claude Connectors Directory" passes when every tool has a title, an explicit hint and a short enough name. --- ## 4. Safari, Firefox, Edge and older Chrome ### Diagnose Find the script that registers your tools and look at how it reaches the API: ```sh curl -s https://example.com/js/webmcp.js | grep -n -E 'modelContext|registerTool|provideContext|typeof|\?\.' ``` Look for two tokens on the page, one from Chrome's console and one from Edge's: ```sh curl -s -D - -o /dev/null https://example.com/ | grep -i '^origin-trial' curl -s https://example.com/ | grep -i -c 'http-equiv="origin-trial"' ``` ### Decide | Browser or assistant | What it uses | What your site needs | |---|---|---| | Chrome 152 and later | `document.modelContext`, Chrome token | Chrome token; guarded code | | Chrome 149 to 151 | `navigator.modelContext`, Chrome token | `navigator` fallback (optional, shrinking) | | Microsoft Edge 150 and later | `document.modelContext`, **Edge token** | A second token from Edge's console | | ChatGPT desktop browser (site tools) | `document.modelContext` tools registered in code; ignores form attributes | Imperative tools for anything important | | Safari | No WebMCP (WebKit opposes it) | Clear HTML: labels, headings, prices as text | | Firefox | Prototype on `navigator.modelContext`, off by default | Same as Safari; the `navigator` fallback costs nothing | | Assistants outside a browser | Your MCP server or A2A card | A remote endpoint (section 3) | ### Fix 1. **Guard every use and prefer the current name:** ```js const mc = document.modelContext || navigator.modelContext; if (mc && typeof mc.registerTool === 'function') { // register tools here; wrap each registration in try/catch so one bad tool can't break the page } ``` Never call `document.modelContext.registerTool(...)` unguarded. In Safari it throws, and if it's in a shared script it can stop the rest of that script. 2. **Keep `provideContext()` only as a fallback** for old builds, after `registerTool`. The current draft removed it. 3. **Register in the top-level page.** Tools registered inside an iframe aren't discovered. 4. **Get an Edge token** at the [Microsoft Edge Origin Trials](https://developer.microsoft.com/en-us/microsoft-edge/origin-trials/) site. Sign in with GitHub, open the WebMCP trial and register your origin. Add it next to the Chrome token. Both can share one header separated by a comma, or go in two meta tags. 5. **Give each WebMCP tool a `title`, and `annotations: { readOnlyHint: true }` on tools that only read.** WebMCP's hint set is `readOnlyHint`, `untrustedContentHint` and `consequentialHint`. 6. **Label every form field** with a real `