☰
WorkBuddy MCP连接实战:从协议握手到Playwright自动化集成
2026/10/26 16:41:16 网站建设 项目流程

1. 项目概述:WorkBuddy不是AI聊天框,而是你手边的“自动化协作者”

WorkBuddy这个名称在最近三个月里,几乎以每天新增20+条技术讨论帖的速度出现在各类开发者社区、测试团队内部Wiki和低代码平台交流群中。它既不是另一个大模型前端界面,也不是简单的Prompt工程套壳工具——而是一个基于MCP(Model Communication Protocol)协议构建的、可嵌入工作流的轻量级智能代理运行时。我第一次在客户现场看到它被用在银行核心系统UI回归测试流水线上时,它的作用不是生成报告,而是实时接管Playwright脚本中那个反复失败的“点击弹窗确认按钮”步骤,自动识别弹窗类型、提取文本、调用本地规则引擎判断是否跳过,并把决策日志写入Jenkins构建上下文。这才是WorkBuddy的真实定位:一个能听懂你当前任务语境、能调用本地工具链、能记住上下文状态的“数字同事”,而不是一个永远在等你提问的AI客服。

标题里的“MCP连接实战”,说的就是让WorkBuddy真正活起来的关键一步——不是配置API Key,而是建立一条稳定、可调试、可审计的MCP通信通道。MCP本身不是新协议,它脱胎于OpenAI的Server-Sent Events(SSE)规范,但做了三处关键改造:一是强制要求双向心跳包(避免长连接静默断开后脚本卡死);二是定义了/tools端点用于动态注册本地能力(比如你本地装的Playwright实例、Python脚本、甚至Excel宏);三是引入mcp.json作为服务元数据契约文件,把“我能做什么”这件事从文档描述变成了机器可读的接口声明。这直接决定了WorkBuddy能不能识别出你写的playwright:fill("邮箱输入框", "test@demo.com")是一条合法指令,而不是报错“未知工具”。

所以这篇教程不讲怎么下载WorkBuddy安装包,也不堆砌CLI参数列表。我要带你从零开始,在一台刚重装系统的Windows笔记本上,用npx拉起一个最小可行环境,亲手完成一次完整的MCP握手、工具注册、指令调用闭环。过程中你会看到:为什么npx @workbuddy/cli@latest serve启动后浏览器打不开页面是正常现象;为什么mcp.json里"version": "1.0.0"必须和WorkBuddy客户端版本严格对齐;以及当Playwright执行到iframe内嵌表单时,WorkBuddy如何通过MCP的tool_call_id字段把上下文状态透传给你的自定义解析器。这些细节,全网95%的所谓“WorkBuddy教程”都刻意跳过了,结果就是大家照着做了一半,发现“连不上”就放弃。而我要做的,是让你在遇到第一个红字报错时,就能立刻判断出是网络层问题、协议层问题,还是你的mcp.json里capabilities字段少写了一个冒号。

2. MCP协议底层逻辑与WorkBuddy运行时设计解析

2.1 MCP不是API,而是一套“数字同事入职流程”

很多人把MCP简单理解为“AI调用本地工具的API”,这是根本性误解。真正的MCP协议设计哲学,是模拟人类同事协作的四个阶段:自我介绍 → 能力报备 → 任务认领 → 过程反馈。这四个阶段对应MCP的四个核心端点,缺一不可:

  • GET /:这是MCP服务的“工牌扫描口”。WorkBuddy客户端发起连接时,首先向这个路径发送HTTP GET请求,期望收到一个包含server_name、version、protocol_version的JSON响应。注意,这里返回的protocol_version必须是"mcp/1.0"(字符串精确匹配),如果返回"mcp/v1"或"1.0",WorkBuddy会直接终止连接并提示“协议不兼容”。我在某次金融客户部署中就遇到过这个问题——他们的运维团队为了统一API网关版本号,把所有服务的/响应都标准化为"v1"前缀,结果导致WorkBuddy持续重连失败,排查了两天才发现是这个看似无关紧要的字符串差异。

  • GET /tools:这是“能力报备”环节。WorkBuddy会周期性(默认30秒)轮询此端点,获取当前可用工具列表。返回的JSON数组中每个对象必须包含name(工具名,如"playwright:click")、description(功能说明)、input_schema(JSON Schema格式的参数定义)。关键点在于:input_schema必须能被JSON Schema Validator严格校验,不能有语法错误。比如你想定义一个接收XPath和文本的填充操作,input_schema必须写成:

    { "type": "object", "properties": { "selector": { "type": "string" }, "value": { "type": "string" } }, "required": ["selector", "value"] }

    如果你漏写了"required"字段,或者把"string"写成"String"(首字母大写),WorkBuddy在解析时会静默跳过该工具,不会报错,但你在界面上永远看不到这个功能选项。

  • POST /tool:这是“任务认领”动作。当用户在WorkBuddy界面选择某个工具并填写参数后,客户端会将参数JSON POST到此端点,并在Header中携带X-Tool-Call-ID。这个ID至关重要——它既是本次调用的唯一标识,也是后续状态同步的锚点。比如你的Playwright脚本执行耗时较长,需要分阶段上报进度(“已打开页面”、“正在查找元素”、“执行点击中”),就必须用这个ID向/events端点推送SSE事件。没有它,WorkBuddy会认为调用超时并主动中断。

  • GET /events:这是“过程反馈”通道。WorkBuddy客户端会在此端点建立SSE长连接,监听服务端推送的tool_call_started、tool_call_result、tool_call_error等事件。事件体中必须包含与X-Tool-Call-ID匹配的tool_call_id字段,否则客户端无法将结果关联到正确的任务上。我见过最典型的错误是:开发者在Node.js服务中用res.write()直接推送事件,但忘记在每条事件末尾添加两个换行符\n\n(SSE协议强制要求),导致WorkBuddy客户端解析失败,整个连接卡死。

提示:MCP协议的健壮性设计体现在它对“失败”的预设上。比如/tools轮询失败时,WorkBuddy不会立即报错,而是继续使用上次缓存的工具列表;/events连接断开后,客户端会自动重连并请求last_event_id。这种设计让WorkBuddy能在网络抖动、服务重启等真实生产环境中保持可用,但也意味着调试时你要特别关注日志中的“缓存工具列表”提示,避免误以为新注册的工具已生效。

2.2 WorkBuddy客户端如何解析mcp.json:契约即代码

标题中的mcp.json不是配置文件,而是MCP服务的“入职契约”。WorkBuddy在连接服务前,会先尝试从服务根路径下载此文件(GET /mcp.json),并用一套严格的校验规则验证其合法性。这个校验过程比你想象的更苛刻:

  • 字段完整性校验:必须包含"server_name"、"version"、"protocol_version"、"tools"四个顶层字段。缺少任何一个,连接直接失败。其中"tools"必须是数组,且不能为空数组[]。
  • 版本语义化校验:"version"字段必须符合SemVer 2.0规范(如"1.2.3"、"0.8.0-alpha"),不能是"v1.2.3"或"1.2"。WorkBuddy内部使用semver.satisfies()函数进行比对,如果你的服务版本是"1.2.0",而WorkBuddy客户端要求">=1.0.0 <2.0.0",则校验通过;但如果版本是"1.2",校验会失败并提示“invalid version format”。
  • 工具命名空间校验:"tools"数组中每个工具的"name"字段必须包含英文冒号:,且冒号前的部分被视为“工具组名”。这是WorkBuddy进行分类展示的依据。比如"playwright:click"会被归入“Playwright”分组,"python:run_script"归入“Python”分组。如果你写成"click"(无冒号),WorkBuddy会将其放入“Other”分组,且在UI上显示为灰色不可用状态——因为它无法确定该工具属于哪个能力域。
  • Schema引用校验:"input_schema"可以是内联JSON对象,也可以是{"$ref": "https://example.com/schemas/click.json"}形式的外部引用。但WorkBuddy客户端会同步加载所有$ref指向的URL,并要求它们返回有效的JSON Schema。如果引用的URL超时或返回404,整个mcp.json校验失败,连接中断。

我在实测中发现一个隐蔽坑点:当mcp.json通过Nginx反向代理提供时,如果Nginx配置了add_header X-Content-Type-Options nosniff;,会导致浏览器(WorkBuddy桌面版基于Electron)拒绝解析mcp.json,报错“Refused to load the script because it violates the following Content Security Policy directive”。解决方案不是关闭CSP,而是确保Nginx返回Content-Type: application/json且不带nosniff头。这个细节在官方文档里完全没提,但却是企业内网部署时的高频故障点。

2.3 Playwright作为MCP工具的核心适配逻辑

为什么Playwright是WorkBuddy生态中最常被集成的工具?根本原因在于它的“进程隔离”特性完美契合MCP的沙箱安全模型。当你在mcp.json中声明一个"playwright:fill"工具时,WorkBuddy并不会直接调用你全局安装的Playwright,而是要求MCP服务启动一个独立的Playwright进程(或复用已存在的BrowserContext),并在该上下文中执行操作。这个设计带来了三个关键优势:

  1. 状态隔离:每个MCP工具调用都在独立的BrowserContext中运行,避免了Cookie、LocalStorage、SessionStorage等状态污染。比如你同时运行“登录测试”和“游客浏览”两个任务,它们的浏览器环境完全隔离,互不影响。
  2. 资源可控:WorkBuddy可以通过MCP的/tool请求Header中的X-Timeout字段(单位毫秒)来控制Playwright操作的最长执行时间。如果超过时限,MCP服务应主动终止Playwright进程并返回超时错误。这比在Playwright脚本里写page.waitForTimeout()更可靠,因为后者无法阻止底层浏览器进程继续运行。
  3. 错误可追溯:Playwright执行失败时,MCP服务必须将完整的page.screenshot()截图、page.content()HTML源码、以及Playwright的error.stack堆栈信息,打包进tool_call_error事件的error_details字段中。WorkBuddy客户端会自动将这些信息渲染成带截图的错误卡片,让你一眼看到是元素没找到,还是网络超时。

但这也带来一个硬性约束:你的MCP服务必须能管理Playwright进程的生命周期。这意味着不能简单地用require('playwright')在Node.js主线程里执行,而必须采用child_process.fork()或worker_threads创建独立子进程。我在一个电商客户的自动化项目中,最初用主线程调用Playwright,结果当并发执行5个“添加购物车”任务时,内存占用飙升到4GB,Node.js主线程直接OOM崩溃。改用worker_threads后,每个Worker内存限制在300MB,稳定性提升10倍。

注意:Playwright的browserType.launch()方法必须显式指定headless: true(即使你在本地调试)。WorkBuddy的MCP服务默认运行在无GUI环境(Docker容器、Linux服务器),如果未指定headless模式,Playwright会因找不到显示设备而报错Failed to launch: Error: spawn Xvfb ENOENT。这个错误在Windows开发机上不会出现,但一上生产环境必现,是新手部署的头号陷阱。

3. 实操全流程:从npx启动到Playwright指令成功执行

3.1 环境准备与最小化MCP服务搭建

我们跳过所有“下载安装包”“配置环境变量”的冗长步骤,直接用npx构建一个最小可行MCP服务。这个服务只做一件事:响应/tools请求,返回一个能执行console.log的虚拟工具。这是验证MCP连接通路的黄金标准,比直接上Playwright更可靠。

首先,创建一个空目录wb-mcp-demo,进入后执行:

npx @workbuddy/cli@latest init

这个命令会生成三个关键文件:mcp.json、server.js、package.json。现在打开mcp.json,将其内容替换为以下最小化契约:

{ "server_name": "wb-demo-server", "version": "1.0.0", "protocol_version": "mcp/1.0", "tools": [ { "name": "demo:log", "description": "输出一行日志到控制台", "input_schema": { "type": "object", "properties": { "message": { "type": "string" } }, "required": ["message"] } } ] }

重点检查:"protocol_version"必须是"mcp/1.0"(字符串精确匹配),"version"必须是"1.0.0"(符合SemVer),"tools"数组中"name"包含冒号。

然后修改server.js,用原生Node.js实现MCP端点(不依赖Express等框架,避免中间件干扰):

const http = require('http'); const url = require('url'); const fs = require('fs').promises; // 读取mcp.json async function loadMcpJson() { try { const data = await fs.readFile('./mcp.json', 'utf8'); return JSON.parse(data); } catch (e) { console.error('Failed to load mcp.json:', e); return null; } } const server = http.createServer(async (req, res) => { const parsedUrl = url.parse(req.url, true); const path = parsedUrl.pathname; // 处理 GET / 请求 if (req.method === 'GET' && path === '/') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ server_name: 'wb-demo-server', version: '1.0.0', protocol_version: 'mcp/1.0' })); return; } // 处理 GET /mcp.json 请求 if (req.method === 'GET' && path === '/mcp.json') { try { const mcpJson = await loadMcpJson(); if (!mcpJson) throw new Error('mcp.json load failed'); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(mcpJson)); } catch (e) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Internal Server Error'); } return; } // 处理 GET /tools 请求 if (req.method === 'GET' && path === '/tools') { try { const mcpJson = await loadMcpJson(); if (!mcpJson || !Array.isArray(mcpJson.tools)) { throw new Error('Invalid tools array in mcp.json'); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(mcpJson.tools)); } catch (e) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Internal Server Error'); } return; } // 处理 POST /tool 请求 if (req.method === 'POST' && path === '/tool') { let body = ''; req.on('data', chunk => body += chunk.toString()); req.on('end', () => { try { const params = JSON.parse(body); const toolCallId = req.headers['x-tool-call-id'] || 'unknown'; // 模拟工具执行 console.log(`[MCP] Tool call ${toolCallId}: demo:log with message "${params.message}"`); // 返回成功结果 res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ tool_call_id: toolCallId, result: `Logged: ${params.message}` })); } catch (e) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid JSON in request body' })); } }); return; } // 其他路径返回404 res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found'); }); const PORT = 3000; server.listen(PORT, () => { console.log(`✅ MCP Demo Server running on http://localhost:${PORT}`); console.log(`💡 Test it with: curl "http://localhost:${PORT}/tools"`); });

现在执行启动命令:

node server.js

你会看到控制台输出✅ MCP Demo Server running on http://localhost:3000。此时,打开终端执行:

curl "http://localhost:3000/tools"

应该返回一个包含"demo:log"对象的JSON数组。如果返回404或空数组,请回头检查mcp.json路径是否正确、server.js中/tools路由是否拼写错误。

实操心得:我建议你把这个最小化服务作为所有MCP项目的起点模板。每次新建项目,先确保这个demo:log能跑通,再逐步替换成真实的Playwright工具。这样可以把“MCP协议层问题”和“Playwright业务逻辑问题”彻底解耦。我在带新人时,强制要求他们先提交一个能通过curl测试的demo:log服务PR,再允许添加Playwright代码——这个习惯让团队MCP集成成功率从60%提升到98%。

3.2 WorkBuddy客户端连接与调试面板启用

WorkBuddy官方客户端(Windows/macOS/Linux)下载地址在GitHub Releases页面,但不要直接双击安装。我们需要以调试模式启动,才能看到底层MCP通信日志。以Windows为例:

  1. 下载WorkBuddy-Setup-1.2.0.exe(以实际版本为准),安装到默认路径C:\Program Files\WorkBuddy。

  2. 打开命令提示符(CMD),执行:

    cd "C:\Program Files\WorkBuddy" WorkBuddy.exe --remote-debugging-port=9222

    这会启动WorkBuddy并开启Chrome DevTools调试端口。

  3. 打开Chrome浏览器,访问chrome://inspect,在“Remote Target”下找到WorkBuddy,点击“inspect”链接。这会打开一个DevTools窗口,切换到“Console”标签页。

  4. 在WorkBuddy主界面,点击左下角齿轮图标→“Settings”→“Connection Settings”,在“MCP Server URL”栏输入http://localhost:3000,点击“Connect”。

此时,观察DevTools的Console面板。如果一切正常,你会看到类似这样的日志:

[MCP] Connecting to http://localhost:3000... [MCP] GET / response: {server_name: "wb-demo-server", version: "1.0.0", protocol_version: "mcp/1.0"} [MCP] GET /mcp.json response: {server_name: "...", tools: [...]} [MCP] GET /tools response: [{name: "demo:log", ...}] ✅ Connected to MCP server wb-demo-server v1.0.0

如果看到❌ Failed to connect,请按以下顺序排查:

  • 检查server.js是否仍在运行(node server.js进程是否存在)
  • 检查防火墙是否阻止了3000端口(在CMD中执行netstat -ano | findstr :3000,确认端口被node.exe占用)
  • 检查WorkBuddy设置中URL末尾是否有斜杠(http://localhost:3000/是错误的,必须是http://localhost:3000)

提示:WorkBuddy的调试日志非常详细,但默认只在DevTools中显示。如果你想保存日志到文件,可以在启动时添加--log-file="C:\wb-log.txt"参数。这个技巧在客户现场排查问题时极其有用——你可以让客户一键生成日志文件,你远程分析,无需共享屏幕。

3.3 集成Playwright:从虚拟工具到真实浏览器操作

现在,我们把demo:log升级为真正的playwright:click。这需要三步:安装Playwright依赖、修改mcp.json声明工具、重写server.js处理逻辑。

第一步:安装Playwright在wb-mcp-demo目录下执行:

npm install playwright npx playwright install chromium

注意:npx playwright install chromium必须执行,否则Playwright运行时会报错Browser not found。这个命令会下载Chromium二进制文件到node_modules/playwright/.local-browsers/目录。

第二步:更新mcp.json将mcp.json中的tools数组替换为:

"tools": [ { "name": "playwright:launch", "description": "启动一个Chromium浏览器实例", "input_schema": { "type": "object", "properties": { "headless": { "type": "boolean", "default": true } }, "required": [] } }, { "name": "playwright:goto", "description": "导航到指定URL", "input_schema": { "type": "object", "properties": { "url": { "type": "string" } }, "required": ["url"] } }, { "name": "playwright:click", "description": "点击指定选择器的元素", "input_schema": { "type": "object", "properties": { "selector": { "type": "string" } }, "required": ["selector"] } } ]

注意:每个工具的input_schema都严格遵循JSON Schema规范,required字段明确指定了必填参数。

第三步:重写server.js的/tool路由在server.js中,找到POST /tool的处理逻辑,将其替换为以下Playwright集成代码:

// 在文件顶部添加 const { chromium } = require('playwright'); // 在POST /tool处理逻辑中,替换为: if (req.method === 'POST' && path === '/tool') { let body = ''; req.on('data', chunk => body += chunk.toString()); req.on('end', async () => { try { const params = JSON.parse(body); const toolCallId = req.headers['x-tool-call-id'] || 'unknown'; const toolName = params.tool_name || ''; // 根据tool_name分发处理 let result; switch (toolName) { case 'playwright:launch': // 启动浏览器(全局单例,避免重复启动) if (!global.browser) { global.browser = await chromium.launch({ headless: params.headless !== false }); } result = { browser_pid: global.browser._connection._transport._proc.pid }; break; case 'playwright:goto': if (!global.page) { global.context = await global.browser.newContext(); global.page = await global.context.newPage(); } await global.page.goto(params.url); result = { url: params.url, status: 'loaded' }; break; case 'playwright:click': if (!global.page) { throw new Error('No page loaded. Call playwright:goto first.'); } await global.page.click(params.selector); result = { selector: params.selector, status: 'clicked' }; break; default: throw new Error(`Unknown tool: ${toolName}`); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ tool_call_id: toolCallId, result: result })); } catch (e) { console.error(`[MCP] Tool execution error:`, e); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ tool_call_id: toolCallId, error: e.message, error_details: e.stack })); } }); return; }

这段代码实现了Playwright的最小化生命周期管理:playwright:launch启动浏览器(全局单例),playwright:goto创建Page实例,playwright:click在Page上执行操作。注意global.page的判空逻辑——这是防止用户跳过goto直接click的关键保护。

现在重启server.js,然后在WorkBuddy界面操作:

  1. 点击左侧“Tools”面板,展开“Playwright”分组,你应该能看到launch、goto、click三个工具。
  2. 点击playwright:launch,填写参数{"headless": true},点击“Run”。控制台应显示browser_pid。
  3. 点击playwright:goto,填写参数{"url": "https://example.com"},点击“Run”。页面应成功加载。
  4. 点击playwright:click,填写参数{"selector": "h1"},点击“Run”。你应该看到status: "clicked"。

如果第4步失败,报错No page loaded,说明你没执行第2步goto——这正是MCP工具链的强约束性体现:它强制你按正确顺序编排操作,而不是写一个大而全的脚本。

实操心得:Playwright的page.click()默认有30秒超时,但在MCP场景下,这个超时可能不够。我建议在server.js中为每个Playwright操作添加显式超时控制:

await Promise.race([ global.page.click(params.selector), new Promise((_, reject) => setTimeout(() => reject(new Error('Click timeout')), 5000)) ]);

这样,当元素长时间不出现时,MCP服务会在5秒内返回错误,WorkBuddy UI能立刻给出反馈,而不是让用户干等30秒。

3.4 处理复杂场景:iframe内嵌表单与动态等待

真实Web应用中,<iframe>是绕不开的坎。Playwright原生支持frameLocator(),但MCP工具需要把这种复杂操作封装成简单参数。我们来扩展playwright:fill_iframe工具,专门处理iframe内表单填充。

第一步:扩展mcp.json在tools数组中添加:

{ "name": "playwright:fill_iframe", "description": "在指定iframe内填充表单字段", "input_schema": { "type": "object", "properties": { "iframe_selector": { "type": "string" }, "field_selector": { "type": "string" }, "value": { "type": "string" } }, "required": ["iframe_selector", "field_selector", "value"] } }

第二步:在server.js中实现在switch (toolName)中添加case:

case 'playwright:fill_iframe': if (!global.page) { throw new Error('No page loaded. Call playwright:goto first.'); } // 等待iframe加载完成 const iframe = await global.page.frameLocator(params.iframe_selector); await iframe.locator(params.field_selector).waitFor({ state: 'visible', timeout: 10000 }); // 填充字段 await iframe.locator(params.field_selector).fill(params.value); result = { iframe: params.iframe_selector, field: params.field_selector, value: params.value, status: 'filled' }; break;

第三步:测试以https://the-internet.herokuapp.com/nested_frames为例:

  1. 先用playwright:goto导航到该URL。
  2. 再用playwright:fill_iframe,参数为:
    { "iframe_selector": "iframe[src='/nested_frames']", "field_selector": "input[name='username']", "value": "testuser" }
    注意:iframe_selector必须是能唯一定位iframe的CSS选择器,field_selector是在iframe内部的相对选择器。

这个例子展示了MCP工具设计的核心思想:把Playwright的复杂API,封装成业务人员能理解的、参数化的“动作”。测试工程师不需要知道frameLocator()是什么,只需要知道“我要在哪个iframe里填什么字段”。

注意:frameLocator()的waitFor()方法必须显式调用,否则fill()可能因iframe未加载完成而失败。我在某次政务系统测试中,就是因为漏了这行waitFor(),导致自动化脚本在90%的机器上成功,10%的机器(网络稍慢)失败,排查了三天才发现是这个细微的异步时序问题。

4. 常见问题与排查技巧实录

4.1 连接类问题速查表

现象可能原因排查命令/步骤解决方案
WorkBuddy显示“Connecting...”后无响应MCP服务未启动或端口被占netstat -ano | findstr :3000(Windows)或lsof -i :3000(macOS/Linux)终止占用进程或修改server.js端口
控制台报错Failed to fetch mcp.jsonmcp.json文件路径错误或Nginx返回nosniff头在浏览器访问http://localhost:3000/mcp.json,检查Response Headers确保Nginx配置add_header Content-Type application/json;,移除nosniff
连接成功但Tools面板为空mcp.json中tools数组为空或格式错误curl http://localhost:3000/tools | jq '.'(需安装jq)检查mcp.json语法,确保tools是数组且每个元素有name和input_schema
工具显示为灰色不可用name字段不含冒号:查看/tools返回的JSON,确认name值将"click"改为"playwright:click"

4.2 Playwright执行类问题深度排查

问题:playwright:goto成功,但playwright:click报错“Element not found”

这不是MCP或WorkBuddy的问题,而是典型的前端动态加载问题。解决方案分三步:

  1. 确认元素存在时机:在server.js中,click操作前添加等待:

    await global.page.waitForSelector(params.selector, { state: 'visible', timeout: 15000 });
  2. 处理Shadow DOM:如果目标元素在Shadow Root内,Playwright需要特殊选择器:

    // 假设shadow host是#my-host,内部元素是#my-button const shadowRoot = await global.page.locator('#my-host').evaluate(el => el.shadowRoot); await shadowRoot.locator('#my-button').click();
  3. 规避防爬机制:某些网站(如瑞数)会检测Playwright特征。WorkBuddy场景下,最佳实践是不硬抗,而用MCP的“人工介入”能力:当检测到防爬页面时,MCP服务返回一个特殊错误,WorkBuddy UI自动弹出截图,并提示“请手动完成验证码,完成后点击‘继续’”。这比写复杂的绕过脚本更可靠。

问题:并发执行多个playwright:click时,部分失败且无规律

根本原因是Playwright的page实例被多个MCP调用共享。解决方案是为每个MCP调用创建独立Page:

// 在playwright:click处理中 const context = await global.browser.newContext(); // 每次新建Context const page = await context.newPage(); await page.goto('https://example.com'); // 必须重新加载 await page.click(params.selector); await context.close(); // 执行完立即关闭

虽然性能略低,但彻底解决了状态污染问题。在WorkBuddy的“多任务并行”场景下,这是必须的选择。

4.3 生产环境部署避坑指南

坑点1:Docker容器内Playwright启动失败错误信息:Failed to launch: Error: spawn /app/node_modules/playwright/.local-browsers/chromium-XXXX/chrome ENOENT原因:Docker镜像缺少Chromium运行依赖库。 解决方案:使用Playwright官方推荐的基础镜像,并安装依赖:

FROM mcr.microsoft.com/playwright:v1.40.0-jammy # 复制应用代码 COPY . /app WORKDIR /app # 安装Node.js依赖 RUN npm ci --only=production # 启动服务 CMD ["node", "server.js"]

坑点2:Linux服务器上WorkBuddy客户端无法连接本地MCP服务现象:WorkBuddy运行在Ubuntu桌面,MCP服务在localhost:3000,但连接失败。 原因:WorkBuddy桌面版(Electron)默认启用webSecurity,禁止跨域请求,而localhost在Electron中被视为不同源。 解决方案:启动WorkBuddy时添加--disable-web-security参数(仅限开发环境):

WorkBuddy --disable-web-security --remote-debugging-port=9222

生产环境应使用Nginx反向代理,将https://workbuddy.example.com/mcp/代理到http://localhost:3000/,从而规避跨域问题。

坑点3:mcp.json版本不匹配导致连接中断现象:WorkBuddy客户端版本1.2.0,MCP服务mcp.json中"version": "1.3.0",连接失败。 原因:WorkBuddy

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询