平时做大模型应用开发的人,应该都有个共同的痛点——想让AI替我操作网页,光是把工具定义清楚就得耗掉半天:要写MCP server,要给每个操作写JSON Schema,要一遍遍调试参数传得对不对。前阵子我被这种事折磨得不轻,索性做了个小工具,可以录制任意网页操作,然后一键转成能被大模型直接调用的MCP Server。这个工具的核心价值就一句话:把“录屏”变成“MCP工具”。
简单说,你不用再手写工具描述、不用再为某个按钮写定位器,只要在浏览器里点一点、填一填,就能得到一个标准MCP Server。想让你自己的大模型客户端操作某个网站、处理某个固定流程,直接用这个思路就行。这篇文章会把背后的设计思路、录制转MCP的核心逻辑、完整实操过程,以及我踩过的坑都说清楚。
1. MCP到底解决了什么问题,为什么需要“录制转MCP”
1.1 MCP是AI应用的“USB接口”
在讲这个工具之前,先得把MCP协议讲明白。MCP(Model Context Protocol)本质上是给大模型和外部工具之间做的标准化接口。你可以把它理解成大模型世界的USB接口——以前每个外部系统都要单独定制连接方式,现在大家统一用MCP协议描述“我这个工具叫什么、有什么参数、能干什么”,大模型只要理解了协议,就能直接调用。
一个MCP server通常会暴露一组tools,每个tool包含三部分:工具名称、功能描述、参数Schema。大模型读到这些内容后,会根据用户指令自动判断“该用哪个工具、传入什么参数”。这就是为什么最近无论是Claude Desktop、还是各类AI编程工具,都在大力支持MCP——它在把AI从“只会聊天”推向“真正能干活”。
但这个统一接口有个现实问题:写MCP server的人很痛苦。因为一个简单的“在页面上输入订单号然后点击查询”,你得写清楚工具描述、定义输入参数、写一段执行逻辑去操控浏览器。这是一个非常琐碎且重复的工作。
1.2 传统写MCP工具的逻辑,有多累
我之前试过手动写一个MCP server去控制某个报表系统,就一个查询动作,过程是这样的:先新建项目、安装MCP SDK,然后定义一个输入参数orderId,再写一段Playwright代码去打开页面、等待输入框出现、输入内容、点击按钮、等待结果渲染。
整个流程下来,大部分时间不是在写有挑战性的逻辑,而是在重复描述“第几步做什么”。更难受的是,如果这个页面有两个类似的流程,就得再来一遍。我当时就想:录制浏览器操作的方案已经存在十几年了,为什么不把录制结果直接变成MCP工具?录下来的每一步操作,天然就是一个工具的执行体;操作过程中的输入项,天然就是工具的入参。这才是把网页操作和大模型能力打通的最短路径。
2. 工具核心拆解:从“录屏”到“可调用的Tools”
2.1 录制层的设计:采集意图,不采集血泪
录制网页操作,听起来像录屏,但实际技术实现上完全不是一回事。真正的网页操作录制,采集的不是视频像素,而是结构化事件数据。每个操作都对应一个Q: 点击了哪个按钮、往哪个输入框填了什么、页面跳转到了哪里。
我做的录制方案选择了浏览器扩展+内容脚本注入:在页面加载时注入一段JavaScript脚本,监听用户的实际DOM事件。业界常用的录制事件包括click、input、scroll、navigate、select等。每个事件记录的关键字段有选择器、操作类型、输入值、触发时间、等待时长等。这类方案的优点是录制的操作直接绑定在真实DOM节点上,执行时可靠度比较高。相比截屏式录制只能靠图像识别猜测点击位置,结构化录制能拿到精确的定位信息。尤其对于大模型工具调用来说,结构化数据更友好——参数可以传、步骤可以调、结果可以查。
2.2 操作的抽象与参数标记
光有原始操作记录还不够,必须把它抽象成“工具可执行的步骤”。我设计了一个中间表示层,把录制到的原始事件规整成统一的操作序列,形成了下面的格式:
{ "version": 1, "name": "query_order_and_export", "description": "查询指定订单并导出报表", "steps": [ { "id": 1, "type": "goto", "url": "https://report.example.com/orders", "wait_after": 300 }, { "id": 2, "type": "input", "selector": "#order_id_input", "value": "{{orderId}}", "wait_after": 300 }, { "id": 3, "type": "click", "selector": "button[data-testid='query_btn']", "wait_after": 1000 }, { "id": 4, "type": "click", "selector": "button:has-text('导出')", "wait_after": 2000 } ], "parameters": [ { "name": "orderId", "type": "string", "description": "要查询的订单号", "match_selector": "#order_id_input" } ] }这里最关键的设计是“动态参数标记”。录制的时候,你输入的是具体测试值,比如订单号20240001。但工具运行时,这个订单号应该来自大模型根据用户问题自动生成。所以在录制面板里,用户可以把某个输入框标记为动态参数,工具会把参数值替换到对应步骤的value字段中。这样每步操作都是模板,具体执行时根据入参渲染。
2.3 执行层:为什么是Playwright而不是裸CDP
录制完成只是第一步,真正要生成MCP server时,执行层选型也很关键。我最终选择了Playwright,而不是直接用Chrome DevTools Protocol(CDP)或原生Selenium,原因有三点:
一是Playwright对选择器定位有非常完善的等待机制,比如element.waitFor()可以在元素出现前保持等待,避免页面异步加载时直接点击失败。二是它原生支持多浏览器,录制工具如果只支持某个浏览器内核,使用场景会窄很多。三是它的Context隔离做得不错,可以方便地复用登录态、设置持久化Cookie,这对于需要认证的网页操作非常重要。
生成MCP server时,我会把上面的JSON操作序列直接编译成可执行的Playwright脚本。每步骤映射成对应的操作调用,参数通过插值方式动态注入。执行函数的外壳由MCP SDK接管,这样生成的server在结构上是标准的,能直接接入任何支持MCP的客户端。
2.4 回放稳定性的细节
工具要真正好用,不是录出来就算了,回放时的稳定性非常考验细节。最大的问题是“过快执行”:录制时的人工操作一步可能间隔好几秒,但回放时自动化跑得飞快,页面跳转、数据渲染根本来不及。timeout策略是等元素可操作,而不是机械等待固定毫秒数。为每个点击操作添加智能等待,Playwright等待元素绑定的第一步执行完毕,再继续下一步。下面代码展示了一个典型步骤的执行逻辑:
async function executeStep(step: Step, context: BrowserContext) { if (step.type === "click") { const locator = page.locator(step.selector); await locator.waitFor({ state: "visible", timeout: 15000 }); await locator.click(); } if (step.type === "input") { const locator = page.locator(step.selector); await locator.waitFor({ state: "visible", timeout: 15000 }); await locator.fill(renderTemplate(step.value, context.params)); } }配合wait_after字段作为兜底,确保JS渲染型页面有足够的反应时间。这套组合拳让我在实际使用中,回放成功率从最早的六成提升到九成五以上。
3. 实操过程:把一个报表查询流程变成MCP工具
3.1 场景准备与关键选择
拿一个非常典型的场景举例:某内部报表系统,需要查询指定订单、导出数据。这个场景有三个特点,非常适合录制转MCP:一是操作路径固定,查完就导出,不需要复杂决策;二是有输入参数,订单号每次不同,适合作为MCP工具入参;三是页面交互较复杂,有表格渲染、弹窗确认等环节,能验证工具的稳定性。
在正式开始录制前,我建议先做一次“预演”。因为录制过程中如果操作失误,整个序列可能作废,预演能帮你确认入口URL、按钮位置、页面加载时长。特别是那些有权限控制的系统,预演时先手动登录一次,确认有权限访问目标页面。
3.2 录制过程实录
实际录制流程如下:
打开报表系统首页,点击扩展图标,选择“开始录制”。输入一个测试订单号(我用的是20240001),点击“查询”按钮等待表格刷出来,再点击“导出”按钮,界面出现“导出成功”提示。最后停止录制。录制过程高度自动化,我在界面上操作什么,系统就记录什么。
录制完成后,在编辑面板里能直观看到每一步的缩略信息:第1步前往首页,第2步向#order_id_input输入20240001,第3步点击查询按钮,第4步点击导出。这时把第2步的输入框勾选为“动态参数”,命名orderId。系统会自动把这个参数加入Parameters列表,后续生成工具描述时直接映射为输入Schema。
3.3 生成MCP Server并把它跑起来
点击“生成MCP Server”,工具会生成一个标准的TypeScript项目,核心代码是这样的:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; const server = new McpServer({ name: "report-ops", version: "1.0.0" }); server.registerTool( "query_order_and_export", { description: "查询指定订单并导出报表", inputSchema: { type: "object", properties: { orderId: { type: "string", description: "要查询的订单号" } }, required: ["orderId"] } }, async ({ orderId }) => { return runPlaywrightSteps("query_order_and_export", { orderId }); } );跑起来只需要两步:
npm install npx tsx ./src/server.tsMCP server跑起来后,把它配置到客户端。以Claude Desktop为例,修改配置文件加上如下内容:
{ "mcpServers": { "report-ops": { "command": "npx", "args": ["tsx", "/path/to/your/project/src/server.ts"] } } }重启客户端后能自动发现工具。
3.4 在客户端实际调用
配置完成后,直接在对话中告诉大模型:“帮我查一下订单12345678并导出。”大模型会基于工具描述自动匹配到query_order_and_export工具,将订单号12345678作为orderId参数传入。随后server拉起无头浏览器执行录制好的Playwright操作序列,最终把执行结果返回给大模型。整个过程对大模型来说就是“调了个函数”,但对用户来说,这个函数内部完成了一段真实网页操作。
我在实际测试中用的不是无头模式,而是headed模式,这样能让用户看到浏览器弹出、输入、点击的过程。对于偏重感知的流程,能直观看到工具执行过程会极大增强可信度。
4. 常见问题与排查技巧实录
4.1 选择器不稳定,页面一改版工具就废了
这是录制类工具最常遇到的问题。页面元素变了,录制时保存的CSS选择器就定位不到了。解决思路是:录制时不要只保存单一选择器,而是保存多个候选。比如点击按钮既记录button[data-testid='query_btn'],也记录相对位置和文本内容。执行时优先用有明确语义的,如果定位失败再尝试文本匹配。文本定位对页面样式变动有很强的鲁棒性,只要文案不变就能正常工作。
实际操作中遇到最多的场景是前端加了新按钮导致选择器命中多个元素。我给录制系统加了重要性扫描:记录元素可见性、唯一性权重,只有唯一命中的选择器才作为首选。这个细节直接决定了回放成功率,值得重视。
4.2 动态参数替换失败,大模型传进去的值不对
动态参数替换的坑出现在一个场景:如果某个输入框录制时值被标记为动态参数,但执行时传入值的格式和页面要求不一致,比如订单号应该是字符串,页面可能只接受数字开头。这个问题我在实际使用中也遇到过,一度怀疑是大模型传参错误,后来排查发现是页面输入框的校验逻辑对“聚焦后回车”比较敏感,单纯fill值不够。
最终解决的方案是在参数Schema里增加格式校验,动态参数支持正则约束。录制时还可以标记字段是文本、数字还是邮箱。MCP server在运行时先做一次本地校验,不符合格式的直接返回错误信息,不启动浏览器,避免白白等超时。
4.3 登录态复用与安全边界
网页操作工具最现实的问题是要登录。每次调用工具都重新登录一遍,不仅慢,而且很多网站有验证码或风控。我用的是持久化Context方案:录制时启用Persistent Context,登录一次后把Cookie和StorageState保存下来,之后每次回放都直接加载。大模型客户端配置里不需要传任何账号密码,凭据只保存在本地项目中。
需要提醒的是,安全边界要把握好:MCP server作为本地服务,只能绑定127.0.0.1,绝不能开放到公网。如果某天你需要远程调用,建议通过内网隧道或加密通道转发,而不是直接暴露端口。涉及支付、后台管理等敏感操作,更要想清楚操作可回滚性。
4.4 它和Browser Use MCP、Playwright MCP有什么不同
聊到MCP操作网页,很多人会提到另外两个方案。我把它们做了个对比:
| 方案 | 核心定位 | 适用场景 | 主要不足 |
|---|---|---|---|
| Browser Use MCP | 大模型自主浏览网页,自己决定每一步操作 | 开放式探索、无固定流程任务 | 决策依赖大模型推理,速度慢、不稳定 |
| Playwright MCP | 大模型通过自然语言直接控制浏览器 | 临时性调试、探索某个页面 | 每个操作都要实时推理,无法固化成稳定工具 |
| 录制转MCP(本文方案) | 把人工操作固化成确定性工具 | 固定流程、大批量重复、报表导出 | 不适用于页面频繁改版和探索性操作 |
在实践中我的判断是:Browser Use适合让大模型去“探索”,Playwright MCP适合让大模型去“临场指挥”,录制转MCP则适合把成熟的操作流程“沉淀”下来。三者不是互相替代,而是互补。面向业务落地,录制路线胜在可控;面向开发调试,手段越灵活越好。
5. 进阶玩法:从“单个工具”到“工具库”
5.1 录制一次,处处调用
这个工具的下一步演进思路,是把录制结果当成可复用的资产。本来你为报表系统录制了一个查询导出工具,之后另一个同事说“我还要把查询结果发送到企业微信群”,只需要在原有录制的末尾追加一个发送步骤,重新生成MCP server,就多了一个更复合的工具。
我建议按照“操作域”来组织录制内容:一类流程对应一个项目,一个项目里挂多个工具。比如“订单管理”域下有“订单查询”“订单导出”“订单备注修改”三个工具,都挂在同一个MCP server上。这样客户端配置文件不用反复改,大模型可在同一域内自行组合调用工具。
5.2 录制时让大模型自动写工具描述
可以尝试在录制工具里接入大模型能力,录制完成后自动分析操作序列,生成工具名和描述。好处是这样生成的描述更贴合操作语义,而不是用户手动起名。比如录制完“点击导出按钮”,系统自动生成名称export_report和描述“点击页面上的导出按钮,下载当前查询结果为Excel报表”。这套提示词对后续大模型的工具理解准确率提升非常明显。
在实际使用中,大模型API调用产生的cost不高,但对生成工具的描述质量提升是肉眼可见的。特别是当你积累了几十个工具后,描述的好坏直接决定大模型能否在一堆工具里选中正确的那一个。
5.3 实际使用中的边界与限制
说完了优点,也得说清楚这东西不适合哪些场景。页面结构变化频繁的站点,每次改版你都得重新录制,成本其实不低。涉及复杂拖拽、画布绘制、文件上传等非标准操作,录制方案虽然能记录,但回放稳定性大打折扣。那些需要验证码或強风控识别的流程,录制工具能自动化操作,但登录本身可能被网站识别为异常行为,风险需要自己衡量。
最稳妥的使用姿势是:先小范围接入,选1-2条最常用、最稳定的流程录制,跑通后再扩展。这个工具适合“某某系统每天都要人工做一遍的操作”,不适合做通用浏览器自动化平台。把定位想清楚,使用的价值感会强很多。
另外多说一句:录制操作转MCP和时下流行的“Agent写网页自动化脚本”并不冲突。Agent的优势在于临场判断,录制转MCP的优势在于确定性和可控性。两者结合才是操作大模型落地的最佳解法。
我之前也看过不少关于Playwright MCP和Browser Use MCP的讨论,各有侧重。但就“快速交付一个真实可用的MCP工具”这件事而言,录制转MCP目前是我见过性价比最高的做法。你不需要理解AI如何决策,不需要反复调整提示词,只需要把人工操作录一遍。
后续我还想在这个工具里加入录制后的步骤顺序调整、条件分支(比如某个元素存在才执行某步)、以及更智能的等待条件生成。这工具目前覆盖的场景还挺有限,但每次用它把一个重复流程变成大模型能调用的工具时,都会觉得“这事儿方向对了”。如果你也经常和大模型打交道、日常工作里又有些固定网页流程,真的建议自己动手试一试这个思路,说不定能帮你省下不少时间。