Anyone who writes Playwright tests has the reflex: load the page, find the button, click it, check what changed. WebMCP, a browser API still in draft, gives pages a second surface. It consists of named tools an agent can call directly instead of clicking. Each tool is a JavaScript function that comes with a plain-language description and an input schema, which is a JSON Schema listing the arguments the function expects. Playwright 1.64 brought these tools into the same test harness, and the reflex carries over almost unchanged. While you run it, keep an eye on the two layers that sit between your test and your tool, the browser and Playwright itself, and on what each of them actually checks for you.
List the tools
With Chromium launched under --enable-features=WebMCP, one line returns the page's tools:
const tools = await page.webmcp.tools();Each entry has a name, a description, an optional inputSchema, and annotations such as readOnly. Assert on them as you would on a button's label. The tool you depend on should exist, under the name you expect.
Call one
Using the adder from SitePoint's walkthrough:
const result = await page.webmcp.callTool('add_numbers', { a: 3, b: 7 });What comes back is a content array, the standard shape for tool output, holding the text 10.
Those arguments travel a path where nothing is required to compare them with the input schema. The draft's call algorithm parses them as JSON, rejects anything that isn't an object, and runs your function. Playwright adds no schema check of its own. That means { a: "3", b: 7 }, or a call with no b at all, can reach your code as well. A button never prompts this kind of test, because a click carries no arguments, but a tool call does. So send a malformed call and assert that your tool refuses it.
You also have to decide what refusal looks like. A tool that fails politely returns a result flagged isError: true. callTool() throws when no tool by that name exists or when its function throws, but a returned isError: true arrives as an ordinary value. A test that asserts nothing beyond "didn't throw" will pass while the tool reports its own failure.
Check what comes back
On November 2 the draft gained an optional outputSchema describing what a tool returns. The draft says only that agents "may reject outputs that do not validate," which leaves the check optional for every agent. If you want it to happen reliably, your suite is where it will happen. Playwright 1.64 shipped on October 7, so its descriptors don't carry the field yet. You can copy the schema from your page's tool registration into a contract file. If the tool declares no schema, and the adder doesn't, write one from what you expect it to return. The schema covers the whole returned value, so validate result itself, content array and all, with a library such as Ajv:
const validate = ajv.compile(outputSchema);
expect(validate(result), JSON.stringify(validate.errors)).toBe(true);Who else is checking
The Community Group's validation thread records browser-side validation as the intended direction. For now, enforcing what tools declare about themselves falls to the people who wrote them. That division of labor follows an old principle that network engineers call the end-to-end argument: only the endpoint that knows what a message is supposed to mean can verify it. Your site knows what add_numbers should return; the browser in the middle only knows it handled valid JSON.
Testing against a moving draft
Playwright's docs place the API at navigator.modelContext, but in May the draft moved it to document. Playwright looks on document first and falls back to navigator, because Chromium and Firefox put it in different places. So the docs, the spec and the browsers disagree, and the harness reconciles them without telling you it has done so.
Pin your Playwright and browser versions, then, and keep schemas in files your suite reads. The draft will shift again, and when it does, you would rather find out from a red test in CI than from an agent in production that accepted a malformed result and acted on it.
-
Who owns validation: The WebMCP Community Group's open issue #92 records a March resolution that favors browser validation of schemas, inputs and outputs, though the research and spec text are still unwritten, so watch it for the moment your suite stops being the only check.
-
What the harness changes: Playwright 1.64's injected WebMCP adapter tries to parse a string result as JSON on the Chromium path before returning it, so the value your test validates may differ from what the tool's function literally returned.
-
Matching your schema draft: Ajv's default export handles draft-07, while draft-2020-12 schemas need a separate class, as its supported schema versions page explains, and that matters for which validator you import before you copy an
outputSchemainto a contract file. -
Poisoned tool metadata: The peer-reviewed MCPTox benchmark built 1,348 malicious test cases from 45 live MCP servers and showed that descriptors themselves can carry attacks, which is a reason to treat the descriptor list your tests assert on as something to scrutinize as well as confirm.

