第 14 章:MCP、插件、Hook 与 Skill
扩展系统最容易犯两个错误:把所有扩展都当“工具”,或把“被发现”误认为“可以执行”。Grok Build 将能力发现、运行时注册、生命周期回调与信任判断拆开。
14.1 四种扩展机制
| 机制 | 提供什么 | 何时参与 |
|---|---|---|
| MCP | 外部动态工具与资源 | tool discovery/call |
| Plugin | skills、agents、MCP、Hook、LSP 的分发包 | discovery/registry |
| Hook | 生命周期 gate 或观察器 | session/tool/compact 等节点 |
| Skill | 模型可读取的专业指令/流程 | prompt 与按需读取 |
Plugin 是容器,不是第五种执行引擎。其组件最终仍走 MCP、Hook、Skill 等各自边界。
14.2 MCP crate 为什么隔离
xai-grok-mcp 隔离了 rmcp 2.1 与 reqwest 0.13,因为 workspace 其他部分仍可能使用不同 reqwest 主版本。若强行统一,可能把协议 SDK 的依赖约束扩散到整个产品。
它负责:
- server configuration;
- stdio 与 streamable HTTP transport;
- OAuth 与 credentials;
- liveness/backoff;
- ACP reverse transport;
- server/tool descriptor;
- wire constants。
SessionActor 只关心“有哪些 server/tool、初始化到哪、如何调用”。
14.3 MCP 初始化状态
MCP server 握手可能慢或失败,不能阻塞所有非 MCP 工作。State 使用 typed progress:
NotStarted
-> Starting { handshakes }
-> Finished { handshakes }2
3
“Finished”表示初始握手阶段已收束,不一定等于每个 server 都健康;is_complete 还会结合 pending handshake 是否为空。
14.4 Blocking 与 Progressive
当模型调用尚未完成初始化的 MCP 工具时,可有两种策略:
- blocking:等待目标 server 初始化;
- progressive:不阻塞主循环,返回引导模型使用 search/discovery tool 的提示。
Progressive 模式使 Agent 能先用本地工具推进,而不是因一个慢 MCP server 冻结整轮。
14.5 动态工具注册
MCP tool 经过:
- server descriptor;
- 名称和 schema 规范化;
- 注册到
ToolRegistryBuilder/ToolBridge; - 对下一次模型请求生成 definition;
- 调用时路由回对应 server。
名字形如 server__tool,并满足模型供应商兼容的严格字符规则。Descriptor 会清洗不兼容字段与过大内容。
动态 register/unregister 必须原子更新 definition 与 dispatch 映射,否则模型可能看见工具但本地找不到,或旧工具仍可调用已退出 server。
14.6 MCP Transport
stdio
启动 child process,通过 stdin/stdout 传 MCP;liveness 可观察子进程退出。自动 restart 只应在:
- stdio transport;
- 配置仍启用;
- session 未 shutdown;
- 重试策略允许。
Streamable HTTP
使用 HTTP、OAuth、backoff;连接/认证状态与本地子进程不同,不应套用相同 restart 语义。
ACP reverse/SDK
IDE/SDK 可通过 ACP reverse request 提供 in-process MCP。相关 client 被原子保存并继承/共享,wire constants 包括 MCP_CALL、MCP_SDK_CALL、MCP_SERVERS、MCP_SDK。
14.7 MCP Dispatcher 的事件合并
server 可能短时间连续上报状态。Session dispatcher 按 (server, kind) 在约 50ms tumbling window 内 coalesce,保留最新状态。
这样避免:
- 配置热更新触发 UI 风暴;
- 每个中间 liveness 状态都写入日志;
- 同一 server restart 的瞬时状态淹没对话。
合并只适用于可覆盖的状态事件,tool result 等不可丢事件不能这样处理。
14.8 OAuth 与 Credentials
MCP credentials 由专用模块管理,避免把 token 放进普通 tool arguments、日志或 prompt。HTTP/OAuth flow 还需要:
- endpoint/server identity 绑定;
- refresh;
- 取消;
- 错误分类;
- 配置热更新后的失效;
- 不把一个 server 的 token 给另一个 server。
14.9 Plugin 的组成
一个 plugin 可打包:
- skills;
- agent definitions/personas;
- MCP servers;
- hooks;
- LSP 配置或其他约定组件。
Manifest 查找支持:
plugin.json
.grok-plugin/plugin.json
.claude-plugin/plugin.json2
3
没有 manifest 时可按目录约定发现。未知字段被忽略,便于向前兼容。
14.10 发现来源与优先级
来源包括:
- CLI override;
- 项目
.grok/.claude,沿 cwd 到 repo root; - 用户
.grok/.claude/ marketplace; - config paths。
内部 scope 优先级用枚举/数字稳定表达(CLI、Project、User、ConfigPath)。冲突解决不仅看名字,还记录 origin,供 inspect/UI 解释。
低层 DiscoveryConfig 的默认构建会自动纳入 CLI 与 ConfigPath,而 User/Project 是否加入由上层配置显式决定。阅读默认值时要同时看产品层如何填充 config,不能把低层 default 等同最终用户行为。
14.11 路径安全
Plugin 名要求 kebab-case;组件路径 canonicalize 后必须仍在 plugin root 内。否则类似:
plugin/skills: ../../outside
plugin/hook: /absolute/untrusted/script2
可能越过包边界。
还要处理:
- symlink;
- 路径不存在时的父目录验证;
- manifest 目录与 component root;
- 重名 shadowing;
- 可执行文件权限。
14.12 “发现”与“激活”
项目 plugin 需要 folder trust。未信任项目中:
- metadata、skills/agents 可能仍可展示;
- executable hooks、MCP、scripts 被阻断;
- registry 记录 disabled/untrusted reason。
这让用户可以先 inspect 再信任,而不是“为了知道插件是什么,必须先执行插件”。
CLI/user 来源通常被视为用户主动选择;ConfigPath 的信任还取决于配置来自用户范围还是项目范围。
14.13 Session Registry 快照
每个 session 获得 PluginRegistry 快照,确保一轮行为不会因后台目录变化而突然改变。/plugins reload 显式重建最新 registry,并可向活跃 session fan out。
快照语义的好处:
- 可复现当前 session 为什么有某工具;
- reload 是可观察事件;
- 配置 watcher 不直接在任意时间改 ToolBridge;
- 失败可保留旧 registry。
14.14 Hook 事件
Hook 覆盖:
- SessionStart / SessionEnd;
- Stop / StopFailure;
- PreToolUse / PostToolUse / PostToolUseFailure;
- PermissionDenied;
- UserPromptSubmit;
- Notification;
- SubagentStart / SubagentStop;
- PreCompact / PostCompact。
Hook definition 包括 event、matcher、command/URL/client callback 等信息。
14.15 Gate Hook 与 Observer Hook
Gate
PreToolUse 可以显式 deny。多个 pre-tool Hook 按配置顺序运行,第一个 deny 终止调用。
Observer/Feedback
PostTool、SessionEnd、Notification 等主要观察已发生事件;Stop Hook 还可返回 block/context,要求 Agent 继续。
明确区分 gate 可避免一个普通 telemetry Hook 意外拥有阻断权。
14.16 Hook 的失败策略
在当前威胁模型下,Hook timeout、进程失败、无效响应通常 fail-open;只有明确 deny 才阻断工具。Stop Hook 会累积 block/context,失败本身不阻止回合完成。
为什么不是全部 fail-closed?
- 项目辅助脚本坏掉不应让 Agent 完全不可用;
- Hook 不是 managed permission 的替代;
- protected environment、permission 和 sandbox 才是主要安全边界。
如果部署把 Hook 当合规硬门,应由 managed config 或专门强策略改变失败语义,而不是默默依赖默认 Hook。
14.17 Hook Payload 边界
Payload 字段有约 128 KiB 上限,防止:
- 巨大工具输出通过环境/JSON 传给每个 Hook;
- Hook 进程内存爆炸;
- 日志泄漏整个文件;
- 恶意插件制造放大。
全局 Hook 先于项目 Hook;canonical event + command/url + matcher 可去重。项目 Hook 受 folder trust 和 disabled file 控制。
14.18 Client Hook
ACP client 也可注册 Hook callback。PreTool client gate 使用较短 deadline,因为它位于工具热路径;第一份 deny 生效。Post Hook 则可并行/完整通知多个 callback。
Leader 多客户端场景下还要确保 callback owner 和 request id 不混淆。
14.19 Skill 与 Plugin Skill
Skill 是模型侧能力说明,不直接执行。它通常包含:
SKILL.md;- workflow/使用条件;
- scripts/assets/templates;
- 进一步参考资料。
原生 skill 与 plugin skill 使用同一发现/优先级系统,但冲突时:
- 原生裸名可优先;
- plugin 资格名
plugin:skill保留可寻址性; - prompt 先提供 catalog,模型按需读取完整内容;
- watcher/reload 让 session 获得新 snapshot。
14.20 四者如何组合
Plugin 负责分发和来源;Registry 负责信任与冲突;具体组件进入各自运行时。
14.21 源码阅读卡
| 目的 | 文件 |
|---|---|
| MCP 总览 | xai-grok-mcp/src/lib.rs |
| server/transport | servers.rs、acp_transport.rs |
| OAuth/credentials | oauth.rs、credentials.rs |
| Session MCP 集成 | acp_session_impl/mcp.rs |
| MCP coalescing | mcp_dispatcher.rs |
| Plugin discovery/registry | shell/src/plugin.rs |
| Hook types | hooks-plugins-types/src/lib.rs |
| Hook runtime | xai-grok-hooks、hook_dispatch.rs |
| Skills | tools/implementations/skills |
14.22 本章结论
扩展平台的核心不是“加载更多代码”,而是保持来源与执行边界:
MCP 动态提供外部工具;Plugin 负责打包与发现;Hook 在明确生命周期点介入;Skill 影响模型方法。Registry、folder trust、路径 containment、权限和沙箱共同决定发现的能力能否真正执行。
最后一章把所有内容整理成工程地图:如何查问题、改功能、选测试,以及这份拆解有哪些验证边界。