microsoft/playwright · error · Error
WebMCP is not available on this page
Error message
WebMCP is not available on this page
What it means
This error is thrown inside an injected page-side function: neither document.modelContext nor navigator.modelContext is present, meaning the page does not expose the WebMCP model-context API. Only pages that registered a WebMCP ModelContext can have tools invoked in them.
Solutions
- Navigate to a page that actually registers a WebMCP modelContext before calling tools.
- Verify document.modelContext or navigator.modelContext exists in the target frame (evaluate a probe expression).
- Ensure the browser build supports WebMCP and that the page's MCP registration script has finished loading.
Example fix
// before
await client.callTool('webmcp-call', { params: { tool: 'get_weather', inputJson: '{}' } }); // page has no modelContext
// after
await page.goto('https://example.com/webmcp-enabled');
await page.waitForFunction(() => 'modelContext' in navigator || 'modelContext' in document);
await client.callTool('webmcp-call', { params: { tool: 'get_weather', inputJson: '{}' } }); Defensive patterns
Strategy: validation
Validate before calling
const hasWebMcp = await page.evaluate(() => 'modelContext' in navigator || 'modelContext' in document);
if (!hasWebMcp) throw new Error('This page does not expose WebMCP modelContext'); Type guard
function pageHasModelContext(doc: Document): doc is Document & { modelContext: object } {
return 'modelContext' in doc || 'modelContext' in navigator;
} Try / catch
try {
return await webmcpCall(frame, tool, inputJson);
} catch (e) {
if (e.message.includes('WebMCP is not available')) throw new Error(`Frame ${frame.url()} has no WebMCP modelContext`);
throw e;
} Prevention
- Probe for modelContext before dispatching tool calls.
- Verify the page's MCP registration script has loaded (wait for the global).
- Confirm the browser build supports WebMCP.
When it happens
Trigger: Invoking a WebMCP tool call (via the webmcp backend callToolInPage) against a frame whose document/navigator lack a modelContext — e.g. an about:blank page, a non-MCP-enabled site, or a browser build without WebMCP support.
Common situations: Pointing the WebMCP CLI/tooling at the wrong tab or frame; testing on a page before its WebMCP polyfill/script has loaded; running against an older browser that has not shipped the modelContext API.
Related errors
- CDP session is only available in Chromium
- kCoverageStashError + ': ' + error
- page: expected Page or Frame
- Playwright Extension not found in
- The gallery page does not define window.mount().
AI-assisted analysis of microsoft/playwright@f1d33b5029 (2026-09-15).
Data as JSON: /api/errors/1c572a347524abe7.
Report an issue: GitHub.
Appendix: source
Thrown at packages/playwright-core/src/tools/backend/webmcp.ts:128
title: tool.title || undefined,
description: tool.description ?? '',
inputSchema,
// The JS surface uses the `*Hint` names, the CDP WebMCP domain uses the short ones.
annotations: annotations ? {
readOnly: annotations.readOnlyHint ?? annotations.readOnly,
untrustedContent: annotations.untrustedContentHint ?? annotations.untrustedContent,
consequential: annotations.consequentialHint ?? annotations.consequential,
} : undefined,
origin: tool.origin,
};
}));
}
function callToolInPage(params: { name: string, inputJson: string }) {
const modelContext = (document as DocumentWithModelContext).modelContext
?? (navigator as NavigatorWithModelContext).modelContext;
if (!modelContext)
throw new Error('WebMCP is not available on this page');
const stringify = (result: unknown) => result === undefined ? 'null' : JSON.stringify(result);
if (modelContext.executeTool) {
// Chromium: executeTool(registeredTool, inputJsonString) resolves to a JSON string.
return Promise.resolve(modelContext.getTools!()).then(tools => {
const tool = tools.filter(t => !('window' in t) || t.window === window).find(t => t.name === params.name);
if (!tool)
throw new Error(`WebMCP tool "${params.name}" is not registered in this frame`);
return modelContext.executeTool!(tool, params.inputJson);
}).then(result => typeof result === 'string' ? result : stringify(result));
}
// Firefox: invokeTool(name, inputObject) resolves to the value itself.
return Promise.resolve(modelContext.invokeTool!(params.name, JSON.parse(params.inputJson))).then(stringify);
}
const kTimedOut = Symbol('timedOut');
async function withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T | typeof kTimedOut> {
let timer: NodeJS.Timeout | undefined;View on GitHub (pinned to f1d33b5029)