上个月某项目的H5页面在部分手机型号上出现白屏。负责的同事把截图丢到群里群里猜了半小时有人说是兼容性问题有人怀疑是缓存还有人建议改配置重发。其实页面下方就有一个调试面板入口点开就能看到一行明晃晃的报错但问题在于看报错的人得先把日志从手机里抄出来贴到电脑上再结合代码慢慢找原因。那一刻我就在想AI既然能帮我们写代码、改需求、审diff为什么不能让它直接看到页面日志于是就有了这个项目vConsole MCP——把vConsole捕获到的H5日志和网络请求通过MCP协议暴露给AI助手。这样AI不再是盲猜而是能看到页面运行时真正发生了什么然后结合它读过的代码直接给出定位结论。这个项目适合谁如果你日常在调移动端H5、各种App内嵌页或者任何“没法开DevTools”的浏览器场景如果你已经在用AI辅助写代码但总觉得它不了解运行时状态——这篇文章会把原理、接入方式和踩坑记录都讲清楚你可以直接照做。1. AI调试H5的最大障碍它看不见运行时的“犯罪现场”1.1 AI很会改代码但debug时只能“隔空猜”现在的AI编程助手最擅长的是读你粘贴过去的代码、对比diff、给出可能的原因列表。但一个线上H5页面白屏了AI根本不知道页面上发生了什么。console里有没有报错某个请求返回的是200还是500localStorage里是不是缺了一个key页面渲染到哪一步停住了这些信息AI统统看不到它只能依赖你手动截图、复制日志、格式化之后喂给它。我有一次让AI帮忙排查“页面按钮点击没反应”的问题。AI分析了半天代码列了五条可能的原因每一条听起来都合理但排查一圈全都不对。真正的原因很简单某个接口返回的字段名变了前端收到数据之后拼接对象时用了一个不存在的属性渲染阶段直接抛异常事件绑定还没来得及执行就断了。这个信息就藏在network面板的响应体里如果AI能看到那条请求的返回五秒钟就能定位。这种“AI很懂代码、但不懂运行时”的割裂感现在已经成为很多人抱怨AI编程工具“只会纸上谈兵”的根源。要补上这个缺口缺的不是更好的模型而是一条让AI获取真实运行数据的管道。1.2 vConsole MCP到底解决了什么给AI装一台“行车记录仪”vConsole是移动端调试场景里很常用的一个调试面板能在页面上挂一个可拖动的悬浮窗实时展示console日志、网络请求、存储信息。它解决了“手机上看不到控制台”的问题但数据仍只能由人来读。MCP模型上下文协议是另一件事它定义了一套开放标准让AI应用可以连接外部工具和数据源以统一的格式调用。简单类比如果说AI天然“长了嘴”擅长对话那MCP就是给AI“接上了手”让它能主动去拿数据、触发动作。vConsole MCP就是把这两者组合起来。页面里嵌入一段采集脚本把vConsole拿到的日志和请求实时转发到一个本地MCP服务AI通过MCP协议去读取这批数据。效果上就像给AI装了一台指向你的H5页面的行车记录仪页面出问题的时候AI可以自己回放“事故录像”而不是坐在那里听你转述。这个思路最大的价值在于AI第一次有了“亲自看一眼现场”的能力。它不再依赖你贴日志——只要你把页面和MCP服务接好AI自己就会去查。2. 从页面到AIvConsole MCP的完整链路动手之前先把管道拆开看一遍。整条链路一共有四个环节页面采集、传输通道、MCP服务、AI客户端。任何一个环节断了AI就拿不到数据。我一个个说。2.1 先明确管道要传什么数据给AI的数据不是越多越好太多反而让它抓不住重点。我在设计时选了四类console日志、网络请求、错误堆栈、存储状态。其中错误堆栈其实是console.error里带出来的单独列出来是为了方便AI快速过滤。数据类型字段作用console日志level, ts, args判断代码执行路径和告警网络请求url, method, status, duration, responseText定位接口异常、字段不匹配错误堆栈message, stack, source快速定位崩溃点和组件存储key, value, type判断登录态、缓存数据是否缺失每条数据必须带时间戳ts这一点后面会专门说。没有时间戳的日志AI只能看到“有一堆问题”看不出“谁先发生、谁导致谁”。2.2 页面端采集hook住console和fetch页面端的核心是一个采集脚本原理不算复杂在页面加载时保留原始console方法和fetch的引用然后用包装函数替换它们。每次调用除了执行原始逻辑还会把参数序列化后push进一个本地队列。// 简化版采集脚本核心逻辑 const queue []; const originalLog console.log; console.log function (...args) { queue.push({ type: log, level: LOG, ts: Date.now(), args: args.map(serializeArg) }); originalLog.apply(console, args); };fetch的hook稍微麻烦一点要处理响应体被消费后不可二次读取的问题。我的做法是先读一次text取前2000个字符然后恢复成原对象返回给业务代码。响应体截断是必须的不然一个文件上传接口的返回能撑爆内存。const originalFetch window.fetch; window.fetch async function (input, init) { const start Date.now(); try { const res await originalFetch.apply(this, arguments); queue.push({ type: request, url: typeof input string ? input : input.url, method: (init init.method) || GET, status: res.status, duration: Date.now() - start }); if (res.clone) { const clone res.clone(); clone.text().then(text { queue[queue.length - 1].responseText text.slice(0, 2000); }).catch(() {}); } return res; } catch (err) { queue.push({ type: request, url: typeof input string ? input : input.url, failed: true, error: String(err), duration: Date.now() - start }); throw err; } };队列不能无限长我用了一个环形缓冲超过2000条最早的会被丢弃。这样即使页面长时间运行内存占用也能控制住。你可能会说为什么不直接每条日志都实时发给服务端因为业务代码里的console调用频率可能非常高实时逐条发会带来大量小包网络请求反而影响页面性能。攒一批再发送是性价比更高的方式。2.3 传输通道WebSocket和轮询的取舍采集到的数据要通过什么方式到MCP服务我对比过两种方案。第一种是WebSocket长连接服务端启动时同时开一个WebSocket端口页面连上来队列里的日志每隔500毫秒批量推一次。第二种是MCP服务提供pull接口AI调用get_logs时服务端再从页面拉取或让页面定时上报。实测下来WebSocket的方案更稳。原因很简单AI调用工具是低频的但日志产生是高频的。如果每次AI来查才拉数据中间的大量日志会丢失或者需要页面端做很大的缓冲。WebSocket让数据先持续到达服务端并缓存在内存里AI来查时随取随用数据的完整性和实时性都能保证。轮询也不是没有优点部署更简单不用维护长连接状态。如果你的页面访问量极小、只是给自己调试用轮询完全够。但考虑到这个项目以后要做成团队共享的调试工具我选了WebSocket。为了兼容部署场景我同时支持了两种传输MCP服务可以本地直连页面进程也可以在服务器上部署一个中继。2.4 MCP服务端把缓冲数据变成AI可调用的工具MCP服务端是整个链路里最关键的一层。它维护了一个环形缓冲队列接收来自页面端的日志数据同时对外暴露一组工具给AI调用。我最终定义了四个核心工具get_logs、get_requests、get_storage、evaluate。其中get_logs和get_requests是只读的AI通过它们获取日志和请求get_storage返回本地存储的键值对evaluate允许AI在页面上执行一小段JS这个工具默认关闭需要手动授权才能开启因为它相当于把页面控制权交给了AI。// MCP服务端核心逻辑简化 import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; const buffer []; const server new Server( { name: vconsole-mcp, version: 1.0.0 }, { capabilities: { tools: {} } } ); server.setToolHandler(get_logs, async ({ level, since }) { const list buffer .filter(item item.type log) .filter(item !level || item.level level) .filter(item !since || item.ts since); return { content: [{ type: text, text: JSON.stringify(list.slice(-300)) }] }; });工具设计有一个原则每次返回的数据量要克制。MCP协议本身支持很大的文本返回但AI的上下文窗口是有限的。日志一次给几千条AI反而无法聚焦。我每次默认返回最近300条并支持用since参数增量拉取。AI第一次查全部第二次只查新的这样既保证了上下文干净也不漏掉关键信息。这里还藏着一个容易被忽略的细节MCP工具的描述文字非常重要AI会不会主动调用它很大程度上取决于description写得清不清楚。我在最初版本里把get_logs的描述写成“获取日志”结果AI很少主动调用。后来改成了“获取H5页面最近产生的console日志列表包含级别、时间戳和参数内容用于排查页面报错和渲染异常”AI基于这段描述几乎每一次debug都会主动调用。给AI的工具描述要说明数据内容和使用场景这既是对AI的引导也是对自己项目逻辑的梳理。另外提醒一句如果这个MCP服务要部署到公网必须加鉴权。无论什么方案暴露日志读取能力等于暴露页面运行时全部数据不加控制的开放服务是给自己挖坑。3. 半小时接入指南让AI开始“看”你的H5原理讲完了实际操作起来并不复杂。以我自己的项目为例整个接入流程半小时以内就能跑通。3.1 页面端引入采集脚本页面引入采集脚本有两种方式。如果只是临时调试直接在HTML里加一行script标签指向本地服务即可如果要长期监控建议通过构建工具打进bundle里并且放到SDK初始化之后再执行确保页面一启动就开始记录。script srchttp://localhost:8080/vmcp-collector.js/script script window.VMCP.init({ wsServer: ws://localhost:8080/ws, includeNetwork: true, maxLogs: 2000, maskFields: [token, phone, userId] }); /script这里有个细节采集脚本要尽早加载否则页面启动早期的报错会漏掉。我的做法是把script标签放在head里并且用普通方式加载保证它在业务代码之前执行。3.2 服务端跑起MCP服务MCP服务我用Node.js写的启动方式很简单git clone 你的仓库地址 npm install npm run server -- --transport stdio服务启动后会同时监听两个通道stdio用来跟AI客户端通信WebSocket用来接收页面数据。如果不想自己管理一个WebSocket服务也可以直接用板载的轮询模式页面每5秒上报一次省去长连接。启动服务前要注意把AI客户端的配置文件指向这个服务。不同的AI桌面客户端配置方式不太一样但核心就是把“stdio命令路径”指向你刚启动的服务进程。这样AI客户端启动时就会自动加载vconsole-mcp提供的能力。我用stdio而不是HTTP是因为stdio的启动方式对AI客户端来说最简单不需要额外维护服务地址你本地跑一个进程AI客户端自己管理它的生命周期。3.3 AI客户端用对话验证管道已通接入完成后先在AI助手里问一句“你现在能调用哪些工具”正常情况下AI会列出vconsole相关的几个工具。然后打开你的H5页面在上面做几个操作再回到AI助手问“看一下这个页面最近的console日志有没有报错”AI会调用get_logs工具读取缓存的日志然后给出它的判断。如果日志里有错误信息你可以继续追问“结合项目代码这个报错最可能出在哪个组件”AI就会基于日志线索进入代码分析。3.4 一个最简验证场景我建议你接入后先跑三个最基础的验证页面控制台手动执行 console.log(hello-vmcp)然后让AI读日志看是否能读到这条。触发一个网络请求随便点个按钮让AI看get_requests的返回。故意在控制台抛一个错误比如JSON.parse()让AI定位。这三个场景能快速确认页面采集、传输、MCP服务、AI调用四个环节各自正常。如果哪一步没通直接用浏览器的network面板看WebSocket连接是否建立基本就能定位问题出在哪个环节。4. 实战复盘用AI定位一个竞态bug理论说再多不如看一次真实的排查过程。我拿最近接手的一个模拟项目X的页面举例。4.1 现象与已有证据页面是一个活动页进入时需要并行拉取两个接口一个获取用户配置/init一个获取活动列表/list。产出反馈是“活动页偶发出现空白卡片刷新后恢复”。由于是偶发问题人工很难复现。页面端已经接入了vConsole MCPAI查看最近的网络请求后发现了线索/list接口的响应时间不稳定有一次856ms有一次30ms。更关键的是有一次/list返回的数据结构里缺少了列表项字段但状态码是200。4.2 AI的调查过程AI接下来做了两个动作先调用get_logs查看页面console的调用顺序再调用get_requests按时间线拉取了最近10次请求的完整响应。AI最终给出的结论是这样的页面在渲染列表前会根据/init返回的某个开关决定是否过滤列表项。正常情况下/init先返回/list后返回渲染逻辑先拿到开关再处理列表数据。但在偶发情况下/list先返回了未过滤的完整数据页面直接渲染随后/init返回触发了一次重新渲染。重新渲染时开关已经拿到但列表数据已经发生了结构变化渲染函数访问了不存在的字段导致空白卡片。这个过程里AI不是靠猜的。它看到了两条具体证据一条是日志里渲染函数的入参结构前后不一致另一条是网络面板里两次请求的返回顺序确实颠倒了。两个证据合在一起结论几乎是确定的。4.3 换做人工排查的差别我事后算了一下如果不借助这个工具人工排查这个bug大概需要经历复现问题、在手机上抓日志、从控制台复制数据、整理时间线、对比接口返回、定位渲染逻辑。运气好半小时运气差一天都有可能。而AI在5分钟内完成了日志跳转、请求对比和代码定位效率差距是数量级的。当然这并不是说AI完全不需要人。排查完成后修复方案还是需要人来写AI负责把证据链清晰呈现让人能快速做出决策。在我看来这种“人做决策、AI做取证”的协作方式才是AI调试工具最合理的状态。5. 开发vConsole MCP过程中踩过的坑这部分我觉得比功能本身更值得记录下来。项目开发过程中踩的坑每一个都是实实在在用调试时间换来的。5.1 海量日志会淹没AI的判断力第一个坑是最直观的页面业务的console.warn非常多过滤器、埋点、旧代码里的调试语句一波一波刷过来。AI一旦拿到几千条日志反而找不到重点。我的解决办法是三管齐下。第一采集端默认只保留LOG和ERROR级别的内容DEBUG和WARN可以配置开启第二MCP工具返回前做一条“摘要提示”把ERROR数量、请求失败数量、平均响应时间单独摘出来AI一上来就能看到整体状况第三所有返回都支持按level和since过滤AI可以自己缩小范围。5.2 敏感信息必须脱敏这个坑相当关键。线上页面的日志里经常混有token、手机号、身份证号。如果你把原始日志直接交给AI等于把用户数据暴露给了第三方模型服务。这不是技术问题是合规问题。我在采集端做了一个脱敏配置可以指定哪些字段需要打码默认对token、password、phone这类字段做正则匹配替换成***。更进一步支持把URL query里的敏感参数自动识别并脱敏。我的原则是采集端的脱敏优先级最高宁可少一条信息也不能漏出隐私数据。另外即使脱敏后也要注意日志里可能残留页面路径、请求URL等间接敏感信息。我在采集端还对URL做了处理默认只保留path部分query里的业务参数全部打码。5.3 时序比日志内容更值钱前面我提过时间戳这里具体说一个case。有一次AI报了这样一个结论“页面初始化失败原因是localStorage里缺少登录态”。但实际上本地存储确实是有的。它为什么会误判因为日志缓存里“读取登录态”这条记录排在某次异常之后AI按顺序读以为登录态读取发生在异常之后所以认为缺失。后来我排查发现问题出在多页面并发加载时日志通过WebSocket传到服务端的顺序乱了。我在采集端给每条日志加了单调递增的序号并在MCP服务端按序号排序后再提供给AI。从那以后类似误判再也没有出现。5.4 页面闪退/刷新时日志会丢WebSocket虽然实时性好但它有个短板页面崩溃或强制刷新时来不及把队列里的日志发出去关键证据直接丢在内存里。这个问题在处理白屏问题时尤其致命因为白屏往往就是崩溃的表现。我的方案是在页面卸载前用navigator.sendBeacon把最近50条日志通过HTTP发送到一个独立接口这个接口在页面长连接断开时也会被触发。sendBeacon能在页面卸载过程中继续发送请求是浏览器专门为这种场景设计的能力。这50条日志不足以还原整个过程但大概率能保留最后一段执行路径——对崩溃排查来说这往往是价值最高的数据。6. 延伸从debug到自动化巡检文章写到这vConsole MCP的基本玩法已经讲完了。但说实话它的潜力远不止让AI被动等命令去查日志。顺着这个思路往下走能做的事情很多。6.1 让AI跑完冒烟测试再自己读日志一个很自然的延伸是让AI动起来。现在很多浏览器自动化工具可以让AI控制页面操作配合vConsole MCPAI就跑完流程后自己读日志判断有没有异常。比如打开页面、登录、进入详情、提交表单、退出登录每个步骤做完AI都拉一次get_logs看到error就停下来报告。这相当于给无头测试加了一个“语义化断言器”。传统自动化测试的断言是预先写死的比如“页面出现XX文案”。但很多问题没法用固定文案断言比如“某个组件内部计算出undefined导致渲染中断”日志里会出现什么你是猜不到的。让AI来读日志等于用自然语言做断言覆盖面大得多。6.2 和录制回放机制配合另一个方向是把用户的实际操作路径录制下来配合日志回放。页面端记录用户点击、滚动、输入的序列出问题后不仅能看到“什么时候报错”还能看到“用户做了哪些操作才触发了这个报错”。AI拿着操作序列和日志两张图还原现场的能力会更强。我在设计采集数据格式时特意在每条日志里预留了一个sessionId字段用来关联一次页面会话。后续如果要接录制回放只需要把操作序列也打上同样的sessionId查询时两条线可以合并展示。6.3 从异常发现到性能分析的延伸网络请求里其实藏着大量性能信息。响应时间、各阶段耗时、资源加载顺序在network数据里都能看到。vConsole MCP天然收集了这些数据如果再做一层聚合AI可以直接回答“这个页面首屏慢是因为哪个接口拖了后腿”这类问题。这个方向可以往性能报告工具上靠不过我还没有深入做算是留个改进空间。回到开头那个白屏问题。现在我的第一反应不是去群里问而是打开vConsole MCP让AI拉日志把证据链摆到面前再下结论。调试工具的意义不单是让我们更快找到bug更是让AI从“代码分析师”变成“现场调查员”。如果你手里也有难啃的H5问题不妨试试给AI装上这双眼睛。