ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

vConsole MCP:让AI直接看见H5页面日志与网络请求的调试利器

vConsole MCP:让AI直接看见H5页面日志与网络请求的调试利器

做前端开发这些年,线上 H5 的报错定位一直是我最头疼的事。用户说"打开白屏",你既没有他的设备,也看不见他浏览器里发生了什么。老套路是让用户装个 vConsole 面板截图发过来;新套路是接一大堆日志平台等数据回流。而我想做的一件事,是让 AI 直接看见 H5 页面运行的日志和请求,帮我 debug。这就有了 vConsole MCP 这个项目——通过 MCP 协议,把页面上的 console 日志、网络请求状态、本地存储这些运行时数据,从"人眼看的 UI"变成"AI 可调用的工具"。AI 只要调一下 get_logs、get_network,就能摸到现场。这篇文章会把项目从动机、设计、实现到踩坑全部拆开讲,适合前端开发、混合 App 调试人员和想上手 MCP 的 AI 玩家。

1. 为什么 H5 debug 需要 AI 直接"看见"页面

1.1 传统 H5 调试的漫长链路

先说场景。线上 H5 出了 bug,用户说白屏。你不在现场,手机上也没有 DevTools,你怎么定位?

大多数团队的做法:让用户打开 vConsole,截三张图,一张 Console、一张 Network、一张页面全貌,发到群里。你对着图片上的红字开始猜,运气好十分钟定位,运气不好网络请求被折叠了,你还得再问一句"那个 /api/user/login 的响应体能展开看一下吗"。这一来一回,快的时候几分钟,慢的时候一上午就没了。

这套链路最大的问题在于,信息获取的每个环节都在衰减。vConsole 面板本身就是给人看的,数据藏在 UI 背后:Console 面板里的 log 对象被格式化成了字符串,Network 面板里的请求详情要一层层点开才能看到响应体,LocalStorage 要单独切 Tab 才能看到。人眼看、人脑记、人手再转述,每一步都可能丢信息。

那用远程日志平台呢?Sentry、Bugsnag 这类工具能上报错误堆栈,但拿不到请求时序,也拿不到 localStorage 和 cookie 这种运行时状态。想完整还原现场,还是差一步。

1.2 AI 不缺理解能力,缺的是"入口"

再说现在的 AI 编程助手。大家普遍的做法,是把报错信息直接复制粘贴给 AI:"帮我看看这个报错"。AI 很聪明,能根据你贴的一段堆栈说出个子丑寅卯来。但问题是,你要贴 Console 里那三行报错?还是 Network Tab 里那几十个请求?还是把用户配置的 localStorage 也拷贝出来?这个过程的每一步,都是纯手工的。

AI 没有第六感,它不知道页面上刚刚发生了什么,除非你主动告诉它。而现在的"告诉"方式太笨了:复制粘贴。复制能带上下文吗?能带时序吗?能带请求响应体吗?不能,最多只能带文本。如果说大模型的推理能力已经接近一个初级工程师,那么它获取运行时数据的能力,可能还不如一个刚入行的实习生——实习生至少知道打开 DevTools 看一眼 Network。

所以核心矛盾是:AI 的代码理解和 debug 能力已经很能打了,但它的"感知能力"完全依赖人工投喂。缺的不是大模型推理,而是让 AI 能直接调取运行时数据的那条通道。

1.3 MCP 恰好补上这条通道

MCP 是 Model Context Protocol 的缩写,一个把 AI 应用和外部工具连接起来的开放标准。它定义了一套简单通用的"工具调用"协议:AI 端(MCP client)向工具端(MCP server)请求能力清单,再按需调用 server 上注册的工具。

你可以把 MCP 理解成给 AI 装了一个通用接口。原来的 AI 只有聊天这一项能力,现在你把"读取 H5 日志""查看网络请求"这些函数挂到它的接口上,AI 就能感知、调用、分析。这就是 vConsole MCP 项目的基本思路:vConsole 负责在 H5 页面里采集数据,MCP server 负责把这些数据暴露给 AI,AI 收到数据后下判断、给结论。

这个思路的好处是,不需要重新训练模型,也不需要专门的调试后端。只要在本地跑一个小服务,配合支持 MCP 的客户端(Claude Desktop、Cursor、Cherry Studio 这类)就能用,开销非常小。

2. 方案拆解:vConsole 负责采集,MCP 负责搬运

2.1 为什么选 vConsole 而不是自研采集层

选 vConsole,是因为它天然适配"能跑 JS 的任何 H5"这个前提。

vConsole 是一个成熟的前端调试面板,集成成本低,一段 script 引入就能用。它最大的优势是跨端:iOS Safari、Android Chrome、微信内置浏览器、钉钉/企业微信的 WebView、混合 App 的 WebView,基本上只要页面能运行 JavaScript,vConsole 都能挂上去。对于"任何 H5"这个目标,它是一个覆盖场景足够广的采集底座。

但我必须说一句实话:直接用 vConsole 的 UI 面板是没法喂给 AI 的。vConsole 的数据基本都在它的插件面板内部,没有对外暴露编程友好的接口。所以我做的是"借思路不借 UI":采集端自己 hook console 和网络请求 API,把数据流出来,vConsole 只作为参考模板和 UI 兜底。相当于拿了 vConsole 的跨端适配经验,但采集逻辑完全自己控制,这样 AI 拿到的才是结构化、可过滤、可追溯的数据。

2.2 MCP 的工具模型怎么帮到调试

MCP 的核心概念有三个:client、server、tool。

  • client:跑着 AI 应用的那一侧,通常是一个聊天界面或者编程 IDE;
  • server:负责暴露工具能力的本地或远程进程;
  • tool:server 上注册的功能单元,每个 tool 有名字、描述、入参和返回格式。

放在调试场景里,client 是用户的 AI 助手,server 是本地 Node 进程,tool 是get_logs、get_network这类函数。AI 根据当前对话上下文判断"该不该调用某个 tool",然后向 server 发请求,server 返回真实的页面数据。整个过程对用户是透明的,你只需要看着 AI 一步步把问题查出来。

传输层面,MCP 在本地跑时一般用 stdio,AI 客户端直接启动一个子进程,通过标准输入输出通信;远端调试场景可以用 SSE 方式。vConsole MCP 的本地 server 走 stdio 和 AI 客户端通信,同时另开一个本地 WebSocket 端口,等着接收 H5 页面采集端推送的数据。

2.3 整体架构与传输选型

整条链路我用一句话描述就是:页面采集端通过 WebSocket 把调试数据推到本地 MCP server,AI 客户端通过 MCP 工具按需拉取。左边是"发生了什么",右边是"谁在看",两边互不阻塞。

选择 WebSocket 而不是轮询,因为日志是流式的,WebSocket 延迟低,能保证 AI 拿到的数据接近实时。而且在移动端弱网环境下,一条长连接比高频轮询更省流量。实测下来,连续跑十分钟日志,消息量也就几十 KB 级别,完全可以接受。

还有一个细节:采集端推数据、AI 拉数据,两者是异步的。页面出 bug 的时候日志先到,AI 后调工具来查;如果 AI 调工具时日志还没到,server 会返回"暂无数据,请等待采集端上报",AI 会再等几秒重试。这个时序问题在实现的时候要专门处理,否则 AI 很容易判断成"页面上没报错"。

3. 从零实现 vConsole MCP 的核心步骤

3.1 采集端:拿到 H5 的日志和请求

采集端是我最先写的部分。思路不复杂:把 window 上的 console 方法包一层,把 XMLHttpRequest 和 fetch 包一层,再加两个全局错误事件监听,最后把数据推到一个环形队列里定时发送。

这是 console 部分的骨架:

class Collector { constructor() { this.queue = []; this.maxQueue = 500; this.ws = null; this.hookConsole(); this.hookNetwork(); this.hookErrors(); } push(item) { this.queue.push({ time: Date.now(), ...item }); if (this.queue.length > this.maxQueue) this.queue.shift(); } hookConsole() { ["log", "warn", "error", "info", "debug"].forEach((level) => { const origin = console[level]; console[level] = (...args) => { this.push({ type: "log", level, args: this.serialize(args) }); origin.apply(console, args); }; }); } serialize(args) { try { return args.map((a) => { if (a instanceof Error) return a.stack || a.message; if (typeof a === "object") return JSON.stringify(a); return String(a); }); } catch { return ["[无法序列化]"]; } } }

这里有两个关键点。第一,serialize一定要做,否则你 push 进去的是对象引用,WebSocket 发送的时候会变成[object Object],数据全丢。第二,hook 之后必须调用原始方法,否则页面里正常的 console.log 会消失,影响调试之外的体验。

网络请求的 hook 稍微复杂一些,因为 fetch 和 XHR 的返回形态不同。fetch 部分我是这样写的:

hookNetwork() { const originFetch = window.fetch.bind(window); window.fetch = async (...args) => { const [input, init] = args; const start = performance.now(); const method = init?.method || "GET"; const url = typeof input === "string" ? input : input.url; try { const res = await originFetch(...args); this.push({ type: "network", method, url, status: res.status, duration: Math.round(performance.now() - start), reqBody: this.safeBody(init?.body), respBody: await this.safeRespBody(res.clone()), }); return res; } catch (err) { this.push({ type: "network", method, url, status: 0, error: String(err), duration: Math.round(performance.now() - start) }); throw err; } }; }

respBody用了res.clone(),是因为原始响应体一旦被页面读取就不能再读了,clone 一份用来采集是最安全的做法,AI 拿到的响应体和你在 Network Tab 里看到的基本一致。判断请求失败用catch而不是只看res.ok,因为 400/500 在 fetch 语义里不算异常,但它们恰恰是调试最需要的数据。XHR 的实现思路类似,重写open和send两个方法,注意在 send 时把 body 存下来就行。

这个采集脚本最终打包成一段 UMD,支持两种接入:HTML 里加<script src="vconsole-mcp-collector.js"></script>,或者在入口文件里 import。生产环境还可以做成构建插件自动注入,免得每次手改。

3.2 本地中转:WebSocket 收数据

采集端是"发",本地这边是"收"。我在 MCP server 进程里内置了一个 WebSocket 服务,默认监听 3987 端口,采集端启动后自动连上。数据格式统一成 JSON,每条至少包含 type、time 和上下文。

网络请求对象长这样:

{ "type": "network", "method": "POST", "url": "https://api.example.com/login", "status": 504, "statusText": "Gateway Timeout", "duration": 2310, "reqBody": "{\"phone\":\"***\"}", "respBody": "{\"code\":504}" }

日志对象长这样:

{ "type": "log", "level": "error", "time": 1735000000000, "args": ["TypeError: Cannot read properties of undefined (reading 'name')", "at Proxy.render"] }

server 收到后按顺序放进环形缓冲区,最多保留最近 5000 条。缓冲区满后旧数据自然淘汰,恰好符合调试场景:AI 关心的是最近发生了什么,不是三个月前的历史。

3.3 MCP server 工具定义

MCP server 用 Node + TypeScript 写,SDK 用官方@modelcontextprotocol/sdk,不需要自己实现协议解析,注册工具、定义入参 schema 就行。

工具清单一开始只有三个,实战中发现还有两个高频需求,补到了五个:

工具名作用典型入参
get_logs获取最近 console 日志,可过滤级别level, limit
get_network获取最近网络请求列表limit, status
get_network_detail按 url 或时间戳查看某个请求的请求体/响应体url, time
get_storage读取 localStorage、sessionStorage、cookiekey, area
clear_buffer清空当前缓冲,开始一段干净的观察窗口无

每个工具注册时都写清楚 description。MCP 里 AI 就是靠 description 决定什么时候调用工具的,写得太模糊 AI 会乱用。比如 get_logs 的 description 我写的是:"获取 H5 页面最近收集到的 console 日志。当用户反馈页面白屏、报错、行为异常时,请先调用本工具了解页面侧错误情况,可按 level 过滤。"

注册代码大致是:

server.registerTool( "get_logs", { description: "获取最近 console 日志,可按 level 过滤", inputSchema: { level: z.enum(["all", "log", "warn", "error"]).optional().default("all"), limit: z.number().int().min(1).max(500).optional().default(100), }, }, async ({ level, limit }) => { const filtered = level === "all" ? buffer : buffer.filter((l) => l.level === level); return { content: [{ type: "text", text: JSON.stringify(filtered.slice(-limit), null, 2) }] }; } );

工具返回的内容统一放在content[0].text里,AI 拿到的是 JSON 文本。有个细节:返回量要克制,一次最多给 100~200 条就够了,超过阈值 AI 反而不知道该看哪条。需要更多就让 AI 二次调用,指定更精确的条件。

3.4 接入 AI 客户端

MCP server 编译好后,在 AI 客户端里配置一下 server 的启动命令。Claude Desktop 和 Cursor 的配置入口不一样,但 JSON 结构是通用的:

{ "mcpServers": { "vconsole-mcp": { "command": "node", "args": ["/opt/vconsole-mcp/dist/index.js"], "env": { "MCP_WSPORT": "3987" } } } }

配置完重启客户端,AI 侧的工具列表里就会出现get_logs、get_network这些工具。可以直接在对话里问 AI:"你能看到哪些调试工具?"它应该能列出来。如果列不出来,优先查 server 进程是否活着,stdio 通信是否正常——这是最常见的失败点。

链路跑通之后,H5 页面注入采集脚本、本地 server 监听 WebSocket、AI 客户端连上 server,三端就组成了一条完整的调试链路。先让页面动起来产生几条日志,再问 AI"最近页面有什么报错",它就会自己去调工具、读数据、下结论。

4. 调试实战:一个典型的"AI 帮我定位 H5 白屏"场景

原理讲完,说一个我真实遇到的场景来演示这条链路怎么工作。

现象:用户反馈某个活动页打开就白屏,本地开发环境完全复现不了。老流程需要用户开 vConsole 截图,然后人肉分析;新流程是用户打开注入过采集脚本的 H5 页面,复现白屏,我这边打开 AI 客户端。

我给 AI 的第一条 prompt 是:

"请用 vconsole-mcp 的 get_logs 查看这个页面最近的日志,优先关注 error 级别的信息,判断白屏是 JS 运行时错误还是接口失败导致的。"

AI 自动调用了get_logs,返回几条 error 日志,最关键的一条是:

TypeError: Cannot read properties of undefined (reading 'render') at VueComponent.mounted (app.a1b2c3.js:152:17)

AI 据此判断:页面在 Vue 组件 mount 阶段报错,是典型的运行时错误。但它还不知道为什么render读不到,于是继续调用get_network查看接口请求,发现/api/user/profile返回了 504。两条信息拼起来,结论就出来了:用户信息接口超时,前端拿到空数据后直接调用了未定义值上的render方法,导致白屏。

AI 随后给出一整套修复建议:接口超时加 2 秒兜底缓存,前端用可选链?.做防御。整个定位过程我没有复制任何一行日志,只负责让页面复现问题,AI"自己看"完了全部分析。

这个场景最能说明 vConsole MCP 的价值:它把"页面运行时数据"从只能人眼看的 UI 面板,变成了 AI 可以编程化访问的数据源。调试效率的提升不是快 20% 那种,而是从"要截图拍照"变成"AI 自己查"的质变。

第二个场景是排查页面卡顿。用户反馈滚动掉帧,我让 AI 用get_network查了一轮没有异常。后来我在采集端扩展了 performance 数据监听,把 long task 和帧率采样也推给 server,再让 AI 结合请求时序和 long task 记录分析,才定位到是某个大图请求阻塞了主线程。这类性能问题,只看报错日志是永远发现不了的。

5. 踩坑记录:跑通之路并不平坦

5.1 vConsole 的 UI 反而会干扰采集

第一个坑来得很快:页面里同时开着 vConsole 面板和我自己的采集脚本,vConsole 的手势交互抢占了一部分 touchstart 事件,导致局部区域的点击偶尔失效。排查了半小时,最终确定不是我 hook 代码的问题,是 vConsole 自身在捕获手势。

解决方案是给 vConsole 提供隐藏模式,只保留能力,不渲染面板。我的采集逻辑完全独立于 vConsole 的 UI,数据全走 WebSocket,所以 UI 隐藏完全不影响采集。改完之后,页面体验和新装一个 SDK 差不多,普通用户无感知。

5.2 移动端 WebSocket 的稳定性问题

实测阶段遇到最多的坑,是移动端 WebSocket 连接不稳定。iOS Safari 在锁屏几分钟后,WebSocket 会静默断开,监听到 close 事件时其实已经断了一会儿了。这样会导致一个很迷惑的结果:AI 调 get_logs 查到的是旧数据,用户已经复现完 bug,日志却迟迟没进来。

我的处理策略是:采集端做指数退避重连,1 秒、2 秒、4 秒、8 秒递增,最多 60 秒封顶。同时 server 端对日志做时间戳校验,如果相邻两条日志的时间间隔超过 30 秒,就视为"采集端断线重连后的批次",在返回给 AI 的文本里标注"注意:以下日志可能存在时间断层"。这个小标注帮 AI 避免了很多误判。

5.3 CSP 环境下的 WebSocket 限制

不少公司的 H5 页面有严格的 CSP 策略,connect-src里没配ws://localhost,WebSocket 根本连不上。这个坑很隐蔽,因为浏览器控制台只会报一条 CSP 拦截错误,设计不好的采集器会静默失败。

我的解法是加了一个 fallback:如果 WebSocket 在 3 秒内没连上,采集端自动切换成"本地暂存 + HTTP 轮询上报"模式,用 POST 请求把日志批量打到本地 server 的一个 HTTP 接口上。代价是延迟从近实时变成 1~2 秒,但至少能拿到数据。这一步对生产环境接入非常关键。

5.4 别把敏感数据裸奔着喂给 AI

这是我从设计之初就坚持的逻辑:默认只采集日志级别、文本、网络请求的 URL、状态码、耗时。请求体和响应体默认不采集,只有开发者明确在采集端配置开启,并且由本人知情操作时才跟随请求上传。

同时,采集端还加了一层脱敏规则:正则匹配token、password、idCard、phone等字段,命中一律替换为***。AI 拿到的数据可以用于技术判断,但不会包含完整的用户敏感信息。这个决定让我敢把工具在团队内部推广,而不是只留在自己手里。

5.5 忘记关的坑

本地调试完,直接把采集脚本留在生产页面上,除了性能损耗,更麻烦的是给页面留了一个可以观察和上报数据的调试入口。我的做法是:把采集脚本做成构建参数控制,ENABLE_MCP_COLLECTOR=true才注入,同时限定域名白名单和采集时间段。这算是我踩过"线上页面多了一个调试入口"的坑之后补上的硬性要求。

6. 还可以怎么玩:vConsole MCP 的扩展方向

6.1 自动复现 + 自动诊断,闭环的第一步

目前 vConsole MCP 是被动采集,页面要有人操作才能产生日志。如果结合 Playwright MCP 这类浏览器自动化工具,可以让 AI 自己打开页面、点击按钮、复现 bug,然后从 vConsole 侧拿到报错。组合起来就是:Playwright MCP 给 AI 操作页面的双手,vConsole MCP 给 AI 观察页面的眼睛。两者配合,"发现问题—复现问题—定位问题"就能自动跑通。

最典型的场景是回归测试:让 AI 按脚本跑一遍核心流程,每步都从 vConsole 采集接口响应和 console 错误,流程结束自动出一份问题清单。这比我手动观察再截图高效得多。

6.2 性能画像:别只盯着错误,也让 AI 看卡顿

白屏和报错只是 H5 问题的一部分,性能问题同样让人头疼。vConsole MCP 现在的核心数据是日志和网络,但我在中间层预留了扩展位,可以把 Performance API 的longtask、navigation数据接进来。之后就能让 AI 分析首屏时间、哪个接口阻塞了首屏、long task 集中在哪个阶段,把简单问题从"报错定位"提升到"性能画像"。

6.3 从本地延伸到团队协作

本地模式解决的是"我自己调试"的问题。如果想团队共用,把本地 WebSocket 服务改成后端转发服务,多个人一起推日志,MCP server 连的是同一个数据源,调试会话就能共享。这个玩法特别适合远程协助:远端的同事触发问题,本地(或多地)的开发和 AI 一起看同一份实时数据,比之前"截图 + 描述 + 等待"的协作方式利索太多。

6.4 能力边界:它解决不了什么

写到这里还是想清楚地把边界讲明白。vConsole MCP 解决的是"AI 能看见页面运行时状态"的问题,它替代不了断点调试,也不能在内存里做变量监控。如果你的问题需要看调用栈内部的值、需要在特定代码行暂停,那还是得用 DevTools 或 IDE 的真调试器。

另外,它依赖页面能执行采集脚本。小程序原生页面这类没有标准 DOM 的环境,这套方案完全不适用,需要另找数据桥。但只要是能跑 JS 的 H5 和 WebView 场景,它就是我目前用得最顺的"AI 调试外设"。

7. 一些真实的体会

用下来最大的体感是:调试这件事从"我在电脑前一遍遍看日志、想问题",变成了"我只需要复现问题,让 AI 按我的思路去查"。思路不是消失了,而是变成了跟 AI 的 prompt 对话:先看 error,再看网络,最后给代码上下文。这套协作方式需要一点磨合,你会发现 AI 的调试路径和你不太一样,但它确实能看到你没注意到的盲点。

最后说几个实际的小建议。采集日志别贪多,环形队列 500 条足够,数据越全,AI 越容易忽略重点;WebSocket 端口选一个跟业务不冲突的冷门端口;调试完记得把采集开关关掉,这个动作我在团队里反复强调了许多次,太容易忘了。如果你也想让 AI 直接看到你手上的 H5 出了什么问题,这个方向真的可以抄作业——MCP 的门槛没有想象中高,采集端代码上不了千行,跑通之后你会觉得,原来 AI 调试 H5 也可以这么顺手。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进