手把手教你部署Playwright MCP Server:让AI助手操作浏览器的完整指南

📅 2026/8/12 17:26:06
手把手教你部署Playwright MCP Server:让AI助手操作浏览器的完整指南
1. 项目概述为什么我们需要 Playwright MCP如果你最近在折腾大模型应用开发特别是想让 AI 助手比如 Claude、Cursor 等能像真人一样操作浏览器完成一些自动化任务那你大概率已经听说过 Playwright 和 MCP 这两个词了。把它们俩结合起来就是今天要聊的Playwright MCP。简单来说它就是一个“翻译官”把大模型能理解的指令翻译成 Playwright 这个浏览器自动化工具能执行的具体动作。我最初接触这个组合是因为一个很实际的需求团队想做一个能自动登录内部系统、抓取日报数据并生成摘要的 AI 助手。纯靠写死脚本每个系统一变就得改代码太僵化。而如果能让 AI 自己“看”着浏览器去操作灵活性和适应性会强得多。Playwright MCP 正是实现这个想法的桥梁。它基于Model Context Protocol协议这是一种新兴的、旨在标准化大模型与外部工具和数据源交互的协议。你可以把它想象成 USB 协议MCP 定义了“插头”和“接口”的标准让不同的大模型电脑能轻松连接并使用像 PlaywrightU盘这样的各种工具。所以这篇教程的目标很明确手把手带你完成 Playwright MCP Server 的深度部署与集成让你能亲手搭建一个环境体验如何用自然语言指挥 AI 去操作网页。整个过程会涉及本地开发环境配置、MCP 服务器运行、以及与不同 AI 客户端如 Claude Desktop、Cursor的对接。我会把我在部署和调试过程中踩过的坑、总结的技巧都摊开来讲确保你跟着做一遍就能跑通。2. 核心概念与工具选型解析在动手之前我们得先理清几个关键概念知道每个部件是干什么的以及为什么选它们。这能帮你后面排查问题时心里有张清晰的地图。2.1 Playwright不只是“另一个”自动化工具你可能用过 Selenium 或者 Puppeteer。Playwright 是微软开源的一个现代化浏览器自动化库它最大的优势在于跨浏览器和网络拦截能力。它原生支持 Chromium、Firefox 和 WebKitSafari 内核写一套脚本能在三个浏览器上跑。对于 MCP 场景来说这意味着你的 AI 助手能应对“请在 Chrome 中打开”或“在 Safari 里测试一下”这类指令。更重要的是它的browser_context概念。每个上下文context都有独立的 cookies、本地存储和缓存相互隔离。这太有用了想象一下AI 助手同时处理两个任务一个用公司账号登录后台另一个用测试账号浏览官网。用两个独立的browser_context就能完美隔离不会互相串号。Playwright 还提供了强大的等待策略和自动等待机制能有效解决页面元素加载时机不确定的问题这对于由 AI 控制、操作节奏不固定的自动化流程至关重要。2.2 MCP 协议大模型的“工具使用”标准MCP 全称 Model Context Protocol你可以把它理解为大模型领域的“驱动协议”。它的核心目标是解决一个问题如何让不同的 AI 模型Claude、GPT、本地模型等能够安全、统一地调用外部工具如搜索引擎、数据库、浏览器在没有 MCP 之前每个 AI 应用开发者都需要为特定的模型和特定的工具编写大量的适配代码像是给每个电器定制不同的插头。MCP 定义了一套标准的“插座”和“通信语言”。它主要包含两部分MCP 服务器封装了具体工具的能力。比如我们今天要部署的 Playwright MCP Server它内部实现了启动浏览器、点击、输入、截图等所有 Playwright 功能并通过 MCP 协议暴露成一系列标准的“工具Tools”和“资源Resources”。MCP 客户端集成在 AI 应用中的部分。比如 Claude Desktop、Cursor IDE 就内置了 MCP 客户端。客户端负责与一个或多个 MCP 服务器通信将用户的自然语言请求转换成对服务器工具的调用并把结果返回给 AI 模型。选择 MCP 而不是为每个项目写定制 API是因为它代表了未来的方向——解耦和标准化。一旦你的工具实现了 MCP 服务器它就能被任何支持 MCP 的 AI 客户端使用可移植性极强。2.3 生态与客户端选择Claude Desktop vs Cursor目前最主流、对 MCP 支持最完善的客户端有两个Claude DesktopAnthropic 官方的 Claude 桌面应用。它的 MCP 配置非常直观通过一个 JSON 配置文件管理。适合专注于与 Claude 模型交互、进行自动化测试、数据抓取等任务的场景。稳定性好文档清晰。Cursor IDE一个深度集成 AI 的代码编辑器。它的优势在于开发上下文。你可以在 Cursor 里直接让 AI 操作浏览器同时 AI 能看到你当前的代码文件。这对于前端调试“帮我看下这个按钮在最新构建里的样式”、结合页面状态生成代码“根据这个后台列表页给我写个对应的 React 组件”等场景有不可替代的优势。我建议你根据主要用途来选择。如果是通用自动化从 Claude Desktop 开始更简单。如果是开发相关Cursor 是神器。好消息是部署一次 Playwright MCP Server可以同时配置给这两个客户端使用互不冲突。3. 本地开发环境深度配置工欲善其事必先利其器。一个干净、规范的开发环境能避免无数诡异的问题。下面我会以 macOS/Linux 环境为主进行说明Windows 用户使用 WSL2 可以获得几乎一致的体验这也是官方推荐的方式。3.1 基础设施Node.js 与包管理器的抉择Playwright MCP Server 是用 TypeScript 写的所以 Node.js 是必需品。版本选择上我强烈推荐使用Node.js 18 LTS 或 20 LTS版本。一些旧的依赖可能在更新的 Node.js 21 版本上有兼容性问题。关于包管理器npm是随 Node.js 自带的但这里我更推荐pnpm或yarn。为什么因为 Playwright 本身会下载浏览器驱动Chromium, Firefox, WebKit这些二进制文件体积很大。pnpm 和 yarn 对磁盘空间的利用更高效使用硬链接或符号链接能为你节省不少硬盘空间。尤其是当你需要管理多个不同项目时优势明显。# 使用 corepackNode.js 16.9 自带启用 pnpm corepack enable pnpm # 或者全局安装 pnpm npm install -g pnpm3.2 项目初始化与依赖安装我们不直接从零编写 MCP 服务器而是基于 Anthropic 官方提供的modelcontextprotocol/sdk和社区优秀的模板或实现来构建。这里我推荐使用一个社区维护的、功能比较完整的 Playwright MCP Server 项目作为基础。# 1. 创建一个新的项目目录 mkdir playwright-mcp-server cd playwright-mcp-server # 2. 初始化项目并安装核心依赖 pnpm init -y pnpm add modelcontextprotocol/sdk playwright # 安装类型定义和开发依赖 pnpm add -D typescript types/node tsx关键一步Playwright 浏览器安装。很多新手会卡在这里。Playwright 库本身不包含浏览器需要单独安装。# 这个命令会下载 Chromium, Firefox, WebKit 到 ~/.cache/ms-playwright 目录 pnpm exec playwright install注意这个下载过程可能需要较长时间特别是 WebKit体积较大。确保网络通畅。如果下载失败可以尝试设置国内镜像或者使用playwright install chromium只安装最常用的 Chromium 以快速开始。3.3 配置 TypeScript 与开发脚本在项目根目录创建tsconfig.json这是 TypeScript 的编译配置。{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, lib: [ES2022], outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true, declaration: true, declarationMap: true }, include: [src/**/*], exclude: [node_modules, dist] }然后在package.json中添加一些实用的脚本{ scripts: { build: tsc, start: node dist/server.js, dev: tsx watch src/server.ts } }tsx是一个 TypeScript 执行器可以在开发时直接运行.ts文件无需手动编译非常适合快速迭代。4. Playwright MCP 服务器核心实现现在进入核心部分编写 MCP 服务器。我们将实现几个最关键的“工具”让 AI 能够调用。4.1 服务器骨架与工具定义首先在src目录下创建server.ts。我们从引入 SDK 和定义工具开始。import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListToolsRequestSchema, } from modelcontextprotocol/sdk/types.js; import { chromium, firefox, webkit, Browser, BrowserContext, Page } from playwright; // 创建 MCP 服务器实例 const server new Server( { name: playwright-mcp-server, version: 1.0.0, }, { capabilities: { tools: {}, // 声明我们提供工具 }, } ); // 用于管理浏览器实例和页面的全局状态 const browserSessions: Mapstring, { browser: Browser; contexts: Mapstring, BrowserContext } new Map(); const pages: Mapstring, Page new Map(); // 工具列表这是我们向 AI 客户端“广告”的能力 server.setRequestHandler(ListToolsRequestSchema, async () { return { tools: [ { name: launch_browser, description: Launch a new browser instance. Specify browser type: chromium, firefox, or webkit., inputSchema: { type: object, properties: { browserType: { type: string, enum: [chromium, firefox, webkit] }, headless: { type: boolean, default: true } }, required: [browserType] } }, { name: open_page, description: Open a new page (tab) in a browser context and navigate to a URL., inputSchema: { type: object, properties: { sessionId: { type: string, description: The browser session ID from launch_browser }, url: { type: string, format: uri } }, required: [sessionId, url] } }, { name: click_element, description: Click on an element on the page using a CSS selector., inputSchema: { type: object, properties: { pageId: { type: string, description: The page ID from open_page }, selector: { type: string } }, required: [pageId, selector] } }, { name: type_text, description: Type text into an input field identified by a CSS selector., inputSchema: { type: object, properties: { pageId: { type: string }, selector: { type: string }, text: { type: string } }, required: [pageId, selector, text] } }, { name: get_page_content, description: Get the current page HTML content or text content for AI to read., inputSchema: { type: object, properties: { pageId: { type: string }, contentType: { type: string, enum: [html, text], default: text } }, required: [pageId] } }, { name: take_screenshot, description: Take a screenshot of the current page or a specific element., inputSchema: { type: object, properties: { pageId: { type: string }, selector: { type: string, description: Optional CSS selector for element screenshot }, fullPage: { type: boolean, default: false } }, required: [pageId] } } ] }; });这段代码定义了服务器的元信息和六个核心工具。每个工具都有清晰的名称、描述和输入参数定义。AI 客户端如 Claude会读取这个列表从而知道它能命令这个服务器做什么。4.2 工具功能的具体实现接下来我们需要为每个工具名实现具体的处理逻辑。这是最核心的部分我们以launch_browser和open_page为例。server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; try { switch (name) { case launch_browser: { const { browserType, headless true } args as any; let browser: Browser; // 根据参数启动不同的浏览器 switch (browserType) { case chromium: browser await chromium.launch({ headless }); break; case firefox: browser await firefox.launch({ headless }); break; case webkit: browser await webkit.launch({ headless }); break; default: throw new Error(Unsupported browser type: ${browserType}); } const sessionId session_${Date.now()}; browserSessions.set(sessionId, { browser, contexts: new Map() }); return { content: [{ type: text, text: Browser launched successfully. Session ID: ${sessionId}. Remember this ID for subsequent operations. }] }; } case open_page: { const { sessionId, url } args as any; const session browserSessions.get(sessionId); if (!session) { throw new Error(Browser session not found: ${sessionId}); } // 为这个新页面创建一个独立的上下文隔离环境 const context await session.browser.newContext(); const contextId context_${Date.now()}; session.contexts.set(contextId, context); const page await context.newPage(); // 设置合理的超时和视图大小这对稳定性很重要 await page.setDefaultTimeout(30000); await page.setViewportSize({ width: 1280, height: 720 }); // 导航到目标URL const response await page.goto(url, { waitUntil: domcontentloaded }); const pageId page_${Date.now()}; pages.set(pageId, page); const status response?.status(); return { content: [{ type: text, text: Page opened. URL: ${url}. Status: ${status}. Page ID: ${pageId}. }] }; } case click_element: { const { pageId, selector } args as any; const page pages.get(pageId); if (!page) { throw new Error(Page not found: ${pageId}); } // Playwright 会自动等待元素可见、可操作 await page.click(selector); return { content: [{ type: text, text: Successfully clicked element: ${selector} }] }; } case type_text: { const { pageId, selector, text } args as any; const page pages.get(pageId); if (!page) { throw new Error(Page not found: ${pageId}); } // 先点击输入框聚焦再输入模拟真人操作 await page.click(selector); await page.fill(selector, ); await page.type(selector, text); return { content: [{ type: text, text: Typed ${text} into ${selector} }] }; } // ... 其他工具的实现get_page_content, take_screenshot逻辑类似 // 它们会调用 page.content(), page.textContent(), page.screenshot() 等方法 default: throw new Error(Unknown tool: ${name}); } } catch (error: any) { // 错误处理至关重要需要将详细的错误信息返回给AI return { content: [{ type: text, text: Error executing tool ${name}: ${error.message}\n${error.stack || } }], isError: true }; } });4.3 服务器启动与 STDIO 传输MCP 服务器通常通过标准输入输出stdio与客户端通信。这是最后一步// 启动服务器监听标准输入输出 async function runServer() { const transport new StdioServerTransport(); await server.connect(transport); console.error(Playwright MCP Server is running on stdio...); } runServer().catch(console.error);将以上所有代码片段组合起来就构成了一个完整的、功能可用的 Playwright MCP 服务器。使用pnpm dev命令可以启动开发模式但此时它只是在等待输入。我们需要一个客户端来连接它。5. 客户端配置与集成实战服务器跑起来了现在要让 AI 能用上它。我们分别配置 Claude Desktop 和 Cursor。5.1 配置 Claude Desktop 连接 MCP 服务器Claude Desktop 的配置非常直观通过一个 JSON 文件完成。找到配置文件位置macOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.jsonLinux:~/.config/Claude/claude_desktop_config.json编辑配置文件如果文件不存在就创建它。我们需要添加mcpServers配置项。{ mcpServers: { playwright: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/playwright-mcp-server/dist/server.js ], env: { DEBUG: mcp:* } } } }关键提示command也可以是pnpm、tsx等。但为了生产环境稳定强烈建议先用pnpm build编译 TypeScript 为 JavaScript然后指向编译后的dist/server.js文件。直接运行tsx在开发时方便但作为常驻服务可能不够稳定。args中的路径必须是绝对路径。使用相对路径会导致 Claude Desktop 找不到你的服务器。env里可以设置环境变量DEBUGmcp:*有助于在终端查看详细的通信日志调试时非常有用。重启 Claude Desktop保存配置文件后完全退出并重新启动 Claude Desktop。验证连接打开 Claude Desktop新建一个对话。如果你在配置中启用了DEBUG环境变量在启动 Claude Desktop 的终端如果你是从终端启动的里应该能看到 MCP 连接的日志。更直接的验证方式是在对话中输入“你能使用浏览器工具吗”或者“你有什么工具”。Claude 应该会回复它已连接到一个 Playwright 服务器并列出可用的工具如launch_browser,open_page等。5.2 配置 Cursor IDE 连接 MCP 服务器Cursor 的配置原理类似但位置和格式稍有不同。打开 Cursor 设置Cmd ,(Mac) 或Ctrl ,(Windows/Linux)。搜索 MCP在设置搜索框中输入 “MCP”。编辑 MCP 配置你会看到一个Cursor MCP Servers的 JSON 配置区域。点击编辑添加如下配置{ mcpServers: { playwright: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/playwright-mcp-server/dist/server.js ] } } }配置项的含义与 Claude Desktop 完全一致。同样确保路径是绝对的。重启 Cursor保存设置后可能需要重启 Cursor 以使配置生效。在 Cursor 中使用重启后在 Cursor 的聊天框中CmdK你就可以直接让 AI 操作浏览器了。例如你可以说“请用 Playwright 打开 GitHub 官网并搜索 ‘playwright mcp’。” Cursor 的 AI通常是 Claude 3.5 Sonnet会理解指令调用相应的工具并返回结果。5.3 首次完整流程测试让我们进行一次端到端测试确保一切正常。编译服务器在你的项目终端里运行pnpm build。确保配置文件正确检查 Claude Desktop 或 Cursor 的配置文件路径无误。启动客户端启动 Claude Desktop 或 Cursor。发起对话在 AI 对话框中输入一个多步指令例如“请启动一个无头模式的 Chromium 浏览器打开百度首页在搜索框里输入‘今日天气’然后点击搜索按钮。最后把搜索结果页面的文本内容摘要给我。”理想情况下AI 会这样工作识别出需要调用launch_browser工具参数为{“browserType”: “chromium”, “headless”: true}。收到服务器返回的sessionId。接着调用open_page参数为{“sessionId”: “xxx”, “url”: “https://www.baidu.com”}收到pageId。调用type_text在搜索框选择器它需要知道是#kw中输入“今日天气”。调用click_element点击选择器为#su的按钮。等待页面加载后调用get_page_content获取文本。最后AI 将获取到的文本内容进行总结并输出给你。如果过程中任何一步失败AI 会返回从 MCP 服务器收到的错误信息这是你调试的主要依据。6. 高级功能扩展与最佳实践基础功能跑通后我们可以让这个服务器变得更强大、更健壮。6.1 实现页面内容智能解析与摘要get_page_content工具返回的是整个页面的 HTML 或文本对于 AI 来说可能信息过载。我们可以实现一个更高级的工具scrape_and_summarize。// 在工具列表中新增 { name: scrape_and_summarize, description: Extract main content from a page, clean excessive ads and navigation, and provide a concise summary. Perfect for news articles or blog posts., inputSchema: { type: object, properties: { pageId: { type: string }, maxLength: { type: number, default: 1000 } }, required: [pageId] } } // 在工具实现 switch 语句中新增 case case scrape_and_summarize: { const { pageId, maxLength 1000 } args as any; const page pages.get(pageId); if (!page) throw new Error(Page not found: ${pageId}); // 一种简单的内容提取策略获取 body 文本或通过常见内容选择器获取 let content ; // 尝试获取文章主体内容常见选择器 const articleSelectors [article, main, .post-content, .article-content]; for (const selector of articleSelectors) { const element await page.$(selector); if (element) { content await element.textContent() || ; break; } } // 如果没找到特定内容区域则获取整个 body 的文本 if (!content) { content await page.textContent(body) || ; } // 简单的文本清理去除过多空白行和短行可能是广告 const cleanedContent content.split(\n) .map(line line.trim()) .filter(line line.length 20) // 过滤掉过短的行 .join(\n) .substring(0, maxLength); return { content: [{ type: text, // 这里返回清理后的文本。实际上更优的做法是调用另一个AI模型进行总结。 // 但为了简化我们先返回清理后的内容让主AI自己去总结。 text: Cleaned page content (first ${maxLength} chars):\n${cleanedContent} }] }; }这个工具让 AI 获取的信息质量更高指令可以变成“打开某某新闻链接然后 scrape_and_summarize 一下告诉我文章主要讲了什么。”6.2 会话管理与资源清理我们的简单实现用全局 Map 存储页面和会话这存在内存泄漏风险。我们需要实现会话管理和清理工具。// 新增工具关闭页面、关闭浏览器 { name: close_page, description: Close a specific page and free its resources., inputSchema: { type: object, properties: { pageId: { type: string } }, required: [pageId] } }, { name: close_browser, description: Close a browser session and all its pages., inputSchema: { type: object, properties: { sessionId: { type: string } }, required: [sessionId] } } // 实现 close_page case close_page: { const { pageId } args as any; const page pages.get(pageId); if (page) { await page.close(); pages.delete(pageId); // 还需要找到这个 page 所属的 context 并清理略需要维护更复杂的关系映射 return { content: [{ type: text, text: Page ${pageId} closed. }] }; } // ... 错误处理 } // 实现 close_browser case close_browser: { const { sessionId } args as any; const session browserSessions.get(sessionId); if (session) { // 关闭所有上下文 for (const context of session.contexts.values()) { await context.close(); } // 关闭浏览器 await session.browser.close(); browserSessions.delete(sessionId); // 清理所有属于这个 session 的 pages需要额外维护映射关系 return { content: [{ type: text, text: Browser session ${sessionId} closed. }] }; } // ... 错误处理 }一个负责任的做法是在 AI 客户端如 Claude结束一个涉及浏览器操作的对话时主动调用close_browser来释放资源。你也可以在服务器端设置一个超时机制自动清理长时间不活动的会话。6.3 错误处理与稳健性增强网络环境复杂页面元素可能加载失败。我们需要增强工具的容错能力。// 以 click_element 为例增强其稳健性 case click_element: { const { pageId, selector } args as any; const page pages.get(pageId); if (!page) throw new Error(Page not found: ${pageId}); try { // 增加显式等待确保元素存在 await page.waitForSelector(selector, { state: visible, timeout: 10000 }); await page.click(selector); return { content: [{ type: text, text: Successfully clicked element: ${selector} }] }; } catch (clickError: any) { // 尝试备用方案通过 JavaScript 直接点击 const clicked await page.evaluate((sel) { const el document.querySelector(sel); if (el) { el.click(); return true; } return false; }, selector); if (clicked) { return { content: [{ type: text, text: Element clicked via JavaScript fallback: ${selector} }] }; } else { throw new Error(Failed to click selector ${selector}: ${clickError.message}); } } }这种“主路径失败尝试备用路径”的策略能显著提高自动化脚本在复杂真实网页环境下的成功率。7. 生产环境部署与性能考量如果你想在服务器或 Docker 中长期运行这个 MCP 服务需要考虑以下几点。7.1 Docker 化部署创建Dockerfile可以保证环境一致性。# 使用带有 Playwright 依赖的官方 Node 镜像避免自己安装系统库 FROM mcr.microsoft.com/playwright:v1.48.0-noble WORKDIR /app # 复制 package.json 和 pnpm-lock.yaml COPY package.json pnpm-lock.yaml* ./ # 安装 pnpm 和依赖 RUN corepack enable pnpm pnpm install --frozen-lockfile # 安装 Playwright 浏览器使用镜像自带的此步可省略或改为验证 # RUN npx playwright install --with-deps # 复制源代码 COPY . . # 编译 TypeScript RUN pnpm build # 运行服务 CMD [node, dist/server.js]然后构建并运行docker build -t playwright-mcp-server . docker run -it --rm playwright-mcp-server在客户端配置中command就需要改为dockerargs改为[“run”, “-i”, “playwright-mcp-server”]。注意 Docker 运行需要-i保持 stdin 打开以便进行 MCP 通信。7.2 安全加固限制可访问的域名在open_page工具中加入一个白名单检查。const ALLOWED_DOMAINS [example.com, github.com, localhost]; const urlObj new URL(url); if (!ALLOWED_DOMAINS.includes(urlObj.hostname)) { throw new Error(Access to domain ${urlObj.hostname} is not allowed.); }超时控制为每个工具调用设置全局超时防止恶意或错误的指令导致资源长期占用。认证高级MCP 协议支持服务器认证。你可以在服务器启动时要求客户端提供令牌并在客户端配置中附带该令牌。这对于公开服务是必须的。7.3 性能监控与日志使用winston或pino等日志库替代console.error将日志结构化并输出到文件。记录每个工具的调用时间、参数可脱敏、成功与否。这有助于后期性能分析和问题排查。你还可以集成简单的健康检查端点虽然 MCP over stdio 不直接提供 HTTP 服务但可以额外开一个端口。8. 常见问题与排查技巧实录在实际部署和使用中你肯定会遇到各种问题。下面是我踩过的一些坑和解决方法。8.1 连接失败客户端找不到服务器症状Claude Desktop 或 Cursor 启动时无错误但 AI 表示没有可用工具或配置后客户端无法启动。排查路径问题99%的问题出在这里。确保配置文件中的路径是绝对路径。在终端中使用pwd命令获取项目绝对路径并完整地复制到配置中。命令问题如果你直接运行tsx src/server.ts确保tsx已在全局安装或在项目node_modules/.bin目录下且该目录在系统的 PATH 环境变量中。最稳妥的方式还是使用编译后的node dist/server.js。权限问题确保启动客户端的用户有权限执行你指定的命令和脚本。在 macOS/Linux 上可以给脚本添加执行权限chmod x dist/server.js如果它被包装成脚本。查看客户端日志Claude Desktop 在启动时如果有 MCP 错误会在其 GUI 的“设置”-“调试”区域或启动它的终端中输出错误信息。Cursor 的错误信息通常出现在其内置终端或开发者工具控制台Help - Toggle Developer Tools。8.2 工具调用失败超时或未知错误症状AI 列出了工具但调用时失败返回超时或模糊的错误。排查启用调试在服务器启动命令的环境变量中添加DEBUGmcp:*。这会在终端打印出所有 MCP 协议级别的通信信息你能看到客户端发送的请求和服务器返回的响应精准定位问题。检查 Playwright 浏览器确保 Playwright 浏览器已正确安装。运行npx playwright install --dry-run检查。如果缺失重新运行pnpm exec playwright install。无头模式问题在服务器端如果你在 Docker 或无 GUI 的服务器上运行必须将headless设置为true这是默认值。在 macOS/Linux 桌面环境可以设为false看到浏览器窗口弹出这对调试很有帮助。页面加载超时网页本身加载慢或需要复杂 JS 执行。在open_page的page.goto中可以尝试不同的waitUntil策略如‘networkidle’等待网络空闲或‘load’等待 load 事件但会增加超时风险。适当增加page.setDefaultTimeout的值。8.3 AI 不理解指令或调用错误工具症状AI 没有按你期望的方式调用工具或者完全误解了指令。排查与技巧工具描述是关键AI 完全依赖你在ListToolsRequestSchema中提供的工具description字段来决定何时调用哪个工具。把你的description写得尽可能清晰、具体包含典型用例和参数说明。例如open_page的描述可以加上“Use this after launch_browser to navigate to a specific web address.”提供上下文在对话中明确告诉 AI 当前的会话状态。例如在 AI 执行完launch_browser后你可以手动补充一句“好的现在浏览器会话 ID 是session_123456。” 然后下一个指令就可以是“用这个会话打开百度。” AI 会更容易理解需要将sessionId参数传递给open_page。分步指导对于复杂任务不要一次性给 AI 一个长指令。拆分成几步每一步让 AI 执行一个明确的工具调用并基于上一步的结果进行下一步。这更符合当前 AI 助手的工作模式。8.4 资源占用与内存泄漏症状运行一段时间后服务器进程内存占用越来越高或者浏览器进程没有关闭。解决主动清理如前所述实现并主动调用close_page和close_browser工具。在对话结束时养成让 AI 或你自己手动清理的习惯。超时清理在服务器端实现一个后台定时任务定期检查browserSessions和pagesMap将超过一定时间如30分钟未使用的会话和页面强制关闭。限制并发在全局状态中维护一个计数器限制同时打开的浏览器实例或页面数量防止资源耗尽。部署和调试 Playwright MCP 是一个典型的“开发运维”过程需要耐心和细致的观察。从最简单的“打开网页”开始逐步增加工具复杂性并利用好调试日志是快速上手的捷径。这个组合一旦跑通将为你的 AI 应用打开一扇新的大门让大模型真正拥有“手”和“眼睛”去操作和感知真实的数字世界。