工具系统设计:ToolDefinition + ToolHandler + McpResponse
工具系统设计:ToolDefinition + ToolHandler + McpResponse
学习目标
本章分析 Chrome DevTools MCP 的工具系统三层流水线。你将理解:
ToolDefinition的工具元数据规范(Zod Schema、Category、Annotations)defineTool()/definePageTool()工厂函数的设计意图ToolHandler的执行管道:校验 → 启用检查 → 执行 → 响应McpResponse的构建器模式(appendResponseLine、attachImage、includeSnapshot)- 工具模块按功能分类的组织方式
前置知识
- 工具调用协议与执行模型 — 工具规格层、路由层、执行层的分层架构
项目实践
ToolDefinition:工具元数据规范
每个工具通过 ToolDefinition 接口定义:
interface ToolDefinition { name: string; // 工具名称(如 "take_screenshot") description: string; // 工具描述(Agent 用来判断是否调用) annotations: { category: ToolCategory; // 分类(input/navigation/performance 等) readOnlyHint: boolean; // 是否只读(影响 Agent 的调用决策) conditions?: string[]; // 启用条件(如 "experimentalVision") }; schema: ZodRawShape; // Zod 参数 schema blockedByDialog: boolean; // 对话框打开时是否阻塞 verifyFilesSchema: Array<keyof Schema>; // 需要路径校验的参数 handler: (request, response, context) => Promise<void>;}defineTool / definePageTool 工厂函数
项目提供两种工厂函数:
defineTool(definition) → 注册到全局工具列表 → handler 接收 { params }
definePageTool(definition) → 自动设置 pageScoped: true → handler 接收 { params, page } → 在启用 pageIdRouting 时自动获得 pageId 参数工厂函数的优势:
- 统一工具定义的接口
- 自动附加
pageScoped标记 - 支持参数化工厂(
(args) => ToolDefinition)
工具模块组织
工具按功能分组到 13 个模块文件中:
| 模块文件 | 工具类别 | 工具数 |
|---|---|---|
input.ts | 输入自动化 | 10 |
pages.ts | 导航自动化 | 6 |
emulation.ts | 设备模拟 | 2 |
performance.ts | 性能分析 | 3 |
network.ts | 网络调试 | 2 |
console.ts / screenshot.ts / script.ts / snapshot.ts | 调试工具 | 8 |
memory.ts | 内存检测 | 5 |
lighthouse.ts | Lighthouse 审计 | 1 |
extensions.ts | 扩展调试 | 5 |
screencast.ts | 屏幕录制 | 2 |
thirdPartyDeveloper.ts | 第三方工具 | 2 |
webmcp.ts | WebMCP | 2 |
slim/tools.ts | Slim 模式 | 3 |
ToolHandler:执行管道
每次工具调用的完整执行链:
handle(params): ↓1. 检查 disabled → 返回错误(含启用命令) ↓2. 检查 unknownArgumentNames → 返回错误(含正确参数列表) ↓3. await toolMutex.acquire() → FIFO 排队 ↓4. await getContext() → 确保浏览器就绪 ↓5. context.detectOpenDevToolsWindows() → 更新 DevTools 状态 ↓6. new McpResponse() / SlimMcpResponse() → 响应构建器 ↓7. verifyFilesSchema → 路径白名单校验 ↓8. isPageScopedTool ? handler({ params, page }) : handler({ params }) ↓9. response.handle() → 生成最终 content ↓10. ClearcutLogger.logToolInvocation() → 遥测 ↓11. guard.dispose() → 释放 MutexMcpResponse:构建器模式
McpResponse 使用构建器模式逐步组装响应:
const response = new McpResponse();
// 工具 handler 调用这些方法response.appendResponseLine("Navigation completed.");response.attachImage({ data: "...", mimeType: "image/png" });response.includeSnapshot({ verbose: true });response.setIncludeNetworkRequests(true, { pageIdx: 0, pageSize: 50 });response.setIncludeConsoleData(true, { types: ["error", "warn"] });response.attachTraceSummary(traceResult);response.attachLighthouseResult(lighthouseData);response.setError(err); // 可选,标记错误状态响应生成:
response.handle() 方法将所有追加的内容合并为最终的 CallToolResult,包括:
content数组(TextContent+ImageContent)isError标志structuredContent(可选,实验性)
设计取舍
| 取舍 | 选择 | 原因 |
|---|---|---|
| 定义时检查 vs 运行时检查 | 两者 | 定义时决定是否注册,运行时做参数校验 |
| 统一 Handler vs 每个工具自建管道 | 统一 Handler | 复用 Mutex、遥测、错误处理逻辑 |
| 构建器 vs 一次性返回 | 构建器 | Handler 按需追加内容,不强制全部生成 |
陷阱与对策
| 陷阱 | 后果 | 对策 |
|---|---|---|
| Handler 中抛出未捕获异常 | Mutex 未释放 | finally 块保证 guard.dispose() |
| 参数 schema 描述不完整 | Agent 无法正确使用参数 | 每个 schema 字段必须有 .describe() |
readOnlyHint 设置错误 | Agent 错误判断是否需要确认 | 有副作用的工具(文件写入、页面操作)设为 false |