三步构建基于Playwright MCP的跨浏览器自动化架构

发布时间:2026/7/8 17:32:28
三步构建基于Playwright MCP的跨浏览器自动化架构 1. 项目概述为什么我们需要Playwright MCP如果你和我一样长期奋战在Web自动化测试或爬虫开发的一线肯定对“跨浏览器兼容性”这六个字又爱又恨。爱的是它确保了我们的应用或脚本能在Chrome、Firefox、Edge甚至Safari上表现一致恨的是为了实现它我们往往需要维护多套脚本、处理各种诡异的API差异以及应对那永远配不对的浏览器驱动版本。传统的解决方案无论是Selenium WebDriver还是早期的Puppeteer都或多或少地让我们在“配置地狱”和“环境玄学”中挣扎。直到我遇到了Playwright微软出品的这个现代浏览器自动化库以其强大的API、出色的执行速度和原生的跨浏览器支持Chromium、Firefox、WebKit让我眼前一亮。但很快新的问题又来了如何在一个架构中优雅、统一地管理和调度这些不同内核的浏览器实例如何将复杂的浏览器操作抽象成更通用的指令方便与上层应用比如AI智能体、CI/CD流水线集成这就是MCPModel Context Protocol登场的时候了。简单来说Playwright MCP不是一个全新的框架而是一个基于Playwright核心能力构建的、遵循MCP协议的服务层。它的核心价值在于将Playwright对浏览器的精细控制能力封装成一套标准化的、与具体浏览器实现解耦的“指令集”。你可以把它想象成一个“浏览器遥控器”的通用说明书无论你手里拿的是Chrome、Firefox还是Edge的遥控器你都可以用同一套指令MCP协议来操作它们。这个项目标题“3步实战基于Playwright MCP构建跨浏览器自动化架构”精准地抓住了痛点简单、快速、架构化。它承诺的不是零散的脚本技巧而是一套可复用的工程架构。通过三个核心步骤我们就能搭建起一个健壮的、支持并行跨浏览器测试或数据采集的自动化系统。这对于需要频繁进行多浏览器验证的前端团队、开发数据聚合服务的工程师或是构建需要与网页交互的AI Agent开发者来说无疑是一个效率利器。接下来我将结合自己踩过的坑和实战经验带你一步步拆解这个架构的搭建过程、核心原理以及那些官方文档里不会写的“生存技巧”。2. 架构核心Playwright与MCP如何珠联璧合在撸起袖子敲代码之前我们必须先理解这套架构的基石。如果把整个系统比作一支交响乐团那么Playwright就是技艺高超的乐手能精准操控每一种乐器/浏览器而MCP协议就是那位指挥家负责解读总谱业务逻辑并将统一的指令分发给对应的乐手。2.1 Playwright现代浏览器自动化的“瑞士军刀”Playwright的强大在于它从设计之初就为现代Web和自动化场景做了深度优化。与Selenium相比它不需要独立的Driver进程通信效率更高它提供了自动等待、网络拦截、移动端模拟等开箱即用的高级特性。但对我们构建架构而言最关键的是它的两个核心设计BrowserContext浏览器上下文这是一个独立的“隐身会话”拥有独立的cookie、缓存和权限设置。我们可以为每个测试任务或数据采集任务创建一个独立的Context实现完美的环境隔离避免任务间相互污染。这在并行架构中至关重要。多浏览器类型支持通过统一的API如chromium.launch(),firefox.launch()来启动不同内核的浏览器底层差异被极大屏蔽。这为我们实现“跨浏览器”的统一调度提供了可能。然而原生的Playwright API仍然是面向开发者的、相对“底层”的。如果我们想构建一个服务让一个AI模型或者一个简单的HTTP客户端也能发起“点击某个按钮”、“提取某段文本”这样的操作就需要一层抽象。2.2 MCP协议上下文交互的“通用语言”MCPModel Context Protocol是一种新兴的协议旨在为大语言模型LLM或其他客户端提供一种标准化方式来发现、调用工具Tools和访问资源Resources。你可以把它理解为一套定义良好的“工具调用说明书”。在Playwright MCP项目中MCP Server将Playwright的能力如打开页面、点击元素、截图、执行脚本包装成了一个个标准的Tool。例如browser_navigateTool对应page.goto(url)browser_clickTool对应page.click(selector)browser_get_contentTool对应page.content()或更精细的选择器提取这样一来任何兼容MCP协议的客户端如Claude Code、Cursor或你自己写的脚本都不需要了解Playwright的具体语法只需要按照MCP的格式发送JSON-RPC请求就能驱动浏览器完成操作。这实现了控制逻辑与浏览器操作逻辑的彻底解耦。2.3 架构分层设计解析一个典型的基于Playwright MCP的自动化架构通常包含以下四层客户端层 (Client)这是指令的发起者。可以是一个AI智能体、一个CI/CD脚本、一个图形化操作面板或者一个简单的Python/Node.js脚本。它只需要知道MCP Server的地址并按照MCP协议格式组装请求。MCP Server层这是架构的核心枢纽。它启动一个服务通常是HTTP或Stdio Server对外暴露MCP接口。内部维护着一个或多个Playwright浏览器实例或BrowserContext。当收到客户端的Tool调用请求时它将其“翻译”成对应的Playwright API调用并执行在指定的浏览器实例上最后将结果封装成MCP格式返回。Playwright驱动层由MCP Server管理。负责实际启动和生命周期管理Chromium、Firefox、WebKit等浏览器进程。这一层处理所有与浏览器二进制文件、驱动版本相关的复杂问题。一个好的MCP实现会内置浏览器自动下载和版本管理。目标浏览器层实际运行并渲染页面的浏览器进程。它们被Playwright以无头Headless或有头Headed模式启动完全受控。这种分层架构的好处是显而易见的可扩展性可以轻松增加新的Tool、可维护性各层职责清晰、多客户端支持任何懂MCP的客户端都能接入。接下来我们就进入实战看看如何用三步搭建起这个架构。3. 三步实战从零搭建你的跨浏览器自动化架构理论说得再多不如动手一试。我将会以一个真实的场景为例我们需要构建一个服务能够接受指令并行地在Chrome和Firefox上打开百度首页搜索同一个关键词并截取搜索结果页的截图进行比对。我们将分三步完成。3.1 第一步环境准备与Playwright MCP Server部署这一步的目标是搭建起MCP Server让它能同时操控两种浏览器。1. 基础环境检查与搭建确保你的系统已安装Node.js (18.0.0) 和 npm。这是Playwright生态的根基。我强烈建议使用nvm来管理Node.js版本避免全局污染。# 检查Node.js和npm版本 node -v # 应输出 v18.x 或更高 npm -v # 应输出 8.x 或更高2. 初始化项目并安装核心依赖我们创建一个新的项目目录并初始化。mkdir playwright-mcp-arch cd playwright-mcp-arch npm init -y接下来安装最关键的依赖playwright/test和modelcontextprotocol/sdk。Playwright Test包包含了核心的浏览器自动化库和测试运行器我们也会用到它的工具而MCP SDK则帮助我们快速构建MCP Server。npm install playwright/test modelcontextprotocol/sdk3. 部署Playwright MCP参考实现网络上已经有了一些Playwright MCP的开源实现例如playwright-mcp。我们可以直接克隆并参考其结构或者基于它进行二次开发。这里我们以学习和构建的角度手动创建一个简化的核心。首先安装浏览器二进制文件。Playwright提供了便捷的命令这一步可能会下载几百MB的数据请保持网络通畅。npx playwright install chromium firefox4. 创建并启动我们的MCP Server我们创建一个server.js文件作为MCP Server的入口。这个Server将提供两个核心Toollaunch_browser和navigate_page。// server.js const { Server } require(modelcontextprotocol/sdk/server/index.js); const { StdioServerTransport } require(modelcontextprotocol/sdk/server/stdio.js); const { playwright } require(playwright/test); class PlaywrightMCPServer { constructor() { this.server new Server( { name: playwright-mcp-server, version: 0.1.0, }, { capabilities: { tools: {}, }, } ); this.browserInstances new Map(); // 用来存储不同浏览器类型的实例 this.contexts new Map(); // 存储浏览器上下文 this.pages new Map(); // 存储页面 this._setupToolHandlers(); } _setupToolHandlers() { // Tool 1: 启动指定类型的浏览器 this.server.setRequestHandler(tools/call, async (request) { if (request.params.name launch_browser) { const { browserType chromium, headless true } request.params.arguments; let browser; try { if (browserType chromium) { browser await playwright.chromium.launch({ headless }); } else if (browserType firefox) { browser await playwright.firefox.launch({ headless }); } else { throw new Error(Unsupported browser type: ${browserType}); } const browserId browser_${Date.now()}; this.browserInstances.set(browserId, browser); // 创建一个独立的上下文 const context await browser.newContext(); const page await context.newPage(); const contextId context_${browserId}; const pageId page_${contextId}; this.contexts.set(contextId, context); this.pages.set(pageId, page); return { content: [ { type: text, text: Successfully launched ${browserType} in ${headless ? headless : headed} mode., }, ], toolCallId: request.params.toolCallId, isError: false, meta: { browserId, contextId, pageId }, // 返回资源ID供后续操作使用 }; } catch (error) { return { content: [{ type: text, text: Failed to launch browser: ${error.message} }], toolCallId: request.params.toolCallId, isError: true, }; } } // Tool 2: 让指定页面导航到某个URL if (request.params.name navigate_page) { const { pageId, url } request.params.arguments; const page this.pages.get(pageId); if (!page) { return { content: [{ type: text, text: Page with ID ${pageId} not found. }], toolCallId: request.params.toolCallId, isError: true, }; } try { const response await page.goto(url, { waitUntil: networkidle }); return { content: [ { type: text, text: Navigated to ${url}. Status: ${response.status()}, }, ], toolCallId: request.params.toolCallId, isError: false, }; } catch (error) { return { content: [{ type: text, text: Navigation failed: ${error.message} }], toolCallId: request.params.toolCallId, isError: true, }; } } // ... 可以继续添加更多Tool如 click, screenshot, get_content 等 }); } async run() { const transport new StdioServerTransport(); await this.server.connect(transport); console.error(Playwright MCP Server running on stdio...); } } const mcpServer new PlaywrightMCPServer(); mcpServer.run().catch(console.error);这个Server通过Stdio标准输入输出与客户端通信这是MCP的一种常见传输方式易于集成。它定义了两个工具并维护了几个Map来管理浏览器、上下文和页面的生命周期。5. 配置运行脚本在package.json中添加启动脚本{ scripts: { start:mcp: node server.js } }现在一个最简单的Playwright MCP Server就准备好了。虽然功能简陋但它已经具备了架构的核心雏形接收标准化指令操作多类型浏览器。实操心得一浏览器管理策略在生产环境中直接为每个请求启动新浏览器是不可取的这会消耗巨大资源。更优的策略是使用浏览器池Browser Pool。我们可以预先启动固定数量的不同浏览器实例放入池中。当收到launch_browser请求时实际上是从池中分配一个空闲实例并为其创建一个全新的BrowserContext和Page。任务完成后关闭Page和Context但浏览器实例归还到池中等待下次使用。这能极大提升响应速度和系统稳定性。3.2 第二步开发MCP客户端与并行任务调度Server跑起来了我们需要一个客户端来指挥它。同时我们要实现“并行在Chrome和Firefox上执行任务”这个核心场景。1. 创建并行调度客户端我们创建一个parallel_client.js文件。这个客户端将同时向MCP Server发起两个浏览器的启动和导航请求。为了模拟真实的MCP客户端我们使用modelcontextprotocol/sdk的客户端部分。// parallel_client.js const { Client } require(modelcontextprotocol/sdk/client/index.js); const { StdioClientTransport } require(modelcontextprotocol/sdk/client/stdio.js); const { spawn } require(child_process); const path require(path); async function runParallelTask() { // 启动MCP Server子进程 const serverProcess spawn(node, [path.join(__dirname, server.js)], { stdio: [pipe, pipe, inherit], // 将server的stdout重定向到pipe以便客户端读取 }); // 创建MCP客户端连接到Server进程的stdio const transport new StdioClientTransport({ command: node, args: [path.join(__dirname, server.js)], }); const client new Client( { name: parallel-test-client, version: 0.1.0, }, { capabilities: {}, } ); await client.connect(transport); console.log(Client connected. Launching browsers in parallel...); const tasks [ { browserType: chromium, taskName: Chrome Task }, { browserType: firefox, taskName: Firefox Task }, ]; const results await Promise.allSettled( tasks.map(async ({ browserType, taskName }) { try { // 调用 launch_browser Tool const launchResult await client.request(tools/call, { name: launch_browser, arguments: { browserType, headless: true }, }); if (launchResult.isError) { throw new Error(Launch failed: ${launchResult.content[0].text}); } const { browserId, contextId, pageId } launchResult.meta; console.log([${taskName}] Browser launched. Page ID: ${pageId}); // 调用 navigate_page Tool const navResult await client.request(tools/call, { name: navigate_page, arguments: { pageId, url: https://www.baidu.com }, }); if (navResult.isError) { throw new Error(Navigation failed: ${navResult.content[0].text}); } console.log([${taskName}] ${navResult.content[0].text}); // 这里可以继续调用更多Tool比如截图、输入搜索词等 // 例如await client.request(tools/call, { name: screenshot, arguments: { pageId, path: ./${browserType}_baidu.png } }); return { taskName, success: true, pageId }; } catch (error) { console.error([${taskName}] Error:, error.message); return { taskName, success: false, error: error.message }; } }) ); console.log(\n--- Parallel Task Results ---); results.forEach((result) { if (result.status fulfilled) { console.log(${result.value.taskName}: ${result.value.success ? SUCCESS : FAILED}); } else { console.log(Task failed: ${result.reason}); } }); // 所有任务完成后断开连接并关闭Server进程 await client.close(); serverProcess.kill(); console.log(Client disconnected.); } runParallelTask().catch(console.error);这个客户端演示了如何并发地控制多个浏览器。Promise.allSettled确保所有任务无论成功失败都执行完毕后再汇总结果这对于稳定性要求高的自动化流程很重要。2. 扩展更多浏览器操作Tool一个实用的架构需要更丰富的操作能力。我们需要在Server端继续完善Tool集。在server.js的_setupToolHandlers方法中我们可以添加screenshot: 截图工具type_text: 输入文本工具click_element: 点击元素工具get_element_content: 获取元素内容工具以截图工具为例// 在 server.js 的 _setupToolHandlers 方法中添加 if (request.params.name screenshot) { const { pageId, path screenshot_${Date.now()}.png } request.params.arguments; const page this.pages.get(pageId); if (!page) { /* 错误处理 */ } try { await page.screenshot({ path, fullPage: true }); return { content: [{ type: text, text: Screenshot saved to ${path} }], toolCallId: request.params.toolCallId, isError: false, }; } catch (error) { /* 错误处理 */ } }3. 运行并行测试在package.json中添加客户端运行脚本{ scripts: { start:mcp: node server.js, run:parallel: node parallel_client.js } }然后在终端运行npm run run:parallel你将看到控制台输出显示Chrome和Firefox两个任务被并行启动并依次导航到了百度首页。至此一个支持并行跨浏览器操作的自动化架构骨架已经搭建完成。实操心得二资源管理与清理上面的示例为了简洁没有处理浏览器资源的关闭。在实际架构中资源泄漏是致命问题。必须在客户端任务结束时或Server端检测到连接断开时有序地关闭Page、Context并最终关闭Browser。可以在Server端监听断开事件或在客户端显式调用一个close_browserTool。更健壮的做法是引入超时机制长时间无活动的浏览器实例自动回收。3.3 第三步架构优化、错误处理与生产级配置一个能用于实际项目的架构必须考虑健壮性、可观测性和可配置性。第三步我们来为这个骨架注入“灵魂”。1. 配置化管理将浏览器类型、并发数、超时时间、截图路径等可变参数抽取到配置文件如config.js或config.yaml中。// config.js module.exports { browsers: [chromium, firefox], // 支持的浏览器类型 defaultHeadless: true, viewport: { width: 1920, height: 1080 }, parallelLimit: 2, // 最大并行任务数避免资源耗尽 navigationTimeout: 30000, // 30秒 actionTimeout: 10000, // 10秒 screenshotDir: ./screenshots, mcpServer: { transport: stdio, // 或 http httpPort: 8080, // 如果使用HTTP传输 }, };在Server启动时读取配置应用到Playwright的启动选项和全局超时设置中。2. 增强错误处理与重试机制网络不稳定、元素加载慢、页面弹窗都可能导致自动化失败。我们需要一个强大的错误处理层。结构化错误返回在MCP Server的Tool handler中不仅返回简单的错误文本更应返回结构化的错误码、错误类型和可能的修复建议。自动重试对于网络超时TimeoutError或元素未找到ElementNotFound等临时性错误可以实现自动重试逻辑。例如在客户端调用Tool时包裹一个重试装饰器。async function callToolWithRetry(client, toolName, args, maxRetries 3) { let lastError; for (let i 0; i maxRetries; i) { try { const result await client.request(tools/call, { name: toolName, arguments: args }); if (result.isError) { throw new Error(Tool error: ${result.content[0].text}); } return result; } catch (error) { lastError error; console.warn(Attempt ${i 1} failed for ${toolName}:, error.message); if (i maxRetries - 1) { await new Promise(resolve setTimeout(resolve, 1000 * Math.pow(2, i))); // 指数退避 } } } throw lastError; // 重试全部失败后抛出最终错误 }上下文恢复如果某个页面崩溃Page Crashed最佳实践不是整个任务失败而是尝试从最近的检查点恢复。例如在关键步骤后保存页面状态如URL、Cookies崩溃后重新启动浏览器并恢复到该状态。3. 日志与监控没有日志的系统就像盲人摸象。我们需要记录关键事件浏览器启动/关闭、页面导航、Tool调用参数和结果、错误发生。结构化日志使用winston或pino等日志库输出JSON格式的日志便于后续用ELK等工具分析。性能指标记录每个Tool调用的耗时、每个页面的加载时间。这有助于发现性能瓶颈和异常。集成监控可以将关键指标如活跃浏览器实例数、任务队列长度、错误率推送到Prometheus再通过Grafana展示。4. 容器化部署Docker为了确保环境一致性尤其是在CI/CD流水线中将整个Playwright MCP架构Docker化是终极方案。这能解决“在我机器上好好的”这一经典难题。# Dockerfile FROM mcr.microsoft.com/playwright:v1.40.0-focal WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY . . # 安装项目所需的Playwright浏览器使用内置的playwright二进制 RUN npx playwright install chromium firefox --with-deps # 暴露MCP HTTP Server端口如果使用HTTP传输 # EXPOSE 8080 CMD [node, server.js]在Docker Compose中可以定义多个服务比如一个MCP Server服务一个负责调度任务的Worker服务和一个存储日志/报告的存储服务。5. 安全加固输入验证对所有从客户端传入的Tool参数如URL、CSS选择器进行严格的验证和清理防止注入攻击。资源限制限制单个客户端能启动的最大浏览器实例数、单个页面的最大运行时间防止资源耗尽攻击。认证与授权如果MCP Server以HTTP服务暴露在外网必须实现API密钥、JWT Token等认证机制。经过以上三步的构建和优化我们得到的已经不再是一个简单的脚本而是一个具备高可用性、可观测性和可维护性的生产级跨浏览器自动化架构。它能够稳定、高效地处理复杂的多浏览器并行任务。4. 常见问题与排查技巧实录在实际搭建和运行过程中你几乎一定会遇到下面这些问题。我把它们和我的解决方案整理出来希望能帮你节省大量排查时间。4.1 环境与依赖问题问题1npx playwright install下载极慢或失败。原因Playwright需要从Google、Mozilla等官方源下载浏览器二进制文件国内网络环境可能不稳定。解决使用镜像源设置环境变量。对于Chromium和Firefox可以尝试使用国内镜像。# Linux/macOS export PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright npx playwright install # Windows (PowerShell) $env:PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright npx playwright install手动下载如果镜像也不行可以去Playwright的GitHub Releases页面找到对应版本的浏览器包手动下载并放置到~/.cache/ms-playwright目录下对应的文件夹中。Docker基础镜像直接使用官方mcr.microsoft.com/playwright镜像它已经包含了所有浏览器是最省事的方法。问题2在Linux服务器尤其是CentOS/RHEL老版本上启动浏览器失败报错关于libc、libatk等依赖缺失。原因Playwright的浏览器需要一些系统图形库的支持即使是无头模式。解决安装缺失的依赖。对于基于Debian/Ubuntu的系统sudo apt-get update sudo apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2对于CentOS/RHELsudo yum install -y atk at-spi2-atk cups-libs libdrm libXcomposite libXdamage libXrandr mesa-libgbm alsa-lib注意如果系统版本太老如CentOS 7 glibc 2.17可能无法安装Playwright最新版所需的依赖。这时要么升级系统要么寻找旧版本的Playwright要么放弃直接部署改用Docker。4.2 Playwright MCP Server运行问题问题3MCP客户端连接Server后调用Tool无响应或超时。排查步骤检查传输协议确保客户端和Server使用的是同一种传输方式Stdio或HTTP。我们的例子用了Stdio这是进程间通信。如果Server以独立进程启动客户端需要以子进程方式启动它或通过网络连接。检查Tool名称和参数用console.log在Server端打印接收到的请求确认request.params.name和request.params.arguments与客户端发送的完全一致。JSON的键名必须匹配。检查异步处理确保Server端的Tool handler函数是async的并且正确使用了await处理Playwright的异步API。一个未处理的Promise拒绝可能导致整个请求挂起。增加超时在客户端设置请求超时避免无限等待。问题4并行任务时系统资源CPU/内存飙升甚至崩溃。原因每个浏览器实例都是重量级进程。并行启动过多实例会压垮系统。解决实施资源池如前所述使用浏览器池限制全局活跃实例数。控制并发度在客户端使用p-queue这类库来控制并发任务数量确保不超过config.parallelLimit。使用轻量级Context如果任务间不需要完全隔离可以考虑在同一个Browser实例下创建多个轻量的BrowserContext而不是启动多个Browser。这能节省大量资源。监控与告警集成系统监控当内存或CPU使用率超过阈值时停止接收新任务或报警。4.3 浏览器自动化操作问题问题5页面元素定位失败报错TimeoutError: Waiting for selector “...”。原因这是自动化测试中最常见的问题。可能是选择器写错了、元素还没加载出来、元素在iframe里、或者页面发生了跳转。排查与解决优先使用Playwright推荐的定位器如page.getByRole(),page.getByText(),page.getByTestId()。这些比脆弱的CSS选择器更稳定。增加等待策略page.click(selector, { timeout: 10000 })可以增加单次操作的超时。更佳实践是使用page.waitForSelector(selector, { state: visible })在操作前显式等待。截图辅助调试在操作失败时自动截图保存到日志中。这是定位问题的黄金手段。可以在Server的Tool handler中添加全局错误捕获一旦出错就调用page.screenshot()。检查Frame如果元素在iframe内需要先定位到frameconst frame page.frame({ name: iframe-name }); await frame.click(selector);。问题6在CI/CD如GitHub Actions环境中运行失败。原因CI环境通常是轻量级容器缺少必要的系统依赖或显示服务器即使是无头模式。解决使用官方Docker镜像这是最推荐的方式能最大程度复现本地成功环境。在CI脚本中安装依赖如果必须在CI裸机上运行参考问题2在CI的setup步骤中安装系统依赖。设置环境变量在某些CI环境中需要设置DISPLAY变量或使用xvfb虚拟帧缓冲区来模拟显示。# GitHub Actions 示例步骤 - name: Install Playwright and Browsers run: | npx playwright install --with-deps chromium - name: Run Tests run: | xvfb-run --auto-servernum -- npm run run:parallel4.4 架构设计进阶问题问题7如何实现动态Tool注册我不想每次加功能都改Server代码。思路这是MCP协议的优势所在。你可以设计一个插件系统。将每个Tool的实现写在一个独立的文件中Server启动时扫描某个目录如tools/动态加载并注册这些Tool到MCP Server。这样扩展功能只需要新增文件无需修改核心Server逻辑。问题8如何将这套架构与我的AI Agent如基于Claude、GPT集成方案这正是MCP协议设计的初衷。你的AI Agent作为MCP客户端可以通过Stdio或HTTP连接到你的Playwright MCP Server。当用户对Agent说“帮我去百度搜索Playwright的最新新闻并总结”Agent可以将这个自然语言指令通过自身的逻辑或调用一个LLM分解成一系列MCP Tool调用序列launch_browser(chromium)navigate_page(to baidu.com)type_text(into search box, text: “Playwright latest news”)click_element(search button)get_element_content(of search results)...(后续处理内容) Agent拿到网页内容后再调用LLM进行总结。这样你就赋予了AI Agent“操作浏览器”的能力。构建这样一个架构就像搭积木从核心的Playwright和MCP协议开始逐步添加并行调度、错误处理、资源管理、监控等模块。过程中遇到的每一个坑都会让你对浏览器自动化、服务编排和系统稳定性的理解更深一层。当你看到自己搭建的服务能够稳定、可靠地驱动多个浏览器完成复杂任务时那种成就感绝对是值得的。