
1. 移动端调试的痛点与破局思路做过 H5 的同学应该都有过这种体验页面在电脑浏览器上跑得好好的一上真机就各种玄学问题——接口偶尔报错、某个按钮点了没反应、页面白屏但控制台干干净净。更难受的是很多问题只在特定机型、特定网络环境、特定操作路径下才复现你拿着手机反复点就是抓不到那条关键的日志。传统的移动端调试方案无非几种一是用数据线连电脑走远程调试但 iOS 和 Android 的坑各不相同Android 还好说iOS 的 Safari 调试经常抽风而且真机连电脑在很多场景下根本不现实比如测试同学拿着手机在外面跑或者用户反馈问题时你不可能让对方连电脑。二是用 Charles、Fiddler 这类抓包工具但抓包只能看网络请求看不到 JS 运行时的日志和报错而且 HTTPS 证书配置本身就是一道门槛。三是直接在代码里埋 alert 或者把日志渲染到页面上这种方式极其原始改一次代码发一次版效率低到令人发指。vConsole 的出现解决了一部分问题——它把控制台搬到了手机屏幕上你可以直接在真机上看到 console.log、网络请求、DOM 结构、存储信息。但 vConsole 本质上还是一个“人肉查看”的工具你得自己盯着屏幕找问题而且当页面日志量很大的时候在手机小屏幕上翻日志简直是折磨。更关键的是现在 AI 编程助手越来越普及我们能不能让 AI 直接“看见”这些日志和请求帮我们分析问题、定位 bug这就是 vConsole MCP 这个项目要解决的核心问题。它把 vConsole 采集到的运行时数据通过 MCP 协议暴露给 AI 工具让 AI 能够直接读取 H5 页面的日志、网络请求、错误信息从而实现“AI 帮你 debug”的工作流。简单说以前是你盯着 vConsole 看然后手动把错误信息复制给 AI现在是 AI 直接连上 vConsole自己看、自己分析、自己给建议。这个方案适合谁前端开发、测试工程师、以及任何需要频繁调试 H5 页面的同学。不管你用的是 Vue、React 还是原生 JS只要你的页面跑在浏览器或 WebView 里这套思路都能用上。接下来我会从整体设计、核心实现、实操步骤、踩坑经验几个维度把这个项目完整拆解一遍。2. 整体架构设计与技术选型考量2.1 为什么是 vConsole MCP 这个组合先说说为什么选 vConsole 作为数据采集端。市面上移动端调试工具不少比如 Eruda、Weinre但 vConsole 的生态最成熟接入成本最低而且它的插件机制非常灵活。你只需要在页面里引入一个 JS 文件初始化一下就能获得完整的控制台能力。更重要的是vConsole 内部维护了日志队列、网络请求队列、错误队列这些数据结构是现成的我们不需要自己去劫持 console 或者 XMLHttpRequest直接读它的内部状态就行。再说 MCP。MCP 是 Model Context Protocol 的缩写简单理解就是一套让 AI 工具能够调用外部能力的标准协议。你可以把它想象成 AI 世界的 USB 接口——只要你的工具实现了 MCP 协议AI 就能通过标准方式调用它。现在主流的 AI 编程工具基本都支持 MCP比如各种 IDE 插件、命令行工具、桌面应用。把 vConsole 的数据通过 MCP 暴露出去意味着任何支持 MCP 的 AI 都能直接读取 H5 的运行时信息不需要你手动复制粘贴。这个组合的精妙之处在于vConsole 负责“采集”MCP 负责“传输”AI 负责“分析”。三者各司其职解耦得很干净。你甚至可以把 MCP 服务端部署在本地vConsole 通过 WebSocket 把数据推过来AI 再通过 MCP 协议来查询。整个链路是H5 页面 → vConsole → WebSocket → MCP Server → AI 工具。2.2 通信层为什么选 WebSocket这里重点说一下通信层的选择。vConsole 跑在手机浏览器里MCP Server 跑在开发机上两者之间需要一条双向通信通道。可选方案有几种HTTP 轮询、Server-Sent Events、WebSocket。HTTP 轮询的问题很明显实时性差而且每次请求都要带一堆 header开销大。SSE 是单向的服务端可以推数据给客户端但客户端没法主动发消息给服务端而我们的场景里AI 可能需要主动查询某条日志的详情所以需要双向通信。WebSocket 天然支持全双工建立连接后双方可以随时互发消息而且协议开销小非常适合这种实时数据传输场景。还有一个实际考量WebSocket 在移动端的兼容性非常好iOS 和 Android 的 WebView 都支持得很完善。你不需要担心某些老机型不支持的问题。另外WebSocket 的心跳机制也很成熟我们可以通过定时 ping/pong 来检测连接状态断线后自动重连保证调试过程中不会因为网络波动丢失数据。注意WebSocket 连接地址不要写死成 localhost因为手机访问开发机需要用局域网 IP。建议在初始化时动态获取当前页面的 host然后替换端口号这样无论你换什么网络环境都能自动适配。2.3 数据模型设计日志、请求、错误如何组织vConsole 内部的数据结构其实比较松散日志就是一条条字符串网络请求是一个对象数组。如果直接把这些原始数据丢给 AIAI 理解起来会很吃力。所以我们需要做一层抽象把数据组织成 AI 友好的格式。我的做法是定义三种核心事件类型log、request、error。每条事件都有统一的字段id、type、timestamp、payload。log 的 payload 包含 levellog/warn/error、message、stackrequest 的 payload 包含 method、url、status、duration、requestBody、responseBodyerror 的 payload 包含 message、stack、source。这样 AI 拿到数据后可以按类型过滤、按时间排序、按关键字搜索非常灵活。另外我还会给每条事件打上一个 sessionId用来区分不同的调试会话。比如你刷新了页面vConsole 重新初始化这时候应该生成一个新的 sessionId避免新旧数据混在一起。AI 在分析时也可以指定只看某个 session 的数据减少干扰。3. 核心细节解析与实操要点3.1 vConsole 数据采集的关键改造vConsole 默认只把日志显示在它自己的面板里不会主动往外发送。所以我们需要做一点改造把它的数据“钩”出来。有两种思路一是直接读 vConsole 实例的内部属性二是通过插件机制监听事件。第一种思路比较直接vConsole 实例上有一个$dom属性里面包含了日志列表的 DOM 结构但解析 DOM 太脆弱了vConsole 一升级可能就失效。更好的方式是读它的内部数据存储。vConsole 的日志模块会把每条日志 push 到一个数组里网络模块也有自己的请求列表。我们可以通过vConsole.log.logList和vConsole.network.requestList来访问但这两个属性并不是官方公开的 API所以需要做好版本兼容。第二种思路更优雅vConsole 支持插件机制我们可以写一个自定义插件在插件里劫持 console 方法和 XMLHttpRequest。但这样等于重新实现了一遍采集逻辑工作量不小。我的建议是如果只是自己项目用直接读内部属性最快如果要做成通用工具还是走插件机制更稳妥。实际操作中我采用的是混合方案优先尝试读内部属性如果读不到就降级到劫持 console。同时用一个定时器每隔 500ms 扫描一次新增的日志和请求通过对比上一次的快照来识别新数据。这种方式虽然有点“笨”但胜在稳定不依赖 vConsole 的内部实现细节。3.2 WebSocket 服务端的搭建与心跳机制WebSocket 服务端我用 Node.js 的ws库来实现轻量且性能好。核心逻辑就三件事接受连接、接收消息、广播消息。但这里有几个细节需要注意。首先是连接管理。每个 H5 页面连接上来时我会给它分配一个唯一的 clientId并记录它的 sessionId。这样当 AI 查询时可以指定只看某个 client 的数据。同时如果同一个页面刷新了旧连接会断开新连接会建立我需要把旧连接的数据保留一段时间避免刷新后数据丢失。其次是心跳机制。移动端网络不稳定WebSocket 连接可能悄无声息地断掉而客户端和服务端都不知道。所以需要定时发送 ping 消息如果连续几次没收到 pong 回复就判定连接已断开主动关闭并触发重连。心跳间隔我设的是 30 秒太短了费电太长了检测不及时。重连策略采用指数退避第一次 1 秒后重连第二次 2 秒第三次 4 秒最多重试 5 次。// WebSocket 心跳与重连核心逻辑 let reconnectAttempts 0; const MAX_RECONNECT 5; function connect() { const ws new WebSocket(ws://${location.hostname}:8765); ws.onopen () { reconnectAttempts 0; startHeartbeat(ws); }; ws.onmessage (event) { const msg JSON.parse(event.data); if (msg.type pong) { lastPongTime Date.now(); } }; ws.onclose () { stopHeartbeat(); if (reconnectAttempts MAX_RECONNECT) { const delay Math.pow(2, reconnectAttempts) * 1000; setTimeout(connect, delay); reconnectAttempts; } }; } function startHeartbeat(ws) { heartbeatTimer setInterval(() { if (Date.now() - lastPongTime 60000) { ws.close(); return; } ws.send(JSON.stringify({ type: ping })); }, 30000); }提示心跳消息不要和业务数据混在一起建议用type字段区分。业务数据用type: event心跳用type: ping和type: pong这样服务端处理起来逻辑清晰。3.3 MCP 协议层的实现要点MCP 协议的核心是定义一组工具toolAI 通过这些工具来查询数据。我定义了四个核心工具get_logs获取日志列表支持按 level、关键字、时间范围过滤get_requests获取网络请求列表支持按 URL、状态码、方法过滤get_errors获取错误列表包含 JS 错误和未捕获的 Promise 异常get_session_info获取当前会话信息包括页面 URL、User Agent、连接时间每个工具都有明确的输入参数和输出格式。比如get_logs的输入是{ level?: string, keyword?: string, limit?: number }输出是一个日志数组。AI 调用时只需要传参数服务端负责从内存中查询并返回结果。这里有个关键设计数据是实时更新的AI 每次调用工具拿到的都是最新数据。但 AI 可能一次拿太多数据导致上下文爆炸所以需要支持分页和限制条数。我默认限制返回最近 100 条AI 可以通过limit参数调整但最大不超过 500 条。如果 AI 需要更早的数据可以通过offset参数翻页。另外MCP 工具的描述description非常重要它决定了 AI 能不能正确理解工具的用途。我在描述里写清楚了每个参数的用途、取值范围、默认值以及返回数据的格式。这样 AI 在调用时就不会传错参数。4. 完整实操流程与核心环节实现4.1 环境准备与依赖安装先说一下整体环境要求。你需要一台开发机Mac、Windows、Linux 都行Node.js 版本建议 16 以上因为 MCP SDK 对 Node 版本有要求。手机和开发机需要在同一个局域网内这是 WebSocket 通信的前提。第一步初始化项目mkdir vconsole-mcp cd vconsole-mcp npm init -y npm install ws modelcontextprotocol/sdk第二步创建服务端入口文件server.js。这个文件负责启动 WebSocket 服务和 MCP 服务。WebSocket 服务监听 8765 端口MCP 服务通过 stdio 和 AI 工具通信。const WebSocket require(ws); const { Server } require(modelcontextprotocol/sdk/server/index.js); const { StdioServerTransport } require(modelcontextprotocol/sdk/server/stdio.js); // 内存数据存储 const sessions new Map(); const clients new Map(); // 启动 WebSocket 服务 const wss new WebSocket.Server({ port: 8765 }); wss.on(connection, (ws, req) { const clientId generateId(); const sessionId generateId(); clients.set(clientId, { ws, sessionId, connectedAt: Date.now() }); sessions.set(sessionId, { logs: [], requests: [], errors: [] }); ws.on(message, (data) { const msg JSON.parse(data); if (msg.type ping) { ws.send(JSON.stringify({ type: pong })); return; } if (msg.type event) { const session sessions.get(sessionId); if (msg.eventType log) session.logs.push(msg.payload); if (msg.eventType request) session.requests.push(msg.payload); if (msg.eventType error) session.errors.push(msg.payload); } }); ws.on(close, () { clients.delete(clientId); }); });第三步实现 MCP 工具。这里以get_logs为例const mcpServer new Server( { name: vconsole-mcp, version: 1.0.0 }, { capabilities: { tools: {} } } ); mcpServer.setRequestHandler(tools/list, async () ({ tools: [ { name: get_logs, description: 获取 H5 页面的控制台日志支持按级别和关键字过滤, inputSchema: { type: object, properties: { sessionId: { type: string, description: 会话 ID不传则返回最新会话 }, level: { type: string, enum: [log, warn, error], description: 日志级别 }, keyword: { type: string, description: 关键字搜索 }, limit: { type: number, description: 返回条数默认 100 } } } } ] })); mcpServer.setRequestHandler(tools/call, async (request) { const { name, arguments: args } request.params; if (name get_logs) { const session getSession(args.sessionId); let logs session.logs; if (args.level) logs logs.filter(l l.level args.level); if (args.keyword) logs logs.filter(l l.message.includes(args.keyword)); logs logs.slice(-(args.limit || 100)); return { content: [{ type: text, text: JSON.stringify(logs, null, 2) }] }; } });第四步在 H5 页面里注入 vConsole 和采集脚本。你可以把采集脚本打包成一个独立的 JS 文件通过 script 标签引入或者在构建时内联到页面里。script srchttps://cdn.jsdelivr.net/npm/vconsolelatest/dist/vconsole.min.js/script script var vConsole new VConsole(); // 采集脚本 (function() { var ws new WebSocket(ws:// location.hostname :8765); var lastLogIndex 0; var lastRequestIndex 0; ws.onopen function() { setInterval(function() { // 采集新增日志 var logList vConsole.log.logList || []; for (var i lastLogIndex; i logList.length; i) { ws.send(JSON.stringify({ type: event, eventType: log, payload: { level: logList[i].level || log, message: logList[i].content, timestamp: Date.now() } })); } lastLogIndex logList.length; // 采集新增请求 var reqList vConsole.network.requestList || []; for (var j lastRequestIndex; j reqList.length; j) { ws.send(JSON.stringify({ type: event, eventType: request, payload: { method: reqList[j].method, url: reqList[j].url, status: reqList[j].status, duration: reqList[j].costTime, timestamp: Date.now() } })); } lastRequestIndex reqList.length; }, 500); }; })(); /script4.2 参数计算与性能优化采集频率设成 500ms 是经过权衡的。太快了会增加手机耗电和网络流量太慢了会丢失实时性。实测下来500ms 对于大多数调试场景足够了因为人眼对日志的感知本身就有延迟你不可能在 100ms 内看完一条日志。数据量控制也很重要。如果页面日志量特别大比如每秒几百条内存会迅速膨胀。我的做法是给每个 session 设置上限日志最多保留 5000 条请求最多保留 1000 条超出后自动丢弃最旧的数据。这样既保证了 AI 能拿到足够的历史数据又不会把内存撑爆。WebSocket 消息大小也需要控制。如果某条请求的 responseBody 特别大比如返回了几 MB 的 JSON直接通过 WebSocket 传输会阻塞其他消息。我的处理方式是超过 100KB 的响应体只传前 10KB并标记truncated: trueAI 看到这个标记就知道数据被截断了。实操心得在采集脚本里加一个开关通过 URL 参数控制是否启用采集。比如?vconsole_mcp1时才连接 WebSocket否则只初始化 vConsole 不发送数据。这样你在生产环境可以放心保留 vConsole不会因为采集脚本影响性能。4.3 AI 工具端的配置与联调MCP 服务端跑起来后需要在 AI 工具里配置。以常见的 MCP 客户端为例配置文件大概长这样{ mcpServers: { vconsole: { command: node, args: [/path/to/vconsole-mcp/server.js] } } }配置好后重启 AI 工具它就能发现get_logs、get_requests等工具。你可以直接问 AI“帮我看看最近有没有报错”AI 会自动调用get_errors工具拿到数据后分析并给出结论。联调时建议先手动测试 WebSocket 连接。你可以用wscat工具连上服务端手动发几条消息看看服务端能不能正确接收和存储。然后再测试 MCP 工具用 AI 工具调用get_session_info确认能拿到会话信息。最后再跑完整的 H5 页面验证端到端链路。5. 常见问题与排查技巧实录5.1 连接类问题速查问题现象可能原因排查方法解决方案手机连不上 WebSocket手机和电脑不在同一网段手机浏览器访问电脑 IP 看能否打开连同一个 WiFi关闭 AP 隔离连接频繁断开心跳超时设置过短查看服务端日志的断开时间间隔调大心跳间隔到 30s增加重试次数连接成功但收不到数据vConsole 未正确初始化手机屏幕上能看到 vConsole 面板吗确认 vConsole 初始化在采集脚本之前AI 工具找不到 MCP 服务配置文件路径错误检查 args 里的路径是否绝对路径用绝对路径确认 node 命令可用连接问题是最常见的十有八九是网络问题。我踩过最坑的一次是公司 WiFi 开了 AP 隔离手机和电脑虽然连的是同一个 WiFi但互相 ping 不通。后来换了个路由器就好了。所以遇到连不上的情况先别怀疑代码用手机浏览器直接访问http://电脑IP:8765如果打不开就是网络问题。5.2 数据采集类问题有时候你会发现日志采集不全比如 console.log 的内容拿到了但 console.error 的堆栈信息丢了。这是因为 vConsole 对不同级别的日志存储格式不一样error 级别的日志会把 Error 对象序列化你需要额外处理。我的做法是在采集时统一做一次格式化如果是 Error 对象提取 message 和 stack如果是普通对象用 JSON.stringify 序列化但要注意循环引用的问题。可以写一个安全的序列化函数function safeStringify(obj) { const seen new WeakSet(); return JSON.stringify(obj, (key, value) { if (typeof value object value ! null) { if (seen.has(value)) return [Circular]; seen.add(value); } return value; }); }另一个常见问题是网络请求采集不到。vConsole 的网络模块默认只记录 fetch 和 XMLHttpRequest如果你用的是 axios 并且底层是 XMLHttpRequest那没问题但如果用了某些特殊的请求库可能就抓不到。这时候需要确认 vConsole 的 network 插件是否启用以及请求是否在 vConsole 初始化之后发出。5.3 AI 分析类问题AI 拿到数据后有时候会给出一些“正确的废话”比如“建议检查网络请求是否成功”。这通常是因为数据不够具体或者 AI 没有理解上下文。我的经验是在提问时尽量具体比如“帮我看看 /api/user 这个请求为什么返回 500”而不是笼统地问“帮我看看有什么问题”。另外AI 的上下文窗口有限如果日志量太大它可能只看了前面几条就下结论。这时候可以先用get_errors拿到错误列表再针对具体错误去查相关日志。分步查询比一次性把所有数据丢给 AI 效果更好。避坑技巧在 MCP 工具返回数据时给每条日志加上序号和时间戳这样 AI 在分析时可以参考时间顺序。另外把相关的请求和日志关联起来比如某条错误日志发生的时间点前后 2 秒内的请求可以一起返回帮助 AI 建立因果关系。6. 扩展思路与个人实践体会这套方案跑通之后其实还有很多可以扩展的方向。比如可以把采集到的数据持久化到本地文件这样即使页面关闭了历史数据还在AI 可以随时回溯分析。再比如可以做一个简单的 Web 界面把日志和请求可视化展示方便人工快速浏览。还可以把 MCP 工具扩展一下支持执行简单的 JS 代码片段让 AI 能够主动在页面上做一些验证操作。我在实际使用中最大的体会是AI 不是万能的它需要足够准确、足够结构化的数据才能给出有价值的分析。vConsole MCP 的价值不在于让 AI 替代你 debug而在于把“人肉搬运数据”这个环节自动化了。以前你要把错误信息复制给 AI现在 AI 自己就能拿到省下来的时间可以花在真正需要思考的地方。另外一个小技巧在采集脚本里加一个mark功能你可以在代码里调用window.__vconsole_mcp_mark(用户点击了提交按钮)这样在日志流里会插入一条标记AI 分析时就能知道某个时间点发生了什么操作对定位问题非常有帮助。这个功能实现起来很简单就是往日志队列里 push 一条特殊类型的记录但在实际排查复杂交互问题时特别好用。最后说一个我踩过的坑不要在生产环境开启数据采集。虽然采集脚本本身很轻量但 WebSocket 连接和定时扫描还是会消耗资源而且把用户日志传到开发机也存在隐私风险。建议只在测试环境或者通过 URL 参数控制确保生产环境干净。