数据截至 (上游 commit fd0b7e1d9ed9)
第 8 章:巧妙之处、边界与横向对比
这章讲什么: 把前七章散落的设计决策收成一张可带走的清单,再诚实地说清它做不到什么、以及和同货架兄弟项目的取舍差异。
8.1 巧妙之处(可借鉴的技术)
① uid 跨快照复用:让引用不因重新截图而作废
妙在哪: 大多数"元素引用"方案在重新截取页面表示后就失效,模型必须重来一遍。这里用 loaderId + backendNodeId 做索引,同一个 DOM 节点在多次快照之间保持同一个 uid;文档一换(loaderId 变),旧 uid 自动全部失效。
依据: src/TextSnapshot.ts:73-87 的 assignIds,复用表在 src/McpPage.ts:119 的 uniqueBackendNodeIdToMcpId。
能拿走的模式: 对外暴露短 id,对内用一个稳定的物理键做索引,并在"世代"变化时整批作废。
② 响应装配集中化:handler 不生产文本
妙在哪: 56 个工具的 handler 只调 setter 打标记,渲染逻辑集中在 format() 一处。于是分页、脱敏、压缩编码、结构化输出这些横切能力只写一遍就全局生效,而且所有工具的输出结 构天然一致。
依据: src/McpResponse.ts:735 的 format,对照 src/tools/input.ts:121-129 的 handler(全部输出就三行 setter)。
③ 错误自带修复路径
妙在哪: 每一条面向模型的错误都回答"接下来该做什么",而不只是"哪里错了"。
| 场景 | 错误文案的可操作部分 | 依据 |
|---|---|---|
| 传了未知参数 | 列出期望参数 + "Remove them and retry" | src/ToolHandler.ts:138 |
| 工具被类别禁用 | 给出确切的开启命令 | src/ToolHandler.ts:35 |
| 还没截过快照 | "Use take_snapshot to capture one" | src/McpPage.ts:642 |
| 分页越界 | 自动回第一页 + 提示 | src/McpResponse.ts:1420-1422 |
| 浏览器已在运行 | "Use --isolated to run multiple instances" | src/browser.ts:268-271 |
| insight 名字错 | "Only use ids given in the 'Available insight sets' list" | src/processors/PerformanceTrace.ts:115-118 |
并且工具名是从定义里取的(listPages().name、handleDialog.name、takeSnapshot.name),改名不会让提示失真。
④ handler 抛错也照样给上下文
妙在哪: 内层 catch 把 handler 的异常存进 response.setError,流程继续走完渲染。模型拿到的是"点击失败 + 当前页面结构 + 控制台里那条报错",一次往返就能判断怎么改。
依据: src/ToolHandler.ts:340-342,错误最终渲染在 src/McpResponse.ts:1395-1398。
⑤ schema 驱动的遥测脱敏
妙在哪: 遥测不是把参数直接上报,而是按 zod 类型逐个变形:
ZodString → 只报长度,且长度还要过一遍分桶 name → name_length: 50
ZodArray → 只报元素个数 filePaths → file_paths_count: 3
ZodNumber/Boolean/Enum → 原样(本身无隐私)
uid / reqid / msgid → 直接不报(PARAM_BLOCKLIST)