BrowserTools 源码解析
拆解 BrowserTools 的三层架构——Chrome 扩展、Node 中间件、MCP 服务如何分工协作,以及 Lighthouse 集成、元素捕获、调试模式等关键实现背后的工程权衡。
Browser-Tools-MCP 是一个基于 Model Context Protocol (MCP) 实现的浏览器控制工具,让 AI 工具(如编程助手、智能分析助手等)能够与用户的浏览器交互,在无人工介入的情况下从网页中获取所需数据并进行分析处理,如读取日志、分析性能、读取 DOM 等浏览器相关的任务。
例如,借助 Browser-Tools-MCP,AI 客户端可以自动提取网页的控制台日志、网络请求错误、当前选中元素的信息,甚至运行网页性能和 SEO 审计。这对于前端工程师调试应用、性能优化以及让 AI 协助完成浏览器相关任务具有重要意义。
具体使用方法在《最好用的浏览器 MCP:BrowserTools》一节有详细描述,这里不赘述,本文主要聚焦在 Browser Tools MCP 的代码实现原理上。
1. 系统架构
Browser-Tools-MCP 采用三层架构,主要由如下部件组成。
**Chrome 扩展(浏览器数据采集端)**是整个系统的数据采集层,直接运行在浏览器中,负责捕获各种浏览器内部数据和事件。它承担以下核心功能:监听并记录网页的 XHR/Fetch 网络请求及响应、控制台日志输出、错误信息等实时事件;追踪用户在开发者工具 (DevTools) 中选中的 DOM 元素,收集该元素的详细信息;截取当前网页的截图(应对 AI 需要视觉反馈的场景);通过 WebSocket 或 HTTP 将收集的数据发送给 Node 中间件服务。该扩展由后台脚本和 DevTools 面板脚本组成:后台脚本负责与中间件通信和通用事件捕获,DevTools 面板脚本负责与浏览器开发者工具集成(例如获取 $0 选中元素等,详见下文)。
MCP 服务(AI 协议适配层)(browser-tools-mcp)是与 AI 客户端直接对接的部分,它实现了 Model Context Protocol,为 AI 提供一系列可用的"工具"接口。主要功能包括:遵循 MCP 标准,通过标准输入输出(stdio)或其他指定方式与 AI 客户端通信,解析 AI 的工具调用请求并返回结果;定义并注册各种可供 AI 使用的工具(如 getConsoleLogs、runPerformanceAudit、getSelectedElement、runDebuggerMode 等);负责发现并连接本地的中间件服务;接收到 AI 发出的工具调用后,通过调用中间件服务提供的 HTTP API 获取所需数据或操作,然后将结果封装为 AI 可理解的格式返回。MCP 服务相当于整个系统的"大脑"和适配层——一方面屏蔽底层复杂性,为 AI 提供统一简洁的接口;另一方面通过 MCP 协议,让 AI 客户端能够安全地调用本地浏览器功能,而不会直接访问系统或浏览器内部,实现了 AI 到浏览器的安全沙箱通信。
中间件服务(Node.js 服务)是一个比较特别的设计,Browser Tools 在 MCP Server 与 Chrome 插件之外,还设计了一个中间桥接用的 Node 服务(browser-tools-server),负责连接浏览器扩展和 MCP Server,实现数据的中转和加工处理。核心职责包括:通过 WebSocket 与浏览器扩展建立长连接,实时接收扩展发送的事件数据,同时提供 HTTP RESTful API 供 MCP 服务调用,从而获取所需的浏览器数据;对来自扩展的数据进行清洗和格式转换,例如截断过长的字符串、防止敏感信息泄露(过滤掉 Cookie、授权令牌等),以适应 AI 模型的 token 上限和安全要求;利用 Puppeteer 等库提供浏览器自动化功能,例如在独立无头浏览器中执行 Lighthouse 审计;维护浏览器的运行状态(如当前选中元素、日志缓存等),以便在 AI 多次请求之间保持上下文连续。作为中间层,Node 服务解耦了浏览器环境和 AI 协议层,并维护着与浏览器的长连接,使系统具有更高的稳定性和扩展性。
三者之间通讯过程如下:
2. 底层原理
在架构之外,Browser-Tools-MCP 有不少值得学习的技术方案,包括:如何实现网页性能分析、如何获取用户当前选定的元素、如何启动调试功能等,下面逐一讲解。
2.1 Lighthouse 集成设计分析
Browser-Tools-MCP 提供了一系列用于 Review 页面整体表现的功能,包括 runAccessibilityAudit、runPerformanceAudit、runSEOAudit 等(对应无障碍、性能、SEO、最佳实践四类审计)。它通过集成 Lighthouse 和自定义的数据处理逻辑,实现了对网页多维度的质量审计(PWA 审计尚未支持)。
各审计工具在架构上共享相同的流程:AI 客户端调用对应的 MCP 工具 → MCP 服务向 Node 服务发送审计请求 → Node 服务启动无头浏览器并运行 Lighthouse → 获取原始审计结果后进行处理 → 将处理后的结果返回。每种审计类型都有各自的评估指标和报告格式,项目为此分别实现了对应的处理模块,例如 accessibility.ts、performance.ts、seo.ts、best-practices.ts 等。
当 AI 客户端调用某个审计工具(例如性能审计)时,MCP 服务会向 Node 中间件的相应接口(如 POST /audit/performance)发送请求。Node 服务收到请求后,会获取当前需要审计的页面 URL,然后启动一个独立的无头 Chrome 浏览器(通过 Puppeteer),在这个干净的浏览器实例中导航到目标页面,并调用 Lighthouse 对页面执行指定类别的审计。这一机制确保审计在隔离的浏览器环境中进行,不直接打扰用户的浏览器。
最开始我觉得这种设计丢失了各类用户状态(例如登录态)、脱离真实用户环境,取到的数据可能并不真实,但仔细思考后发现这是一个不得已的妥协之举:
- 技术限制:Lighthouse 提供的 Node.js 接口本身就设计用于在受控的 Chrome DevTools Protocol 环境运行,需要对 Chrome 实例有调试控制权。Chrome 扩展的权限受限,无法像 Puppeteer 那样完全控制浏览器(尤其无法无缝调用 DevTools 协议)。通过 Puppeteer 获取一个 Chrome 实例的调试端口,再将该端口提供给 Lighthouse,Lighthouse 才能完成工作。
- 一致性与可控性:用户的浏览器环境千差万别,已打开的页面可能带有各种扩展、登录状态、缓存数据,这些都会影响 Lighthouse 审计结果的客观性和一致性。使用 Puppeteer 启动干净的无头浏览器提供了一个可控的标准环境,每次审计都从零开始,无额外插件和缓存干扰,从而保证结果的一致性和公正性。
- 避免干扰用户体验:Lighthouse 审计过程非常耗费资源,可能让浏览器 CPU 飙升、页面响应变慢。让审计在后台无头浏览器中进行,用户毫无感知,既获取了结果又不打扰用户。
- 审计配置的精细控制:不同类型的审计对浏览器的行为要求不同。例如性能审计需要加载完整资源以测量真实性能指标,而 SEO 或无障碍审计并不需要等待所有图像、视频加载完成。使用 Puppeteer,可以在启动无头浏览器时根据审计类型来拦截或允许部分资源加载。
- 资源复用与效率:每次审计都启动一个新的浏览器开销不小,因此 Node 服务实现了浏览器实例复用的逻辑。审计完成后并不立即关闭浏览器,而是设置一个定时器延迟关闭(例如 60 秒后),如果期间有新的审计进来,便可取消关闭、直接重用。这种**"延迟清理"**策略有效减少了频繁启动浏览器的开销。
当然,也可以设想另一种方案:直接在用户浏览器中运行 Lighthouse。这种方案表面上审计结果更贴近真实用户环境、不需额外启动浏览器实例、架构上也少了一层 Puppeteer 调用,看起来更简单。然而其劣势也明显:直接审计会严重干扰用户体验,导致用户浏览卡顿甚至崩溃;扩展需要提升权限去驱动 Lighthouse,实施起来技术复杂且可能受限;不同用户环境导致结果不一致,可比性差;可控性不足,难以针对不同审计类型优化过程;Chrome 扩展对 DevTools 协议的访问受限,难以获取 Lighthouse 所需的全面调试权限。
综合权衡,Browser-Tools-MCP 最终选择了 Puppeteer + 无头浏览器的方案。虽然引入了额外的浏览器实例,但换来了审计结果的可靠性、一致性以及对用户无侵扰的特性。
2.2 getSelectedElement 元素捕获机制
getSelectedElement 允许 AI 获取到用户当前在浏览器开发者工具中选中的 DOM 元素的详细信息。对于前端调试和分析来说,这是非常实用的能力:AI 可以基于用户选中的元素,提供属性解析、样式建议,甚至生成操作该元素的代码片段。具体过程如下:
- 用户触发:用户在浏览器的 DevTools 面板中选择了某个 DOM 元素。
- 扩展捕获:Chrome 插件检测到元素选择变化事件(DevTools 提供了
onSelectionChanged监听),通过特殊变量$0引用当前选中的元素,在页面上下文执行脚本收集该元素的各项信息,组织成一个 JSON 对象。 - 数据传输:扩展通过 WebSocket 或 HTTP 将上述元素数据发送给本地的 Node 中间件服务(以消息类型
"selected-element"发送)。 - Node 存储:Node 服务接收后保存在内存变量中(例如
selectedElement变量),每当有新的元素选中时都会更新。 - AI 请求:当 AI 客户端调用
getSelectedElement工具时,MCP 服务通过withServerConnection调用 Node 暴露的 API(例如 GET 请求/selected-element)来请求当前选中元素信息。 - 获取返回:Node 服务将之前存储的元素数据返回给 MCP 服务;如果当前没有任何元素被选中,则返回一个标识未选中的消息。
- 结果呈现:MCP 服务将其封装为 AI 可理解的内容格式,最终作为工具调用结果返回给 AI 客户端。
以上流程看似繁琐,但好处在于实时、可靠:无论 AI 何时调用 getSelectedElement,Node 那边总是保留着最近一次用户选中过的元素信息。getSelectedElement 返回的元素数据结构设计也经过精心考虑:
{
tagName: string, // 元素标签名,如 DIV/INPUT 等
id: string, // 元素的 ID(如果有)
className: string, // 元素的 class 列表
textContent: string, // 元素内部文本(截断至100字符)
attributes: [ // 元素所有属性的数组
{ name: string, value: string }, ...
],
dimensions: { // 元素在页面中的位置和大小
width: number,
height: number,
top: number,
left: number
},
innerHTML: string // 元素内部HTML(截断至500字符)
}这个结构提供了一个 DOM 元素常用信息的快照:标签、属性、内容、样式类名,以及位置信息和局部 HTML 内容。通过截断长文本和 HTML,保证数据量不会过大但又包含了元素的主要内容,全面性与紧凑性兼具。
实现细节上值得一提:Chrome 扩展利用了 DevTools 提供的特殊上下文,chrome.devtools.inspectedWindow.eval 可在当前选中页面的上下文执行脚本,通过这个能力获取 $0 并对其进行原生 DOM 操作;Node 服务只是简单地用一个全局变量缓存元素信息,在此场景已经足够,因为 Node 服务通常长时间运行,缓存的数据可视为一种会话级持久;数据的获取是在元素选中时完成的,AI 请求时只是在 Node 中读取现有数据并返回,几乎瞬时,这种预先捕获 + 缓存的机制使每次工具调用开销很小。
2.3 runDebuggerMode 调试模式功能分析
除了单一的数据获取或分析工具,Browser-Tools-MCP 还提供了一个特殊的调试模式工具:runDebuggerMode。它本质上是一个"元工具",用于引导 AI 按照预定步骤对网页应用进行系统化的故障排查和调试。与其说它提供某个具体数据,不如说它提供了一套调试方法论。
runDebuggerMode 的主要目的不是直接返回日志或分析结果,而是指导 AI 客户端执行一系列调试步骤,相当于一个调试剧本。有趣的是,该工具完全由 MCP 服务层实现,不需要 Node 中间件或浏览器扩展的新能力支撑。具体实现上,MCP 服务器在启动时注册了这样一个工具:
server.tool(
"runDebuggerMode",
"Run debugger mode to debug an issue in our application",
async () => ({
content: [
{
type: "text",
text: `
Please follow this exact sequence to debug an issue in our application:
1. Reflect on 5-7 different possible sources of the problem
2. Distill those down to 1-2 most likely sources
3. Add additional logs to validate your assumptions and track the transformation of data structures throughout the application control flow before we move onto implementing the actual code fix
4. Use the "getConsoleLogs", "getConsoleErrors", "getNetworkLogs" & "getNetworkErrors" tools to obtain any newly added web browser logs
5. Obtain the server logs as well if accessible - otherwise, ask me to copy/paste them into the chat
6. Deeply reflect on what could be wrong + produce a comprehensive analysis of the issue
7. Suggest additional logs if the issue persists or if the source is not yet clear
8. Once a fix is implemented, ask for approval to remove the previously added logs
Note: DO NOT run any of our audits (runAccessibilityAudit, runPerformanceAudit, runBestPracticesAudit, runSEOAudit, runNextJSAudit) when in debugging mode unless explicitly asked to do so or unless you switch to audit mode.
`,
},
],
})
);它并不像其他工具那样去调用某个 API 获取数据,而是直接返回了一段预先写好的多行文本。这段文本包含若干编号步骤,实际上就是一套调试剧本:让 AI 先发散思考 5-7 个可能的问题原因,然后收敛聚焦到 1-2 个最有可能的假设,增加日志验证假设,用 getConsoleLogs、getConsoleErrors、getNetworkLogs、getNetworkErrors 提取新日志,必要时获取后端日志,深入分析找出根本原因,若问题未清则追加日志建议重复上述过程,最后在问题解决后提示 AI 征求用户同意移除调试日志。
最后还有一条重要注意事项:在调试模式下不要随便运行各种审计工具,除非明确需要或转换到审计模式——这是为了避免审计干扰调试流程。通过上述指令集,runDebuggerMode 试图让 AI 像一个有经验的工程师那样进行故障排查,克服 AI 可能出现的随意性,使其输出更具逻辑性和针对性。
3. 其他有启发性的技术实现
除了上述核心模块,本项目在一些细节上也体现了优秀的工程实践,对开发类似的 MCP 工具具有启发意义。
- 服务发现与自动重连:MCP 服务启动时会尝试连接本地多个可能的地址和端口寻找 Node 中间件服务,通过请求
/.identity接口验证服务签名是否匹配。一旦发现,即记录下 host 和 port 供后续使用;如果发生错误,系统会自动执行重新发现和重连。这让工具在不同环境下都能可靠运行。 - Token 限制下的数据截断:Node 服务中实现了一个通用的
truncateStringsInData函数,对字符串进行递归截断,对嵌套对象、数组也做相应处理。这些策略确保即使网页内容很多,最终给 AI 的消息仍在可控大小范围内,不会因信息过载而超出模型上下文。 - 敏感信息过滤:Node 服务对来自浏览器的数据进行脱敏处理,例如移除 HTTP 请求和响应头中的 Cookie、Authorization 等敏感字段,避免用户隐私数据被传递给 AI 模型。
- 资源管理与缓存优化:项目中多处运用缓存和延迟释放的技巧。如 Chrome 扩展会缓存每个标签页的 URL;对于频繁的审计任务,通过延迟关闭无头浏览器来复用实例;Node 服务也设计了定期清理机制来避免内存泄露。
- 错误处理与优雅降级:MCP 服务封装的
withServerConnection在请求失败时不会立即抛出错误,而是尝试重新发现服务并重试,如果仍失败则返回一个带错误信息的结果对象。这样 AI 收到的不是莫名其妙的异常,而是一条可读的错误提示。 - 跨平台和扩展性考虑:得益于松耦合架构,Node 中间件通过标准 HTTP + WebSocket 与前后端通信,只要另一种浏览器(如 Firefox)有类似扩展提供所需数据,也可以接入同一个 Node 服务,而 MCP 服务只需认准 Node 服务提供的接口即可。
4. 最后
🖼️ 配图待补:XzZpbIoVGoW1ZAx6Rq6cUIlVnph
区别于一般的 MCP 服务,Browser-Tools-MCP 有一个比较特别的设计:用一个独立 Node 进程桥接浏览器插件与 MCP Server。最开始我觉得有点迷惑,但仔细分析下来算得上比较精妙,有不少可圈可点的优点:
- 关注点分离:三层架构遵循单一职责原则,MCP 服务层只关注协议和工具抽象,Node 中间件层专注数据获取与处理,Chrome 扩展层负责浏览器内部数据采集和 UI 交互。
- 状态持久与长连接:Node 服务内存中缓存了最近的日志、选中元素等状态数据,并保持与扩展的 WebSocket 连接。即使 MCP 服务或 AI 会话重启,浏览器端的状态不丢失。
- 独立生命周期:各组件可以独立启动和重启,互不影响。MCP 服务通常随每次 AI 会话临时启动,而 Node 中间件可以常驻运行。这种设计支持多个 AI 客户端甚至并发会话共享同一个中间件和浏览器连接。
- 技术栈解耦:每层可以采用最适合其职责的技术实现,Node 中间件充分利用了 Node.js 生态(如 Express、Puppeteer、Lighthouse),Chrome 扩展基于浏览器扩展 API 和 DevTools 接口开发。
- 错误隔离与恢复:模块边界清晰带来了良好的故障隔离能力,任一层崩溃时,其余层可以检测到并尝试重连或等待恢复,不会导致整个链路崩溃。
- 数据预处理优化:在返回给 AI 之前,系统对数据进行裁剪和优化,截断过长的字符串、过滤敏感信息,使 AI 接收到的是精简且安全的关键信息。
- 性能与伸缩性:中间件层为性能优化和横向伸缩提供了空间,例如维护浏览器实例池、对频繁请求的数据进行缓存、启用多个 Node 服务实例分担高并发负载等。
尽管这种架构有诸多优点,也引入了一些复杂性和成本:相比将所有功能集中在单一服务中,三层架构需要同时开发和维护浏览器扩展、Node 服务和 MCP 服务三部分,组件间交互、协议对接增加了理解和调试的门槛;任何一次 AI 工具调用都要经过 MCP 服务 → HTTP 请求 → Node 服务 → WebSocket → 浏览器扩展 这样多个节点再原路返回,跨进程、跨组件的数据传递相比单进程内调用开销更大;用户需要确保浏览器安装扩展、Node 中间件在本地运行、MCP 服务正确启动并集成,环境配置成本更高;组件增多也意味着潜在故障点增多,排查问题时需要协调浏览器控制台、Node 日志和 AI 端日志多处信息。
综合来看,Browser-Tools-MCP 的架构设计体现出在功能完备性和系统复杂度之间的权衡。对于需要让 AI 深度操控浏览器的场景,这种分层架构提供了可靠且可扩展的解决方案,但也要求开发者投入更多精力来维护整个体系的协调运作。

