Puppeteer+MCP双模态方案:让AI Agent精准操控浏览器的实战指南

想让AI Agent精准操控浏览器?Puppeteer+MCP的双模态方案来了
做AI Agent开发的兄弟们,肯定遇到过这个头疼问题:让LLM去操作网页,要么是纯靠DOM结构“盲操作”,遇到动态加载就翻车;要么是截图让模型“看图说话”,但像素级理解又慢又不准。
今天拆解一个狠货——基于MCP(Model Context Protocol)的Puppeteer服务器,它用结构化可访问性数据+视觉模式双管齐下,第一次让浏览器自动化和视觉理解在同一个MCP Server里跑通。
一、痛点:LLM操作浏览器的两难困境
先看传统方案的死穴:
- 纯DOM解析模式:把网页HTML扔给LLM,模型得从一堆标签里猜哪个是按钮、哪个是输入框。遇到React/Vue的动态渲染,DOM结构和肉眼看到的完全对不上,操作成功率直接掉到60%以下。
- 纯视觉模式:截图扔给多模态模型(比如GPT-4V),确实能“看懂”页面,但每次操作都要截图→识别→执行,一轮下来3-5秒,做个复杂表单填写得等半分钟,而且坐标定位容易漂移。
MCP World的方案:用Puppeteer抓取结构化可访问性数据(Accessibility Tree),同时保留视觉模式作为兜底。LLM拿到的是“这个页面有哪些可交互元素、各自是什么角色、当前状态是什么”的结构化JSON,操作精准度直接拉到95%以上。
二、技术架构:一个轻量MCP Server怎么搭
这个方案的核心是一个MCP Server,它暴露了浏览器自动化的能力给LLM调用。架构很清晰:
LLM (Claude/GPT)
↓ MCP协议调用
MCP Server (Node.js)
├── Puppeteer核心:控制Chrome实例
├── 可访问性数据提取:解析页面语义结构
└── 视觉模式:截图+坐标映射(可选)核心代码示例:Server初始化
// server.js - MCP Server核心逻辑
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import puppeteer from "puppeteer";
const server = new McpServer({
name: "puppeteer-browser",
version: "1.0.0",
});
// 全局浏览器实例
let browser = null;
let page = null;
// 初始化浏览器工具
server.tool(
"browser_launch",
"启动浏览器实例",
{
headless: z.boolean().default(true).describe("是否无头模式"),
url: z.string().optional().describe("初始URL"),
},
async ({ headless, url }) => {
browser = await puppeteer.launch({ headless });
page = await browser.newPage();
if (url) await page.goto(url, { waitUntil: "networkidle2" });
return {
content: [{ type: "text", text: `浏览器已启动,当前页面: ${page.url()}` }],
};
}
);关键能力:结构化可访问性数据提取
这是整个方案的灵魂。Puppeteer的 page.accessibility.snapshot() 能拿到页面的语义化树结构,不是原始DOM,而是“按钮”“链接”“输入框”这种人类可理解的元素描述:
// 获取页面结构化数据(核心工具)
server.tool(
"get_accessibility_snapshot",
"获取当前页面的结构化可访问性数据",
{},
async () => {
const snapshot = await page.accessibility.snapshot();
// 增强:补充可交互元素的额外信息
const interactiveElements = await page.evaluate(() => {
const elements = [];
document.querySelectorAll('button, a, input, select, textarea').forEach(el => {
elements.push({
role: el.tagName.toLowerCase(),
text: el.textContent?.trim() || el.placeholder || '',
disabled: el.disabled,
visible: el.offsetParent !== null,
});
});
return elements;
});
return {
content: [{
type: "text",
text: JSON.stringify({
accessibility_tree: snapshot,
interactive_elements: interactiveElements,
current_url: page.url(),
page_title: await page.title(),
}, null, 2)
}],
};
}
);LLM拿到的数据长这样:
{
"accessibility_tree": {
"role": "WebArea",
"name": "GitHub",
"children": [
{ "role": "link", "name": "Sign in" },
{ "role": "button", "name": "Search" },
{ "role": "textbox", "name": "Search GitHub" }
]
},
"interactive_elements": [
{ "role": "button", "text": "Sign in", "disabled": false, "visible": true }
]
}对比纯DOM的优势:LLM不用解析<div class="jsx-38291">这种天书,直接拿到“有个叫Sign in的按钮,可以点”,操作指令精准匹配。
视觉模式:复杂界面的兜底方案
遇到Canvas画的图表、验证码、或者可访问性数据缺失的页面,切视觉模式:

// 视觉模式:截图+坐标点击
server.tool(
"browser_screenshot_and_click",
"截图并点击指定坐标(视觉模式)",
{
x: z.number().describe("X坐标"),
y: z.number().describe("Y坐标"),
},
async ({ x, y }) => {
// 截图用于LLM理解当前状态
const screenshot = await page.screenshot({ encoding: "base64" });
// 执行点击
await page.mouse.click(x, y);
await page.waitForTimeout(500); // 等待页面响应
// 点击后再截一张,确认操作结果
const afterScreenshot = await page.screenshot({ encoding: "base64" });
return {
content: [
{ type: "image", data: screenshot, mimeType: "image/png" },
{ type: "text", text: `已点击坐标 (${x}, ${y})` },
{ type: "image", data: afterScreenshot, mimeType: "image/png" },
],
};
}
);三、实战场景:这套方案能干什么
场景1:自动化数据抓取(电商价格监控)
需求:每小时抓取某电商平台100个商品的价格,存入数据库。
传统方案:写CSS选择器,页面一改版就全挂,维护成本高。
MCP方案:LLM通过可访问性数据理解页面结构,自动定位价格元素:
// LLM的调用流程(伪代码)
// 1. 打开商品页
await call_tool("browser_navigate", { url: "https://example.com/product/123" });
// 2. 获取结构化数据
const data = await call_tool("get_accessibility_snapshot");
// LLM从data中识别出:{ role: "text", name: "¥299.00" } 是价格
// 3. 提取并存储
await call_tool("browser_evaluate", {
script: "document.querySelector('[data-price]').textContent"
});优势:页面改版后,只要价格元素的可访问性标签没变(比如还是叫“商品价格”),抓取逻辑就不用改。实测维护成本降低70%。
场景2:跨平台UI自动化测试
需求:同一个测试用例,要在Chrome、Firefox、Safari上跑。
MCP方案:MCP Server抽象了浏览器差异,LLM写的测试逻辑是语义化的:
"点击登录按钮" → MCP Server内部翻译成对应浏览器的点击指令
"验证页面显示'欢迎回来'" → 从可访问性数据中查找文本一份测试脚本,三个浏览器通用,不用针对每个浏览器写适配代码。
场景3:AI Agent自动填表(政务/金融场景)
需求:自动填写复杂的多步表单(比如公司注册、贷款申请)。
传统方案:每个字段都要写选择器,表单有20个字段就得写20个定位逻辑。
MCP方案:LLM拿到可访问性树,直接“看懂”表单结构:
{
"role": "form",
"children": [
{ "role": "textbox", "name": "公司名称", "required": true },
{ "role": "combobox", "name": "行业类型", "options": ["科技", "金融", "制造"] },
{ "role": "textbox", "name": "注册资本", "required": true }
]
}LLM根据业务数据自动匹配字段名,填写准确率接近100%。实测一个30字段的表单,从开发到跑通只要2小时,传统方式得2天。
四、部署步骤:5分钟跑起来
# 1. 克隆项目
git clone https://github.com/mcp-world/puppeteer-mcp-server.git
cd puppeteer-mcp-server
# 2. 安装依赖
npm install
# 3. 启动Server(stdio模式,供Claude Desktop等客户端调用)
node server.js
# 4. 在Claude Desktop配置文件中添加:
# ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"puppeteer": {
"command": "node",
"args": ["/path/to/server.js"]
}
}
}配置完重启Claude Desktop,你就能直接对它说“帮我打开GitHub,搜索MCP相关项目”,它会自动调用浏览器工具完成操作。
五、下一步行动
- 立即试玩:把上面的Server代码跑起来,用Claude Desktop测试“打开任意网页并提取页面标题”这个最简场景。
- 扩展能力:给Server加一个
browser_fill_form工具,支持批量填写表单字段,这是商业化价值最高的场景。 - 结合你的业务:想想你手头哪个重复性的网页操作可以自动化——数据录入、报表下载、信息监控——用这套方案搭一个专属Agent,下周就能上线。
本文基于MCP World开源项目,完整代码和更多示例见GitHub仓库。有问题欢迎在龙虾社区交流。