Description of the bug
When a WebMCP tool's execute function throws, execute_webmcp_tool returns {"status": "Error", "errorText": ""} as a successful MCP result (isError: false). The thrown error's message and stack never reach the caller, although Chrome sends them in the WebMCP.toolResponded event's exception field and Puppeteer forwards that field. An agent debugging a page's WebMCP tools sees that the call failed but not why, and an MCP client that checks isError treats the failure as success.
Actual behavior
Both builds, identical output:
execute_webmcp_tool add -> isError=false {"status": "Completed", "output": 5}
execute_webmcp_tool fails -> isError=false {"status": "Error", "errorText": ""}
list_console_messages -> msgid=2 [error] WebMCP tool execution failed: Uncaught Error: boom from page (0 args)
A raw CDP control with Puppeteer alone (same Chrome, same page, WebMCP.enable then WebMCP.invokeTool for fails; run twice, output kept) received:
WebMCP.toolResponded {"status":"Error","errorText":"","exception":{"type":"object","subtype":"error","className":"Error","description":"Error: boom from page\n at execute (http://127.0.0.1:<port>/webmcp.html:4:158)", ...}}
So the reason is available to the server; it is dropped between Puppeteer's result and the tool response.
Reproduction
- Serve this page from a local HTTP server as
webmcp.html:
<!doctype html><title>WebMCP throw</title>
<script>
document.modelContext.registerTool({name: 'add', description: 'Add two numbers', inputSchema: {type: 'object', properties: {a: {type: 'number'}, b: {type: 'number'}}}, execute: async ({a, b}) => String(a + b)});
document.modelContext.registerTool({name: 'fails', description: 'Always throws', inputSchema: {type: 'object', properties: {}}, execute: async () => { throw new Error('boom from page'); }});
</script>
- Send these tool calls on both builds:
{"name":"new_page","arguments":{"url":"http://127.0.0.1:<port>/webmcp.html"}}
{"name":"execute_webmcp_tool","arguments":{"pageId":2,"toolName":"add","input":"{\"a\":2,\"b\":3}"}}
{"name":"execute_webmcp_tool","arguments":{"pageId":2,"toolName":"fails"}}
{"name":"list_console_messages","arguments":{"pageId":2}}
Expectation
execute_webmcp_tool "Executes a WebMCP tool exposed by the page" and reports the invocation's status, output and errorText. The Chrome DevTools Protocol defines WebMCP.toolResponded.exception as "The exception object, if the javascript tool threw an error", and Puppeteer's WebMCPToolCallResult (bundled Puppeteer 25.12.0) carries it as exception. When the page tool throws, the result should include the thrown error (at least its description, Error: boom from page) and be marked as a tool error, as execute_3p_developer_tool does for a third-party tool that throws (it returns isError: true with the error message).
MCP configuration
--headless --isolated --no-usage-statistics --no-performance-crux unless the reproduction states otherwise (update checks disabled with CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=1).
Chrome DevTools MCP version
1.10.1 (npm) and main 5ddb0a3 (local build)
Chrome version
154.0.8037.98 (stable)
Node version
v22.23.2
Operating system
macOS 27.0
Environment details
chrome-devtools-mcp 1.10.1 from npm (the release build) and upstream main at commit 5ddb0a3110c5059f8e5513e566196cce2a35c6d1 built from source (the main build).
- Google Chrome 154.0.8037.98 (stable), Node.js v22.23.2, macOS 27.
- Server started with
--isolated --headless --no-usage-statistics --no-performance-crux --categoryExperimentalWebmcp --chromeArg=--enable-features=WebMCP and CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=1, driven by a minimal stdio MCP client.
Evidence
- Expected source:
execute_webmcp_tool description in docs/tool-reference.md (WebMCP section); CDP WebMCP.toolResponded parameter exception in the bundled devtools-protocol browser_protocol.json; Puppeteer WebMCPToolCallResult.exception in node_modules/puppeteer-core/src/cdp/WebMCP.ts on the main build.
- Failure source: my runs of the steps above on both builds, outputs quoted under Actual behavior; static breadcrumb on the
main build: src/tools/webmcp.ts destructures only {status, output, errorText} from tool.execute(input) and always appends it as a normal response line.
- Evidence provenance: observed
- Local verification: reproduced
- Reproduction completeness: complete
Fix check
Run the steps above. Failing: the fails call returns isError: false and a body whose only error information is "errorText": "". Passing: the fails call's result contains boom from page (from the exception description) and is flagged as an error (isError: true). Control: the add call still returns {"status": "Completed", "output": 5} with isError: false.
Additional context
The full open and closed issue and pull request corpus was screened as of 2026-10-06; no issue covers WebMCP error reporting. Open pull request #2900 routes WebMCP tools annotated for debugging through the third-party developer tools and, in its new McpPage path, throws errorText || 'Tool execution failed: <status>'; that path would still drop the exception text, and the plain execute_webmcp_tool handler is unchanged there. Merged #2785 only rejects array inputs.
Description of the bug
When a WebMCP tool's
executefunction throws,execute_webmcp_toolreturns{"status": "Error", "errorText": ""}as a successful MCP result (isError: false). The thrown error's message and stack never reach the caller, although Chrome sends them in theWebMCP.toolRespondedevent'sexceptionfield and Puppeteer forwards that field. An agent debugging a page's WebMCP tools sees that the call failed but not why, and an MCP client that checksisErrortreats the failure as success.Actual behavior
Both builds, identical output:
A raw CDP control with Puppeteer alone (same Chrome, same page,
WebMCP.enablethenWebMCP.invokeToolforfails; run twice, output kept) received:So the reason is available to the server; it is dropped between Puppeteer's result and the tool response.
Reproduction
webmcp.html:{"name":"new_page","arguments":{"url":"http://127.0.0.1:<port>/webmcp.html"}} {"name":"execute_webmcp_tool","arguments":{"pageId":2,"toolName":"add","input":"{\"a\":2,\"b\":3}"}} {"name":"execute_webmcp_tool","arguments":{"pageId":2,"toolName":"fails"}} {"name":"list_console_messages","arguments":{"pageId":2}}Expectation
execute_webmcp_tool"Executes a WebMCP tool exposed by the page" and reports the invocation'sstatus,outputanderrorText. The Chrome DevTools Protocol definesWebMCP.toolResponded.exceptionas "The exception object, if the javascript tool threw an error", and Puppeteer'sWebMCPToolCallResult(bundled Puppeteer 25.12.0) carries it asexception. When the page tool throws, the result should include the thrown error (at least its description,Error: boom from page) and be marked as a tool error, asexecute_3p_developer_tooldoes for a third-party tool that throws (it returnsisError: truewith the error message).MCP configuration
--headless --isolated --no-usage-statistics --no-performance-cruxunless the reproduction states otherwise (update checks disabled withCHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=1).Chrome DevTools MCP version
1.10.1 (npm) and
main5ddb0a3 (local build)Chrome version
154.0.8037.98 (stable)
Node version
v22.23.2
Operating system
macOS 27.0
Environment details
chrome-devtools-mcp1.10.1 from npm (the release build) and upstreammainat commit5ddb0a3110c5059f8e5513e566196cce2a35c6d1built from source (themainbuild).--isolated --headless --no-usage-statistics --no-performance-crux --categoryExperimentalWebmcp --chromeArg=--enable-features=WebMCPandCHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS=1, driven by a minimal stdio MCP client.Evidence
execute_webmcp_tooldescription indocs/tool-reference.md(WebMCP section); CDPWebMCP.toolRespondedparameterexceptionin the bundleddevtools-protocolbrowser_protocol.json; PuppeteerWebMCPToolCallResult.exceptioninnode_modules/puppeteer-core/src/cdp/WebMCP.tson themainbuild.mainbuild:src/tools/webmcp.tsdestructures only{status, output, errorText}fromtool.execute(input)and always appends it as a normal response line.Fix check
Run the steps above. Failing: the
failscall returnsisError: falseand a body whose only error information is"errorText": "". Passing: thefailscall's result containsboom from page(from the exception description) and is flagged as an error (isError: true). Control: theaddcall still returns{"status": "Completed", "output": 5}withisError: false.Additional context
The full open and closed issue and pull request corpus was screened as of 2026-10-06; no issue covers WebMCP error reporting. Open pull request #2900 routes WebMCP tools annotated for debugging through the third-party developer tools and, in its new
McpPagepath, throwserrorText || 'Tool execution failed: <status>'; that path would still drop the exception text, and the plainexecute_webmcp_toolhandler is unchanged there. Merged #2785 only rejects array inputs.