# Agent tools and WebMCP

With discovery enabled for a Site, the SDK proposes agent tools from the structure of your application's own same-origin JSON requests. A workspace owner reviews each proposed action or workflow and enables it. Enabled tools are offered to agents in the page through WebMCP where the browser supports it, and through the SDK's in-page runner everywhere else.

## Safety rules that always apply

- Discovered tools are disabled until reviewed. Approval is rechecked before every call, and schema drift disables a tool and its workflows until someone reviews the change. Enabling a tool also lints its inputSchema as JSON Schema 2020-12 (object root, known keywords, no $ref, satisfiable constraints); a tool whose stored schema fails the lint is refused and is never listed or executable. The docs MCP server validates tools/call arguments against the same kind of schema, and its errors name keywords and locations, never values.
- Write operations ask the person at the page for confirmation.
- Tool calls are ordinary same-origin requests from the signed-in page, so your backend's own authentication and authorization decide every one of them. A tool never bypasses them.
- Discovery records shapes only (methods, path shapes, field names and types), never typed values, cookies, credentials or request bodies.
- A path input fills only an identifier slot (digits, UUID, ULID, hex, opaque token, date, version or @handle). Before every call the runner checks that the filled-in path templates back to exactly the reviewed path, with each value in its reviewed format, and is not a sensitive route, so an input such as `export`, `new` or `password-reset` can never reach a sibling route nobody reviewed. Requests whose path holds free text (a search phrase or a person's name) are never proposed as tools; tools stored that way by an earlier version are disabled on upgrade and need review.
- Consent withdrawal cancels running tool work and removes registered tools. Tool results are announced with untrustedContentHint: they come from your API and agents should treat them as data.

## Names, descriptions and update grades

Discovery can link a request to the control that caused it, so proposed tools get readable names and descriptions:

- **Trigger.** When a same-origin request starts within a second of a trusted click on a button, link, menu item, option or tab, the action records the control's role. A request first seen without a click (page load, background refresh, badges, consent banners) is never linked on that page, and any later interaction (another click, a change in a select or text field) replaces the earlier one. Enter-key submissions use the form's default button. Links to `blob:` or `data:` URLs link nothing.
- **Label text and privacy.** The control's accessible name is computed in the page (aria-labelledby, aria-label, visible text without hidden or aria-hidden parts, then title). Text is never read from inputs, selects, textareas or editable regions, or from anything inside `data-telltale-ignore`, and no label is taken on a sensitive page. Digits, email addresses, URLs, domains and @handles are removed and the text is capped at 60 characters. The text leaves the page in clear only when you opt in by putting `data-telltale-label` on the control or an ancestor (a value is used as the label; an empty attribute allows the computed text). Otherwise the page sends only an HMAC-SHA256 token of the text, and by default that is all Telltale ever uses: the token never names anything and the text is never requested. **Token reveal** is a per-Site opt-in, off by default (POST /api/admin/label-reveal-setting {siteId, enabled}, owners and admins; the Site record shows labelReveal, and /api/sdk/init returns it as labelReveal so pages compute tokens only while it is on). With it on, Telltale counts the distinct sessions that send each token and asks for the text only after at least five sessions sent the same one (the server setting `TELLTALE_LABEL_MIN_SESSIONS` changes the threshold, minimum 2); only then does a page that still holds the text send it, and Telltale keeps it only if it reproduces the token. Know what this guard is before you turn it on:
  - The threshold counts SDK sessions, not people. One person who signs out and in, or whose page calls `reset()`, is several sessions, so a person's own name on a button can reach the threshold alone.
  - Tokens are pseudonymous, not secret. The key is derived from your publishable key, which is in your page, so anyone who sees the traffic (Telltale included) can test guesses offline, and short labels such as names are easy to guess.
  - So a row's customer content (a report name, a person's name) should never be part of a control's name. Mark such controls with `data-telltale-label="Export report"` (or `data-telltale-ignore`) instead. Turning reveal off again stops every request and stops earlier revealed texts from naming anything.
- **Field descriptions.** For a field submitted from a form (or bound with `data-telltale-field`), the description comes from `toolparamdescription` on the control, then its `<label>` text without the controls inside it (a label wrapping a select reads "Range", not its options), then `aria-description`. `toolparamdescription` is a declaration for agents and is sent in clear; label and aria-description text follow the same opt-in and threshold rules. These attribute names follow Chrome's declarative WebMCP preview and may change.
- **Use.** A known label names a newly discovered action or workflow together with the resource it acts on ("Export" on the report export request gives `export_report`, never a bare `export`), seeds the description of an action that was never approved, and splits a workflow's description at each click (`Export report: "Export report" button → start export → poll job → download`). A suggested name is never one another tool or suggestion on the Site already uses, and when a readable current name already says what the label says, no new name is suggested. Labels never enter session events or request telemetry and never change a reviewed contract. A description is published only after you review and enable the tool, and an approved description may contain a label you reviewed (the seeded "Use the "Export" button…" text, unless you edit it); the label is not published otherwise. Set `captureLabels: false` in `init` to turn all of this off for a page.

Any change to an enabled tool's published input schema is drift, in either direction: a new field or type, and also a required field that requests stopped sending (it would become optional) or a widened or relabelled field. Only tools not yet reviewed keep refining silently while they are learned.

Each detected change in an update entry or update draft is graded **breaking** (the reviewed tool's request or response no longer fits: a new required input, a removed or retyped field, a removed choice, a new required header, a different method or path) or **non-breaking** (a new optional input, a field that became optional, a new choice, integer widened to number, a response that only gained fields). The categories follow oasdiff's backward-compatibility checklist. Request changes are graded from the requests observed since the change, not from the merged schema: a new field counts as required until a request without it is seen (the grade then becomes non-breaking), a retyped field is breaking until its reviewed type is sent again, and a required field that requests stopped sending became optional (non-breaking). Grades are updated as observations arrive while the tool awaits review. The grade only helps reviewers: any schema drift still disables the tool until someone reviews it.

A tool whose request has not been seen for 14 days (`TELLTALE_STALE_TOOL_DAYS`) is flagged stale in the dashboard, with a freshness score of 1/(1 + days/30); an enabled tool that turns stale also gets an update-log entry. Nothing is disabled. When agents' inputs are rejected by the runner, or calls return 4xx answers, Telltale keeps per-field and per-status counts (never values), and model-assisted descriptions use them to make the fields agents get wrong clearer.

## Which agents support WebMCP

Checked on November 5, 2026. This table is not updated automatically; each row says how we checked.
"Tested by Telltale" means a native WebMCP browser check (the browser itself listed and called the page's tools),
not Telltale's manual runner. The detailed tables under "Native WebMCP: what works today" list each developer client
and the exact builds we ran.

| Browser or agent | Status on 5 Nov 2026 | How we checked |
| --- | --- | --- |
| Google Chrome | Origin trial from Chrome 149; for local testing, the flag `chrome://flags/#enable-webmcp-testing` | Chrome documentation, [WebMCP](https://developer.chrome.com/docs/ai/webmcp) (updated 7 Oct 2026). Tested by Telltale on 28 Oct 2026: Chrome 156 started with the WebMCP feature flag registered and called our reviewed tools through developer clients (Chrome DevTools Protocol, agent-browser, chrome-devtools-mcp, and Claude Code through chrome-devtools-mcp). Not tested through the origin trial. |
| ChatGPT desktop app (built-in browser, used by ChatGPT Work and Codex) | Supported, as "site tools" | OpenAI documentation, [Site tools](https://learn.chatgpt.com/docs/webmcp): "Site tools currently run only in the ChatGPT desktop app's built-in browser." Tested by Telltale on 28 Oct 2026: the Codex desktop app's in-app browser listed and called our reviewed tools natively (one configuration). |
| Microsoft Edge | Origin trial (expires 30 Mar 2027) | Microsoft Edge origin trials, [WebMCP](https://developer.microsoft.com/en-us/microsoft-edge/origin-trials/trials/0b76fe60-b266-458e-a285-04e375c0c31a); the [WebMCP implementation status](https://github.com/webmachinelearning/webmcp/blob/main/implementation-status.md) lists it from Edge 150. Not tested by Telltale. |
| Gemini in Chrome | Announced, not released | Chrome blog, [Google I/O 2026](https://developer.chrome.com/blog/chrome-at-io26) (May 19, 2026): "Gemini in Chrome will soon support WebMCP APIs." No release found. |
| Brave (Leo) | Experimental, behind a flag in Nightly | Brave issue [#55232](https://github.com/brave/brave-browser/issues/55232) (open; flag `brave://flags/#enable-webmcp-testing`). Not tested by Telltale. |
| Claude in Chrome | No public support found | Anthropic's Claude in Chrome help pages and release notes mention no WebMCP support. Our Claude test above was Claude Code driving flagged Chrome, not Claude in Chrome. |
| Perplexity Comet | No public support found | No WebMCP mention found in Perplexity's announcements. |
| Firefox, Safari | Not implemented | WebMCP implementation status links only standards-position requests ([Mozilla](https://github.com/mozilla/standards-positions/issues/1412), [WebKit](https://github.com/WebKit/standards-positions/issues/670)). |

## Native WebMCP: what works today

WebMCP is a draft Community Group specification (Web Machine Learning CG). The page API is document.modelContext with registerTool(). The table reflects public sources checked on 2026-10-25; it will change, so verify in your target browser.

| Browser | document.modelContext | How to enable |
| --- | --- | --- |
| Chrome / Chromium desktop and Android, M146 and later | Behind a flag or origin trial | chrome://flags/#enable-webmcp-testing, or --enable-features=WebMCP; origin trial "WebMCP" M149–M156 (extension to M162 requested, not confirmed) |
| Chrome Stable 156 without flag or trial token | No | Not shipped; status Proposed |
| Edge 150 and later | Origin trial (expires 2027-03-30; checked 2026-11-05) | Microsoft Edge origin trial "WebMCP"; not tested by us |
| Firefox | No | Mozilla position: neutral; no implementation found |
| Safari / WebKit | No | WebKit position: oppose |

Older Chromium builds expose navigator.modelContext instead (for example the Chromium 149 bundled with agent-browser 0.39.2); the draft no longer defines it. The SDK registers on one context: the one whose registerTool is native browser code, so a page-script polyfill on document.modelContext cannot capture tools that a native navigator implementation would show to browser agents. When neither is native, it uses document.modelContext.

| Agent or client | Calls page tools natively? | Verified by us? | Notes and first-party source (checked 2026-10-25) |
| --- | --- | --- | --- |
| agent-browser 0.39.2 (vercel-labs) | Yes | Yes | webmcp list / invoke over the CDP WebMCP domain; launch Chrome with --enable-features=WebMCP. Run against this SDK. |
| chrome-devtools-mcp 1.12.0 | Yes, opt-in | Yes | --categoryExperimentalWebmcp adds list_webmcp_tools and execute_webmcp_tool. Run against this SDK. |
| Headless Claude Code through chrome-devtools-mcp | Yes, through chrome-devtools-mcp | Yes | Model-driven discovery and calls of this SDK's tools, through the chrome-devtools-mcp tools above. |
| Raw Chrome DevTools Protocol (WebMCP domain) | Yes | Yes | Listing and invoking over CDP directly, without an agent framework. |
| Chrome DevTools WebMCP pane | Manually | No | Run tool from the Application panel. |
| Google's webmcp-extension example agent | Yes | No | Truncates results at 8,000 characters and base64-encodes results marked untrusted. |
| ChatGPT desktop app built-in browser (ChatGPT Work and Codex) | Yes | Yes, this Codex in-app browser configuration (2026-10-28) | "Site tools" use WebMCP in the desktop app's built-in browser with GPT-5.6 Sol or Terra; not with GPT-5.6 Luna or in Enterprise or Edu workspaces. Codex changelog, 2026-08-25 (developers.openai.com/codex/changelog); ChatGPT release notes, 2026-08-31 entry (help.openai.com/en/articles/6825453-chatgpt-release-notes); help article help.openai.com/en/articles/20001423-using-site-tools-in-the-chatgpt-desktop-app. |
| ChatGPT browser extension (Chrome, Edge and others) | No, according to OpenAI | No | The 2026-08-31 release note says site tools are "not through the Chrome extension". |
| ChatGPT Atlas | Retired | No | OpenAI's release notes (2026-07-09 entry) scheduled Atlas to stop working on 2026-08-09, before site tools launched. |
| Gemini in Chrome | Announced, not confirmed shipped | No | Google: "Gemini in Chrome will soon support WebMCP APIs" (developer.chrome.com/blog/chrome-at-io26, 2026-05-19). No later first-party note says it shipped; Chrome's WebMCP docs (developer.chrome.com/docs/ai/webmcp, updated 2026-10-07) still describe a flag and origin trial only. |
| Microsoft Copilot in Edge | Not confirmed | No | No first-party release note found. |
| Claude in Chrome and other end-user browser agents | Not confirmed | No | No first-party statement found; they operate the page UI. |

On 2026-10-28, the actual Codex desktop in-app browser discovered and invoked our reviewed catalog and file-export tools through its native WebMCP capability. The catalog returned the expected item; the 70-byte PNG matched an independently fetched SHA-256; withdrawing consent removed every tool. This is one verified desktop-browser configuration, separate from the developer clients above. It does not establish support for every consumer agent or workflow.

## Results and files

The draft serializes a tool result to a JSON string and has no file type. Reviewed tools that return files include them in the result itself (text for text-like files, base64 for binary), with name, type, size and SHA-256 metadata, up to 32,000 characters for the whole result. Larger files are named with their size and hash and remain available only through a page-local download link for five minutes or until pause, reset or withdrawal. See File results in the [Browser SDK guide](/docs/sdk).

## Failures, cancellation and refresh

- A failed call resolves with `isError: true` instead of rejecting, because the browser reports a rejected tool callback to the agent only as an opaque error. The result has one fixed text item and `_meta["observatory/error"]` (a historical name from the product's earlier name, stable on purpose; see [Renamed names](/docs/sdk#renamed-names)) with a `code` (for example `http` with `httpStatus` and `step`, `invalid_input`, `approval_changed`, `busy`, `consent`, `expired`, `policy_refused`). Texts never include URLs, identifiers, response bodies or input values. See Agent failures in the Browser SDK guide for the full list.
- Only the caller's own abort rejects (AbortError). Cancellation follows the execution AbortSignal; a toolcancel event counts only when the browser dispatched it (isTrusted), since page scripts can construct one.
- **Versioned names.** Each reviewed tool registers as `<name>__<revision>`, for example `export_report__r0k3j9x2m`. The revision is `r` and 8 base-36 characters derived from what was reviewed and what the agent sees (the approval identity: kind, id, name, version, fingerprint or signature and description; plus title, inputSchema and annotations), so a revised tool is a new name and an agent can never call the updated tool under its old one. The stable part is shortened, never the revision, so the name stays within the 64-character identifier contract (tool prefix included). Schema drift still disables a tool, which unregisters its name; enabling the reviewed update publishes the new name. The tool manifest (`GET /api/sdk/tools`) gives each tool its `revision` and unprefixed `webmcpName`; the `X-Observatory-Tools-Revision` response header (CORS-exposed) and the `toolsRevision` field of every evidence upload answer name the catalogue revision, and the SDK re-registers as soon as an upload reports a different one instead of waiting for the 10-second refresh. The Site's reviewed OpenAPI document (`/api/v1/discovery/openapi.json`) lists the same values under `info["x-observatory-tools-revision"]` and `info["x-observatory-tools"]`. Rules, usage, webhooks, executions and `client.invokeTool()` keep using the stable name; approval is still rechecked before every call.
- The SDK compares tools by registered name and registration fingerprint on every refresh, unregisters names that are no longer current before registering new ones, and leaves unchanged tools registered, so they stay callable. A failed registration is retried for that name alone, with backoff; a name clash or permission refusal waits for the tool list or the page context to change.
- The file reader companion is `observatory_read_file` (a fixed definition, so it has no revision suffix). A truncation notice names it only after its registration succeeded.

## The in-page runner fallback

When document.modelContext is absent, or for agents that drive the page UI, the SDK still runs reviewed tools itself:

```javascript
const client = window.Telltale.getClient(); // the client the hosted tag, GTM, Zaraz or your code initialized
const result = await client.invokeTool('export_report', { query: { format: 'csv' } });
```

invokeTool() applies the same approval recheck, input validation, write confirmation, polling, cancellation and consent checks as native calls, and returns the same result object on success; on failure it rejects with an Error rather than returning an isError result. Pass options.signal to cancel. The init option onTools receives { tools, supported, registrationErrors } whenever the reviewed tool list, WebMCP support or the number of failed registrations changes, where supported says whether the page exposes a WebMCP registerTool, so your page can list tools for UI-driving agents or people. The dashboard's playground (/demo) shows this runner. It also offers a copyable prompt to give a browser agent, a demo-only form (fictional values, validated and discarded) and a live panel with this session's verdict, attribution and timeline, read from the page's own public collector responses. The playground is served on the loopback listener only, never on the public edge.

## Verifying in your browser

1. Launch Chrome with --enable-features=WebMCP (or enable chrome://flags/#enable-webmcp-testing) and open your page with the SDK permitted.
2. Check 'modelContext' in document in the console.
3. List tools with the DevTools WebMCP pane, agent-browser webmcp list or chrome-devtools-mcp list_webmcp_tools, then invoke one and confirm your backend authorized the request.

A manual in-page runner check is not native WebMCP verification; record which one you performed.

## Repairing a changed workflow

Control › Tool access › Changes links a blocked update directly to its multi-step tool. Open **Preview input updates**, review the reported changes, then choose **Repair with current inputs**. This loads current step versions into the existing editor while preserving the broken response bindings and polling conditions so you can choose their replacements explicitly. It does not guess that two fields with the same type mean the same thing.

Use the current input/response schemas to replace a binding's source or target pointer, or update a wait-and-retry response pointer and expected value. Missing fields and incompatible types are rejected before save and checked again by the server. **Save for review** leaves the workflow off; review and approve it separately before agents can use it. A preview from an older saved definition is refused, and schemas that change again must be reviewed again.
