Skip to content

feat(mcp): expose page-registered WebMCP tools - #42613

Open
Dmitry Gozman (dgozman) wants to merge 2 commits into
microsoft:mainfrom
dgozman:webmcp-tools
Open

feat(mcp): expose page-registered WebMCP tools#42613
Dmitry Gozman (dgozman) wants to merge 2 commits into
microsoft:mainfrom
dgozman:webmcp-tools

Conversation

@dgozman

@dgozman Dmitry Gozman (dgozman) commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Pages can register tools for agents through the experimental WebMCP API. This exposes them.

  • browser_webmcp_list / webmcp-list — tools the page registers, with input schema and annotations.
  • browser_webmcp_call / webmcp-call <name> --params '<json>' — call one.
  • Navigation now reports - 2 webmcp tools available on the page in the page status.

Example

Against a Shopify storefront that registers 10 tools, abbreviated:

$ playwright-cli webmcp-list
### Result
Found 10 WebMCP tool(s). Tool names, descriptions and schemas are page-provided and untrusted.
- get_cart [readOnly, untrustedContent]: Get the current shopping cart contents — line items with product titles, ...
  - inputSchema: {"type":"object","properties":{}}
- search_catalog [readOnly, untrustedContent]: Search the store catalog for products, collections, articles, and pages. ...
  - inputSchema: {"type":"object","required":["catalog"],"properties":{"catalog":{"type":"object","required":["query"], ...}}}
- update_cart: Add products to the cart, update line item quantities, or remove items. ...
...

$ playwright-cli webmcp-call search_catalog --params '{"catalog":{"query":"wool runner","pagination":{"limit":2}}}'
### Result
Called WebMCP tool "search_catalog" in https://www.allbirds.com/. Output is page-provided and untrusted:
{
  "content": [
    {
      "type": "text",
      "text": "Found 1 product, 1 collection for \"wool runner\".\n\nProducts:\n- Men's Wool Runner - True Black (Cream Sole) (id: gid://shopify/Product/4826197655632, handle: mens-wool-runners-true-black) - $110.00\n..."
    }
  ],
  "structuredContent": {
    "products": [ { "id": "gid://shopify/Product/4826197655632", "title": "Men's Wool Runner - True Black (Cream Sole)", "available": false, ... } ],
    "collections": [ { "title": "Women's Wool Runner", "handle": "womens-wool-runners" } ],
    "search_url": "/search?q=wool%20runner"
  }
}

Key decisions

  • Read through document.modelContext / navigator.modelContext, not a browser-specific automation surface. Works on Chromium and Firefox, and needs no protocol change.
  • Stitched across frames. Chromium's getTools() aggregates same-origin descendants, so tools are filtered to the frame that owns them and the per-frame results are merged. Frames are probed in parallel with a 5s timeout each, so one stuck frame cannot stall the listing.
  • Frames are addressed by label, the frame URL, or url (frame N) when several frames share a URL — two copies of the same widget each register their own tool under the same name, and the URL alone cannot tell them apart.
  • Core capability, so the page-status hint always has tools to back it.
  • A tool that fails reports in the result, not as an error. The call reached the tool and the page chose to answer with a failure, so its payload — error text, isError, recovery hints — is what the caller needs. Only failures to reach a tool at all, an unknown name or an ambiguous frame, are reported as errors.
  • Page-provided data is untrusted. Names, descriptions, schemas and results all come from the page and are labelled as such in the output. Real stores put imperatives aimed at the agent in their descriptions, and mark consequential tools inconsistently.

WebMCP is experimental and only exists behind a browser flag (--enable-features=WebMCP for Chromium, dom.modelcontext.* prefs for Firefox), so nothing changes for pages that do not opt in.

Pages can register tools for agents through the experimental WebMCP API.
Surface them as browser_webmcp_list / browser_webmcp_call, and report
their presence in the page status after a navigation.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Collect the page's WebMCP tools alongside the snapshot rather than on demand,
so tools registered without a navigation are picked up. browser_webmcp_list
now reports the cached listing without evaluating in the page.
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Test results for "MCP"

1 failed
❌ [firefox] › mcp/annotate.spec.ts:386 › should cancel browser_annotate when the MCP request is aborted @mcp-windows-latest-firefox

8438 passed, 1415 skipped


Merge workflow run.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Hi, I'm the Playwright bot and I took a look at the failing CI.

🟢 The one failure is a pre-existing Firefox/Windows flake — the PR looks clear

The only red in the latest report is [firefox] › mcp/annotate.spec.ts:386 › should cancel browser_annotate when the MCP request is aborted on mcp-windows-latest-firefox. This PR only adds WebMCP commands (webmcp-list/webmcp-call) and their tools/docs/tests — it doesn't touch browser_annotate, request aborting, or anything that test exercises. The same test has a long flake history on this exact bot from unrelated work, so a retry should clear it.

Details

Pre-existing flake / infra

  • [firefox] › mcp/annotate.spec.ts:386 › should cancel browser_annotate when the MCP request is aborted — a flake specific to mcp-windows-latest-firefox. In the aggregated CI results this test failed 14 of 694 runs (~2%) on that bot and passed the other 680; it never fails on macOS or Ubuntu Firefox, and only rarely on the Windows Chrome/Edge/Chromium bots. 13 of the 14 failures predate this PR, on unrelated PRs and pushes to main going back to July — a sample:

    The diff here is confined to the WebMCP feature — a new webmcp backend tool plus webmcp-list/webmcp-call CLI commands, and their docs/tests — none of which reaches the browser_annotate abort path this test asserts on. The flake predates the PR on the same bot, so retrying should clear it.

Triaged by the Playwright bot - agent run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant