news 2026/9/8 16:27:10

Puppeteer 中的 WebMCP 类:通过 page.webmcp 发现与调用页面暴露的 Agent 工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Puppeteer 中的 WebMCP 类:通过 page.webmcp 发现与调用页面暴露的 Agent 工具

Puppeteer 中的 WebMCP 类:通过 page.webmcp 发现与调用页面暴露的 Agent 工具

【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer

WebMCP 是 Puppeteer 中一处尚处于实验阶段的 API 面,它让 Node.js 侧的脚本能够发现当前页面中注册的"工具"(tools),并监听工具的注册、移除与调用事件。读完本文,你将掌握page.webmcp的接入前提、WebMCP类的四个核心事件与tools()方法的使用方式,以及与之配套的WebMCPToolWebMCPToolCall等类型的字段语义,并能在真实页面中驱动一次完整的工具调用。

本文以 WebMCP 类 API 文档 为主体展开,结合 Puppeteer 仓库中 CDP 层实现 与 测试用例 进行源码级印证。

WebMCP 类概览:页面工具与自动化端的桥梁

WebMCP类是 Puppeteer 对 WebMCP(Web Model Context Protocol)能力在页面侧的封装。按照 API 索引 的定位,它"provides an API for the WebMCP API"——即让自动化代码访问页面定义的 WebMCP 工具。

从 类签名文档 可以看到它的完整声明:

export declare class WebMCP extends EventEmitter<{ toolsadded: WebMCPToolsAddedEvent; toolsremoved: WebMCPToolsRemovedEvent; toolinvoked: WebMCPToolCall; toolresponded: WebMCPToolCallResult; }>

它继承自 EventEmitter,并把四类事件类型参数化到泛型中。在实际源码里,对应的类定义位于 packages/puppeteer-core/src/cdp/WebMCP.ts#L255-L264,内部持有CDPSessionFrameManager以及用于缓存工具、追踪进行中调用的几个Map字段:

export class WebMCP extends EventEmitter<{ toolsadded: WebMCPToolsAddedEvent; toolsremoved: WebMCPToolsRemovedEvent; toolinvoked: WebMCPToolCall; toolresponded: WebMCPToolCallResult; }> { #client: CDPSession; #frameManager: FrameManager; #tools = new Map<string, Map<string, WebMCPTool>>(); #pendingCalls = new Map<string, WebMCPToolCall>(); #subscriptions = new DisposableStack(); // ... }

从源码结构看,WebMCP 是典型的 CDP 驱动实现:initialize()时向浏览器发送WebMCP.enable命令(WebMCP.ts#L371-L375),随后把WebMCP.toolsAddedWebMCP.toolsRemovedWebMCP.toolInvokedWebMCP.toolResponded四条 CDP 事件通过 DisposableStack 订阅并翻译成上面的四个 EventEmitter 事件。

访问入口:page.webmcp 与其环境前提

WebMCP实例不需要也不能由第三方代码直接构造——文档的 Remarks 明确说明:

The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend theWebMCPclass.

在 API 文档中它属于公开类型(@public),但用@experimental标注(见 WebMCP.ts 源码注释),构造器仅供 Puppeteer 内部使用。

应用侧统一通过Page上的webmcp属性访问。这一抽象 getter 定义在 Page.ts#L1004-L1010:

/** * Experimental API for WebMCP. * Requires Chrome 151+ with the `--enable-features=WebMCP` flag enabled. * @experimental */ abstract get webmcp(): WebMCP;

对应的 page.webmcp 属性文档 也给出了两个关键限制,这两点是实际运行前必须满足的前提:

  • 浏览器版本:需要 Chrome 151 及以上的版本;
  • 启动参数:必须以--enable-features=WebMCP启动 Chromium。

测试代码正是这样配置的。在 webmcp.test.ts#L20-L24 中,整个测试套件通过setupSeparateTestBrowserHooks为浏览器注入args: ['--enable-features=WebMCP']

describe('Page.webmcp', function () { const state = setupSeparateTestBrowserHooks({ args: ['--enable-features=WebMCP'], acceptInsecureCerts: true, });

因此在你的代码里,启动浏览器时需要类似写法:

import puppeteer from 'puppeteer'; const browser = await puppeteer.launch({ headless: true, args: ['--enable-features=WebMCP'], }); const page = await browser.newPage();

如果使用了 Puppeteer 的connect连接远端浏览器,同样需要确保远端 Chrome 版本 ≥ 151 且以该特性开关启动。由于目前是实验特性,后续版本中 API 形态可能变化,建议在使用时固定 Puppeteer 版本。

枚举页面工具:tools() 方法

WebMCP目前对外暴露的唯一方法就是tools()。其签名与返回类型见 WebMCP.tools() 方法文档:

class WebMCP { tools(): WebMCPTool[]; }

它返回页面上全部已注册的 WebMCP 工具数组,每个元素是一个 WebMCPTool 实例。底层实现是对内部两级Map(frameId → 工具名 → 工具)做扁平化(WebMCP.ts#L404-L411)。

原文档给出了最典型的用法——页面加载完成后枚举工具并打印其名称与描述(示例):

await page.goto('https://www.example.com'); const tools = page.webmcp.tools(); for (const tool of tools) { console.log(`Tool found: ${tool.name} - ${tool.description}`); }

在 测试用例 中可以看到tools()返回结果的完整断言:当页面分别通过"命令式注册"(调用document.modelContext.registerTool)与"声明式声明"(往 DOM 中追加带toolname/tooldescription属性的<form>)两种方式暴露工具后,page.webmcp.tools()会返回两条工具记录,其namedescriptioninputSchemaannotationsframeformElementlocation等字段会被逐一校验。

需要留意的生命周期语义

结合测试可以发现两个与工具集合管理直接相关的边界行为:

  • 整页导航会清空工具toolsremoved事件在 frame 上下文销毁时被触发(源码见 onContextDisposed),测试 should remove tools on frame navigation 验证了 reload 后tools()长度回到 0;
  • 同文档导航(hash 跳转)不会清空工具:上下文未被销毁,工具集合保持不变(测试用例 L363-L387)。

事件体系:toolsadded / toolsremoved / toolinvoked / toolresponded

WebMCP 是一套事件驱动模型,四项事件分别对应"工具上架、工具下架、工具被调用、调用有结果"四个阶段。原文档通过类签名声明了这四个事件,测试用例则逐个验证了它们的触发时机与载荷。

事件载荷类型触发时机对应文档
toolsaddedWebMCPToolsAddedEvent({tools: WebMCPTool[]}页面注册了新的工具源码
toolsremovedWebMCPToolsRemovedEvent({tools: WebMCPTool[]}页面移除工具,或所在 frame 被销毁源码
toolinvokedWebMCPToolCall页面侧发起一次工具调用源码
toolrespondedWebMCPToolCallResult工具调用完成、失败或被取消源码

监听方式与 EventEmitter 完全一致,例如在 toolinvoked 事件测试 中所示:

const tools = page.webmcp.tools(); page.webmcp.on('toolsadded', event => { console.log('added', event.tools.map(t => t.name)); }); page.webmcp.on('toolsremoved', event => { console.log('removed', event.tools.map(t => t.name)); }); page.webmcp.on('toolinvoked', call => { console.log('invoked', call.tool.name, call.input); }); page.webmcp.on('toolresponded', response => { console.log('responded', response.id, response.status, response.output); });

注意同一把事件也会"穿透"到具体工具上:WebMCPTool本身也继承EventEmitter<{toolinvoked: WebMCPToolCall}>(声明见 docs/api/puppeteer.webmcptool.md),因此既可以page.webmcp.on('toolinvoked', ...)全局监听,也可以tool.once('toolinvoked', ...)针对某个工具监听(测试中两种方式都被使用,见 test L419-L425)。

WebMCPTool:单个工具的对象化表示

tools()返回的每个 WebMCPTool 是一个 EventEmitter 子类,把页面暴露的工具元数据完整地对象化。其公开属性如下:

属性类型说明
namestring工具名称
descriptionstring工具描述
inputSchema(可选)object工具输入参数对应的 JSON Schema
annotations(可选)Protocol.WebMCP.Annotation工具的可选标注(如只读提示、不可信内容提示)
frameFrame该工具被定义所在的 frame
location(可选)ConsoleMessageLocation定义工具的源码位置(若可用)
formElement(只读)Promise<ElementHandle<HTMLFormElement> \| undefined>工具若通过<form>声明式注册,则对应其表单元素句柄
rawStackTraceProtocol.Runtime.StackTrace(内部字段)原始调用栈

对应实现见 WebMCP.ts#L26-L132。其中location是从工具注册时的stackTrace首帧解析而来(L75-L82),所以"命令式"注册的工具通常能拿到定义位置,而声明式(纯 HTML)注册的工具该项为空——测试断言也印证了这一差异。

formElement属于懒加载属性:只有当工具经由表单注册(携带 backendNodeId)时才有值,否则返回undefined;有值时它会把 backend node 采纳为主世界中的 ElementHandle(实现见 L88-L103)。

发起调用:execute() 与工具调用结果

虽然页面侧的工具通常由页面自己的逻辑(或页面内的 agent)来触发,WebMCPTool也提供了从自动化侧主动调用工具的方法。签名见 WebMCPTool.execute() 方法文档:

class WebMCPTool { execute( input?: object, options?: WebMCPToolExecuteOptions, ): Promise<WebMCPToolCallResult>; }
  • input:调用参数对象,需与工具的inputSchema匹配;
  • options:可传{signal: AbortSignal}(类型定义见 WebMCPToolExecuteOptions),用于取消仍在执行的调用。

其内部流程(WebMCP.ts#L108-L131)分两步:先经invokeTool()发送 CDP 命令WebMCP.invokeTool(携带frameIdtoolNameinput,见 L380-L389)拿到invocationId,随后挂起等待toolresponded事件中与invocationId匹配的结果;若传入的AbortSignal被触发,则回退到WebMCP.cancelInvocationCDP 命令请求取消。

返回的 WebMCPToolCallResult 字段如下:

字段类型说明
idstring调用标识(与WebMCPToolCall.id对应)
call(可选)WebMCPToolCall本次调用对应的调用对象(若在 pending 表中可找到)
statusProtocol.WebMCP.InvocationStatus调用状态
output(可选)any结果输出;仅当statusCompleted时存在
errorText(可选)string错误文本
exception(可选)Protocol.Runtime.RemoteObject若工具内 JS 抛异常,则为对应的异常远程对象

用 execute() 驱动一次完整调用

结合 should invoke tool 测试,完整流程如下:

// 页面内提前注册工具(命令式 WebMCP 工具) await page.evaluate(async () => { await document.modelContext?.registerTool({ name: 'test-tool-1', description: 'A test tool 1', inputSchema: { type: 'object', properties: {text: {type: 'string', description: 'Some text'}}, required: ['text'], }, execute: (params: {text: string}) => { return `hello ${params.text}`; }, }); }); // 等待工具被发现 await new Promise(resolve => { page.webmcp.once('toolsadded', resolve); }); // 自动化侧直接调用 const [tool] = page.webmcp.tools(); const response = await tool!.execute({text: 'world'}); console.log(response.status); // 'Completed' console.log(response.output); // 'hello world'

status 的三种状态

从测试中可以归纳出status的取值语义,它们与工具本身的执行结果一一对应:

  • Completed:工具成功返回,output携带结果(测试 L462-L512);
  • Error:工具内部抛出了 JS 异常(此时exception.description含错误信息,errorText为空字符串),或输入参数解析失败(如传入非法 JSON,此时errorText'Failed to parse input arguments'),参见 L514-L602;
  • Canceled:调用通过 AbortSignal 被取消(L653-L767)。

取消既可以在调用中途执行(controller.abort()在调用已经开始后才触发),也可以在调用前就把 signal 置为已中止——两种情况下结果状态都是Canceled

const controller = new AbortController(); const executePromise = tool!.execute({text: 'world'}, {signal: controller.signal}); // …一段时间后决定取消 controller.abort(); const response = await executePromise; // status === 'Canceled'

从事件到调用的完整协作视图

把上面各节串起来,一次 WebMCP 交互的生命周期是:

  1. 页面脚本调用document.modelContext.registerTool(...)或向 DOM 追加带toolname<form>,注册工具;
  2. 浏览器发出WebMCP.toolsAdded,Puppeteer 包装成toolsadded事件并更新内部工具表,之后page.webmcp.tools()可枚举到该工具;
  3. 页面(或自动化侧调用tool.execute())发起调用,toolinvoked事件先于结果到达,携带 WebMCPToolCall(含idtoolinput);
  4. 工具执行完成/出错/被取消后,toolresponded事件携带 WebMCPToolCallResult 到达,其id与对应的WebMCPToolCall.id一致;
  5. 若用户导航离开或重新加载页面,frame 上下文销毁,触发toolsremoved并清空待处理调用表(源码见 onContextDisposed)。

在 webmcp.test.ts 中,should fire toolinvoked eventsshould fire toolresponded event with success / with exception / with errorTextshould invoke toolshould cancel tool execution等一系列用例完整覆盖了上述链路,是理解该实验特性最直观的可运行参考。

小结

page.webmcp为 Puppeteer 提供了一块访问"页面声明的 WebMCP 工具"的实验性入口。核心使用要点可归纳为:

  • 环境:Chrome 151+,且启动时携带--enable-features=WebMCP
  • 发现page.webmcp.tools()枚举当前页面全部工具,得到 WebMCPTool 数组;
  • 订阅:监听toolsadded/toolsremoved跟踪工具上/下架,监听toolinvoked/toolresponded跟踪调用过程;
  • 执行与取消tool.execute(input, {signal})从自动化侧主动调用,结果含Completed/Error/Canceled三种状态。

由于该特性仍处于实验阶段(构造器内部化、API 标注@experimental),使用时请以 docs/api/puppeteer.webmcp.md 及 Page.webmcp 属性文档 为基准,并及时跟进新版本 Puppeteer 的 CHANGELOG 以应对可能的接口调整。

【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 16:26:03

Flutter端侧声音克隆TTS实战:sherpa-onnx + ZipVoice离线方案

如果你正准备在 Flutter 里做一款带“声音克隆”能力的离线文字转语音应用&#xff0c;也就是用户录几秒钟自己的声音&#xff0c;然后 App 就能用这个音色把文字读出来&#xff0c;那这套组合应该是当前社区里少见的、能完整跑通的端侧方案&#xff1a;sherpa-onnx 负责离线 T…

作者头像 李华
网站建设 2026/9/8 16:25:49

STM32C5轮询读取LSM6D3TR-C陀螺仪数据:从寄存器配置到物理量换算

1. 项目概述与选型背景1.1 这颗芯片和传感器组合的来龙去脉STM32C5是意法半导体近期主推的入门级Cortex-M33内核MCU系列&#xff0c;主频能跑到250MHz级别&#xff0c;片内集成FPU和DSP指令集&#xff0c;放在几年前这配置妥妥是中高端定位&#xff0c;现在下放到入门系列&…

作者头像 李华
网站建设 2026/9/8 16:24:45

装饰器: 在不改变原函数的基础上, 动态给函数增加功能

面向对象 之 初识 是类之内置的一个装饰器, 的作用在于经由过程特定体式格局, 把一个按正常应当以方法情势调用的执行体, 转化为以属性情势来操控调用。 装饰器: 在不改变原函数的基础上, 动态给函数增加功能 很多装饰器的些许细节, 之前已然开展过讨论, 其本质是借助变量的…

作者头像 李华
网站建设 2026/9/8 16:23:44

前端转AI大模型开发:小白也能轻松入门的收藏必备学习路线!

本文为前端开发者提供了从前端逐步转向AI大模型应用开发的实用指南。作者结合自身近一年的经验&#xff0c;建议前端开发者不必死磕算法或研究训练&#xff0c;而是应优先进入“大模型应用开发”赛道。文章详细介绍了四个学习阶段&#xff1a;基础补齐&#xff08;Python编程&a…

作者头像 李华
网站建设 2026/9/8 16:23:03

Vibe Kanban:用看板管理AI编程助手的任务与上下文

过去一年&#xff0c;我绝大部分编码工作都是和AI编程助手结对完成的。代码生成、重构、补测试&#xff0c;模型干得很漂亮&#xff0c;但我的效率瓶颈却跑到了“会话”上&#xff1a;每次新开一个对话&#xff0c;都要把项目背景、模块入口、技术约束重新喂一遍&#xff1b;如…

作者头像 李华