--- title: Hands + Skills + MCP updated: 2026-04-22 status: active tags: [module, hands, skills, mcp] --- # Hands + Skills + MCP > 从 [[index]] 导航。关联: [[chat]] [[middleware]] [[butler]] ## 1. 设计决策 **Hands = 自主能力 (行动层), Skills = 知识技能 (认知层), MCP = 外部工具协议。** | 决策 | WHY | |------|-----| | 7 注册 Hands (6 TOML + _reminder) | 每个 Hand 有独立配置和 Rust 实现,启用/禁用由 `enabled` 字段控制。_reminder 由 kernel 代码注册,无 HAND.toml | | 75 Skills + 语义路由 | SKILL.md 定义领域知识,SemanticSkillRouter 用 TF-IDF 匹配(详见 [[butler]] 路由细节),增强 LLM 领域能力而不硬编码 | | MCP bridge | 运行时发现外部工具服务器,McpToolWrapper 适配 Tool trait,让 LLM 在对话中直接调用 filesystem/database 等外部工具 | | LLM Tool Calling 触发 | Skill 调用通过 LLM 生成 ToolUse,不是直接函数调用。保持 LLM 主导决策权 | | 定时提醒链路 | NlScheduleParser 中文时间->cron + _reminder Hand + TriggerManager,支持"每天早上9点提醒我查房" | ## 2. 关键文件 + 数据流 ### 核心文件 | 文件 | 职责 | |------|------| | `crates/zclaw-hands/src/hands/` | 7 个 Hand 实现 (browser/collector/researcher/clip/twitter/quiz/reminder) | | `crates/zclaw-runtime/src/tool/registry.rs` | ToolRegistry 工具注册表 | | `crates/zclaw-runtime/src/tool/builtin/execute_skill.rs` | KernelSkillExecutor 技能执行 | | `crates/zclaw-skills/src/semantic_router.rs` | TF-IDF 语义路由 (路由细节见 [[butler]]) | | `crates/zclaw-skills/src/` | 技能解析、索引、WASM runner | | `crates/zclaw-runtime/src/nl_schedule.rs` | 中文时间->cron 解析器 (6 种模式) | | `crates/zclaw-protocols/src/mcp_tool_adapter.rs` | MCP 工具适配器 + 服务管理 | | `crates/zclaw-protocols/src/mcp.rs` | MCP 协议类型 + BasicMcpClient (stdio transport) | | `crates/zclaw-runtime/src/tool/builtin/mcp_tool.rs` | McpToolWrapper (Tool trait 桥接) | | `desktop/src/store/handStore.ts` | 前端 Hand 状态 | | `desktop/src/lib/mcp-client.ts` | 前端 MCP 客户端 | ### Hand 触发流 ``` LLM 生成 ToolUse{hand_name, params} -> AgentLoop (loop_runner.rs) 检测 ContentBlock::ToolUse -> ToolRegistry.get(hand_name) -> HandExecutor -> needs_approval? -> 等待 approvalStore 确认 -> 用户批准 -> Hand.execute(params) -> 结果 -> ToolResult -> LLM 继续对话 -> Tauri Event emit -> handStore 更新状态 ``` ### 集成契约 | 方向 | 模块 | 接口 / 触发点 | |------|------|---------------| | Called by <- | loop_runner | Tool 执行 | Every tool call during chat | | Calls -> | browser/Twitter/etc | External APIs | Hand-specific operations | | Provides -> | middleware: SkillIndex@200 | `skill_index.rs` | 技能索引注入 system prompt | | Provides -> | mcp: McpToolWrapper | `Tool` trait | 外部工具桥接到 ToolRegistry | ### Hand Tauri 命令 (8 个) | 命令 | 状态 | 说明 | |------|------|------| | `hand_list` | @connected | 列出可用 Hands | | `hand_execute` | @connected | 执行 Hand | | `hand_approve` | @connected | 审批 Hand 执行 | | `hand_cancel` | @connected | 取消执行 | | `hand_get` | @connected | 获取 Hand 详情 | | `hand_run_status` | @connected | 运行状态查询 | | `hand_run_list` | @connected | 运行列表 | | `hand_run_cancel` | @reserved | 取消运行 (无前端 UI) | ### MCP 命令 (4 个) `mcp_start_service`, `mcp_stop_service`, `mcp_list_services`, `mcp_call_tool`。 MCP 工具在 ToolRegistry 中使用限定名 `service_name.tool_name` (如 `filesystem.read_file`)。 `McpManagerState` 和 `Kernel` 共享 `Arc>>`,通过 `sync_to_kernel()` 同步。 ## 3. 代码逻辑 ### 7 注册 Hands | Hand | 功能 | 依赖 | 测试 | 配置 | |------|------|------|------|------| | Browser | 浏览器自动化 (23 Tauri 命令) | WebDriver | 11 | `hands/browser.HAND.toml` | | Collector | 数据收集聚合 | -- | 9 | `hands/collector.HAND.toml` | | Researcher | 深度研究 + 网络搜索 | 网络 | 25 | `hands/researcher.HAND.toml` | | Clip | 视频处理 | FFmpeg | 32 | `hands/clip.HAND.toml` | | Twitter | Twitter 自动化 (12 API v2) | OAuth 1.0a | 30 | `hands/twitter.HAND.toml` | | Quiz | 测验生成 | -- | 5 | `hands/quiz.HAND.toml` | | _reminder | 定时提醒 (系统内部) | -- | -- | 无 TOML (代码注册) | ### Researcher 搜索能力 (04-22 修复) - **搜索引擎**: Baidu + Bing CN 并行 (国内可用),DuckDuckGo fallback - **网页获取**: Jina Reader API (优先,干净 Markdown) -> HTTP fetch (降级) - **LLM 兼容**: 扁平化 input_schema (action/query/url/urls/engine),兼容 glm-5.1 等国产模型 - **空参数回退**: LLM 发送空 `{}` 时,loop_runner 注入 `_fallback_query` 自动搜索 ### 定时提醒链路 ``` 用户消息 "每天早上9点提醒我查房" -> agent_chat_stream (chat.rs) -> has_schedule_intent() 关键词检测 (提醒我/定时/每天/每周等) -> parse_nl_schedule() -> cron 表达式 -> ScheduleParseResult::Exact (confidence >= 0.8) -> TriggerConfig { hand_id: "_reminder", trigger_type: Schedule { cron } } -> kernel.create_trigger() -> TriggerManager 存储 -> 跳过 LLM 调用 (省 token) -> SchedulerService 每60秒轮询 -> should_fire_cron() -> ReminderHand.execute() ``` ### Skill 调用链路 (LLM Tool Calling) ``` skills/ -> SkillRegistry 加载 -> SkillIndexMiddleware@200 注入系统提示 -> LLM 看到 skill_load + execute_skill 工具定义 -> LLM 生成 ToolUse{skill_load} -> AgentLoop -> 返回技能详情 -> LLM 生成 ToolUse{execute_skill} -> KernelSkillExecutor -> Skill 执行 -> ToolResult -> LLM 继续对话 ``` 关键: Anthropic Driver 要求 ToolResult 必须用 `ContentBlock::ToolResult{tool_use_id, content}` 格式。 ### 不变量 - Hand 配置中 `enabled=false` 的 Hand 不会注册到 ToolRegistry - Skill 调用通过 LLM Tool Calling,不是直接函数调用 - MCP 限定名 `service_name.tool_name` 避免与内置工具冲突 - 已删除空壳 Hands (04-17): Whiteboard/Slideshow/Speech,净减 ~5400 行 ## 4. 活跃问题 + 陷阱 ### 活跃 | 问题 | 状态 | 说明 | |------|------|------| | Clip 依赖 FFmpeg | P3 | 用户需本地安装 FFmpeg,否则视频处理 Hand 不可用 | | Hands E2E 通过率 ~70% | P2 | 10 Hand 全部启用,审批机制正常,但部分 Hand 边界场景未覆盖 | | hand.rs TODO | P2 | tool_count/metric_count 待从实际 Hand 实例填充 | ### 历史 (已修复) | 问题 | 修复 | |------|------| | skill_execute 反序列化崩溃 | SEC2-P0-01 04-02 已修复 | | Researcher 空参数 (glm-5.1 不理解 oneOf+const schema) | 04-22 schema 扁平化 + empty-input fallback | | 排版乱码 (stripToolNarration 句子级拆分破坏 markdown) | 04-22 行级过滤 | ## 5. 变更日志 | 日期 | 变更 | 关联 | |------|------|------| | 2026-04-22 | Wiki 5-section 重构: 281->~195 行,语义路由细节引用 [[butler]] | wiki/ | | 2026-04-22 | Researcher 搜索修复: schema 扁平化 + 空参数回退 + 排版修复 | commit 5816f56+81005c3 | | 2026-04-17 | 空壳 Hand 清理: Whiteboard/Slideshow/Speech 删除,净减 ~5400 行 | Phase 5 清理 | | 2026-04-16 | 3 项 P0 修复 + 5 项 E2E Bug 修复 | 三端联调测试 | | 2026-04-09 | 管家模式交付: 语义路由 TF-IDF 接入 ButlerRouter | 6 交付物完成 | ### 测试概览 | 功能 | Crate | 测试数 | |------|-------|--------| | Hands (7 实现) | zclaw-hands | 117 | | 语义路由 + WASM + 编排 | zclaw-skills | 26 | | **合计** | | **143** |