chrome-devtools-mcp 设计原则解析:如何为 AI Agent 构建可靠的 Chrome DevTools MCP 服务
本文以仓库中的 docs/design-principles.md 为核心,逐条剖析 chrome-devtools-mcp(Chrome DevTools for coding agents)为 MCP 服务器制定功能时遵循的七条设计原则,并对照源码说明每条原则在工具定义、响应组装与错误处理中的具体落地方式。读完本文,你将掌握:如何判断一个面向 LLM 的工具 API 是否符合 Agent 友好设计,以及如何在自己的 MCP 工具中实践“Token 优化、可组合工具、自修复错误”等关键做法。
该文档原文开宗明义:“These are rough guidelines to follow when shipping features for the MCP server. Apply them with nuance.”(这些是向 MCP 服务器交付功能时应遵循的粗略指导原则,需结合具体情境灵活运用。)下面七条原则即完整出自该文档,每一条都将先给出原文表述,再结合仓库源码印证其实现路径。
一、Agent-Agnostic API:面向标准而非特定模型
原文原则:Agent-Agnostic API —— 使用 MCP 这类标准,不锁定任何一家 LLM,互操作性是关键。
在 chrome-devtools-mcp 中,这条原则体现在整个通信边界上:
- 服务器以标准 MCP 协议对外暴露工具,任何符合 MCP 规范的客户端(文档中提到的 Antigravity、Claude、Cursor、Copilot 等编码 Agent)都可以接入,见 README。
- 所有工具的入参用标准 JSON Schema 描述。每个工具通过 ToolDefinition 声明
schema,ToolHandler 在构造时用 zod 将其编译为registeredInputSchema,注册进 MCP 协议层;参数校验由 schema 驱动,与调用方是哪类模型无关。 - 返回值统一采用 MCP 的内容块模型:
content数组包含text与image两种块,structuredContent作为可选的结构化载荷,定义见 src/McpResponse.ts 中handle()的返回类型。
从源码结构看,服务器内部完全不假设“调用方是某家的 Agent”——它只依赖 MCP 规范定义的能力(如 image 内容块、structuredContent 扩展字段),这正是“不锁定单一 LLM”的直接体现。
二、Token-Optimized:返回语义摘要而非原始数据
原文原则:Token-Optimized —— 返回语义摘要。“LCP was 3.2s” 优于 5 万行 JSON;大量数据的正确归宿是文件。
这是七条原则中最“可量化”的一条,源码中有三处典型落地:
1. 摘要优先的输出层
McpResponse.format() 是所有工具响应汇聚的最终出口。它把各类数据渲染成人可读的文本行,而不是倾倒原始对象。例如 Lighthouse 结果被压缩为类别分数、通过/失败计数与报告清单,而非完整审计 JSON:
response.push('## Lighthouse Audit Results');
response.push(`Mode: ${summary.mode}`);
response.push(`URL: ${summary.url}`);
response.push('### Category Scores');
for (const score of summary.scores) {
response.push(`- ${score.title}: ${(score.score ?? 0) * 100} (${score.id})`);
}
response.push('### Audit Summary');
response.push(`Passed: ${summary.audits.passed}`);
response.push(`Failed: ${summary.audits.failed}`);
性能追踪(trace)同理:attachTraceSummary() 挂载的是解析后的 TraceResult,format() 阶段调用 getTraceSummary() 生成摘要文本写入响应(见 src/McpResponse.ts 与 src/processors/PerformanceTrace.ts)。
2. 大列表分页,而不是全量回传
网络请求、控制台消息、堆快照聚合数据等列表型数据,在 src/utils/pagination.ts 的 paginate() 支持下按页返回,并在响应中附带导航提示:
Showing 1-20 of 150 (Page 1 of 8).
Next page: 2
实现见 McpResponse.#dataWithPagination()。这让 Agent 可以按需翻页,而不是被一次性灌入全量日志。
3. 紧凑编码:toon / gcf 数据格式
对于仍需保留结构化信息的场景,format() 支持通过 --experimentalDataFormat(或其旧别名 --experimentalToonFormat)选择紧凑编码器,将结构化对象编码为比 JSON 更省 Token 的文本形式,格式解析入口在 src/McpResponse.ts。若缺少对应的 peer 依赖包,报错信息会直接给出可复制的安装命令:
The `@toon-format/toon` package is required to use --experimentalDataFormat=toon.
- For npx: npx --package chrome-devtools-mcp@latest --package @toon-format/toon@latest chrome-devtools-mcp --experimentalDataFormat=toon
- For npm: npm install @toon-format/toon
4. 大数据落文件
“Files are the right location for large amounts of data” 在快照工具中体现得最完整:McpResponse.#handleSnapshot() 支持 filePath 参数,指定后快照文本经 context.saveFile() 写入磁盘,响应中只回传一句 Saved snapshot to ...;未指定路径时才把 SnapshotFormatter 的渲染结果内联返回。文件写入统一走 McpContext.saveFile()/saveTemporaryFile(),其中 saveTemporaryFile() 使用 0o600 权限写入临时目录,兼顾安全与隔离。
三、Small, Deterministic Blocks:可组合的小工具而非魔法按钮
原文原则:Small, Deterministic Blocks —— 给 Agent 可组合的工具(Click、Screenshot),而不是魔法按钮。
源码中工具粒度刻意保持得很细:
- 输入类动作拆分为独立的 click / fill / 按键等操作,每个操作有独立 schema 与独立 handler,见 src/tools/input.ts(如
click工具的参数仅uid、dblClick、includeSnapshot三项)。 - 页面类动作(导航、弹窗处理等)独立于输入与调试工具,见 src/tools/pages.ts。
- “slim” 模式把工具面进一步压缩到最小集合:
screenshot(无参数)、navigate(仅url)、evaluate(仅script),定义在 src/tools/slim/tools.ts。
这种拆分的确定性来自两点:其一,每个 handler 只做一件事且副作用明确(readOnlyHint 注解如实标注,例如 screenshot 因 filePath 参数会写文件而被标为 readOnlyHint: false);其二,Agent 拿到的是可复用的原子操作,可以自由编排“导航 → 点击 → 截图”这类流程,而不必依赖一个行为不可预测的复合接口。src/tools/tools.ts 负责按类别汇总全部工具并交给 ToolHandler 注册。
四、Self-Healing Errors:可自修复的错误信息
原文原则:Self-Healing Errors —— 返回包含上下文与潜在修复方法的可执行错误(actionable errors)。
ToolHandler 是这条原则最集中的体现。它没有把“工具不可用”静默掉,而是把“为什么不可用 + 怎么修好”直接写进错误文本:
function buildDisabledMessage(toolName, flag, categoryLabel?) {
// "Tool X is in category Y which is currently disabled.
// Enable it by running chrome-devtools start --flag=true."
}
(见 src/ToolHandler.ts)。Agent 读到这句话后,下一步动作是明确的:带上对应 flag 重启服务器,而不是盲目重试。
同类设计还有:
- 未知参数错误:
buildUnknownArgumentsMessage()会列出未知参数、期望参数全集,并明确指示“Remove it and retry”,见 src/ToolHandler.ts。 - 保留错误因果链:外层 catch 中若错误带
cause,会追加Cause: ...一行再返回,避免底层信息丢失,见 src/ToolHandler.ts。 - 超时错误带上下文:元素交互失败时抛出
Failed to interact with the element with uid X. The element did not become interactive within the configured timeout.(src/tools/input.ts),Agent 由此知道该重试或先处理阻塞。 - 浏览器重连自愈提示:浏览器重启后 page id 会变化,
setReconnectNotice()会让下一次响应开头附上一句Note: the browser was restarted or reconnected since the last call. Page ids have changed. Call list_pages to see open pages.(src/McpResponse.ts),直接告诉 Agent 如何恢复状态。
五、Human-Agent Collaboration:机器可读,人也读得懂
原文原则:Human-Agent Collaboration —— 输出必须机器可读(结构化),同时人类可读(摘要)。
McpResponse.format() 的组装过程本身就是该原则的样板:同一次调用中,它一边往 response 数组里 push 人类可读的文本行(## Pages、Emulating viewport: {...}、Showing 1-20 of 150 ...),一边把对应数据填充进 structuredContent(pages、viewport、pagination 等字段),最后返回:
return {
content: [text, ...images], // 给 LLM 的文本/图片块
structuredContent, // 给程序消费的结构化对象
};
结构化载荷的对外暴露由 --experimentalStructuredContent 开关控制(见 src/ToolHandler.ts),默认关闭以保持协议兼容性——这又是一个“默认简单、按需增强”的实例。此外,分页状态、navigatedToUrl、dialog 等状态也同时出现在文本与结构化两侧,保证人类在日志里排查问题时与 Agent 看到的是同一事实。
六、Progressive Complexity:默认简单,高级参数留给进阶用户
原文原则:Progressive Complexity —— 工具默认简单(高层动作),但为高级用户提供可选的进阶参数。
仓库中有三层递进的复杂度设计:
- slim 模式:最简工具集,例如 src/tools/slim/tools.ts 中的
screenshot工具schema为空对象,调用零参数、语义单一。 - 完整工具的可选参数:
take_screenshot在默认行为(视口截图)之上提供format(png/jpeg/webp)、quality(0-100,仅 JPEG/WebP)、uid、fullPage、filePath等可选项(src/tools/screenshot.ts);click提供dblClick、includeSnapshot等可选项(src/tools/input.ts)。不传参数即是最常见路径。 - 服务器级 flag 门控:实验类能力通过注解条件隐藏,未开启时工具直接不可调用,且错误信息指路(见上文自修复错误一节):
annotations: {
category: ToolCategory.DEBUGGING,
conditions: ['javascriptEvaluation'], // 需开启对应 flag
},
(见 src/tools/slim/tools.ts;条件校验逻辑在 src/ToolHandler.ts)。
从源码结构看,这种分层让轻度用户只暴露最小攻击面与最小认知负担,而深度调试场景(脚本求值、扩展、WebMCP 等)不会被强制启用。
七、Reference over Value:重资产只返回引用
原文原则:Reference over Value —— 对截图、trace、视频等重资产,返回文件路径或资源 URI,绝不返回原始数据流。例外:部分 MCP 客户端支持对重资产的原生处理(例如直接显示图片)。
take_screenshot 的 handler 是三原则(Token 优化、引用优先、渐进复杂度)交汇的最完整案例,其输出策略按优先级为:
if (request.params.filePath) {
// 1) 用户显式指定路径 → 落盘,仅回传 "Saved screenshot to ..."
const result = await context.saveFile(screenshot, request.params.filePath, extension);
response.appendResponseLine(`Saved screenshot to ${result.filename}.`);
} else if (screenshot.length >= 2_000_000) {
// 2) 超过 2MB → 存临时文件,仅回传路径
const {filepath} = await context.saveTemporaryFile(screenshot, `screenshot${extension}`);
response.appendResponseLine(`Saved screenshot to ${filepath}.`);
} else {
// 3) 小图 → 作为 image 内容块直接附加(文档所述“例外”)
response.attachImage({mimeType: `image/${format}`, data: base64});
}
这正对应文档中 “Some MCP clients support a built-in handling of heavy assets e.g. directly displaying images. This could be an exception” 的表述:小尺寸图片以内联 image 块返回,让支持图片的客户端直接展示;大尺寸则退化为文件引用,保护上下文窗口。快照、trace 等其他重资产同样遵循“写文件、回引用”的路线(见第二节 filePath 分支与 storeTraceRecording() 在 src/McpContext.ts 中的存储)。
小结:七条原则如何构成一条工程纪律
回看 docs/design-principles.md 的七条原则,它们在 chrome-devtools-mcp 源码中形成了闭环:
| 原则 | 源码落点 |
|---|---|
| Agent-Agnostic API | MCP 协议工具注册与 JSON Schema(src/ToolHandler.ts) |
| Token-Optimized | 摘要化 format()、分页、紧凑编码(src/McpResponse.ts) |
| Small, Deterministic Blocks | 细粒度工具定义(src/tools/input.ts、src/tools/slim/tools.ts) |
| Self-Healing Errors | 可执行的禁用/参数/重连提示(src/ToolHandler.ts) |
| Human-Agent Collaboration | 文本与 structuredContent 双通道输出(src/McpResponse.ts) |
| Progressive Complexity | slim 模式、可选参数、flag 门控(src/tools/slim/tools.ts) |
| Reference over Value | 2MB 阈值落盘 + 小图内联(src/tools/screenshot.ts) |
文档同时提醒这些原则是 “rough guidelines”,需要 “apply with nuance”——例如 take_screenshot 中对“小于 2MB 直接内联”就是一次有意识的权衡:既守住 Token 预算,又保留了客户端直显图片的体验。对任何要构建“给 LLM 用的工具接口”的团队,这七条原则及其源码证据,都是一份可直接对标的检查清单。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00