Files
clz 3c0cd86352 docs: add MCP, plugin/LSP tool, and RAG notes
- notes/mcp-overview.md: MCP 协议概览,跟 tool use 的分层差异
- notes/mcp-client-internals.md: MCP client 内部实现(发现/连接/catalog)
- notes/plugin-tools.md: plugin tool 与 native/MCP 的对比
- notes/lsp-tools.md: LSP 协议科普 + OpenClaw LSP tool 包装
- notes/rag-overview.md: RAG 与向量化(向量只用来检索,prompt 用原文)
- notes/blog/mcp-101.md: 面向开发者的 MCP 科普,LSP 类比引入
- README 索引更新
2026-06-16 00:59:32 +08:00

227 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plugin Tool
基于 [OpenClaw](https://github.com/openclaw/openclaw) 2026.6.2 版本源码。
本文讲 OpenClaw 的 **plugin tool**:跟 native tool、MCP tool 同列,
但归插件作者拥有、用 TypeScript 写、跑在 OpenClaw 同进程里的那一类工具。
## 定位
OpenClaw agent 看见的工具一共有三种来源(详见
[mcp-overview.md](./mcp-overview.md) 的 "MCP 与 tool use 的区别"):
| | 拥有者 | 在哪儿 | 进程 | 协议 |
| ------------- | ----------------- | -------------------------------- | -------------- | -------------------------- |
| **native tool** | OpenClaw core | `src/agents/tools/*` | 同进程 | 无 |
| **plugin tool** | 插件作者 | `extensions/<name>/`(可第三方) | 同进程 | OpenClaw Plugin SDK |
| **MCP tool** | 第三方/用户 | 任何 MCP server | 子进程/远端 | MCP JSON-RPC |
plugin tool 占的格子是:**用 TypeScript 写、走 OpenClaw 内部 SDK 接口、
不需要跑子进程**。介于 "core 写死的 native" 和 "完全解耦的 MCP" 之间,
专门用来做"深度集成 OpenClaw 内部能力,但又不属于 core" 的工具。
## 怎么写一个
入口 `src/plugin-sdk/tool-plugin.ts``defineToolPlugin`。最小例子
(`extensions/llm-task/index.ts`):
```ts
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
import { Type } from "typebox";
export default defineToolPlugin({
id: "llm-task",
name: "LLM Task",
description: "Generic JSON-only LLM tool for structured tasks.",
configSchema: Type.Object({
defaultProvider: Type.Optional(Type.String()),
defaultModel: Type.Optional(Type.String()),
// ...
}),
tools: (tool) => [
tool({
name: "llm-task",
description: "...",
parameters: Type.Object({ // TypeBox schema
prompt: Type.String(),
input: Type.Optional(Type.Unknown()),
// ...
}),
execute: async (params, config, context) => { // 直接执行
// 用 context.api 调用 host 服务(LLM 推理、存储等)
// 返回 text 或 JSON
},
// 或者:
// factory: ({ api, config, toolContext }) => createMyTool(api),
}),
],
});
```
两种声明方式(`tool-plugin.ts` `ToolPluginToolDefinition` 的两个分支):
- **`execute`**:声明完就是个完整的 tool,框架直接调
- **`factory`**:返回一个或多个 `AnyAgentTool`,适合需要根据
config / capability 动态构造工具的场景(`llm-task` 用的就是 factory)
`defineToolPlugin` 内部把每个 tool 注册到 plugin entry,在 plugin
activation 时跑 `register(api)`,把 tool 加进 agent 可用 tool 池
(`tool-plugin.ts:187-200`)。
## Manifest 让发现"不跑代码"
每个 plugin 同时有一份 `openclaw.plugin.json`
(`extensions/llm-task/openclaw.plugin.json`):
```json
{
"id": "llm-task",
"activation": { "onStartup": true },
"configSchema": { "..." },
"contracts": {
"tools": ["llm-task"]
},
"toolMetadata": {
"llm-task": { "optional": true }
}
}
```
`contracts.tools` 把 plugin 提供的工具名列出来。这样 OpenClaw 在
**还没加载 plugin 代码**时就知道"装了这个 plugin 会多哪些工具"——
`openclaw plugins list`、setup、文档生成都不用 import 真实运行时。
这条规则在 `src/plugins/CLAUDE.md` 里写明:
> Preserve manifest-first behavior: discovery, config validation, and setup
> should work from metadata before plugin runtime executes.
`defineToolPlugin` 输出的对象上挂了一个 `[toolPluginMetadataSymbol]`,
包含同样的 metadata,这样 manifest 落地后可以做 contract 校验,
保证 manifest 跟代码不漂移(`tool-plugin.ts:109-116`)。
## Plugin SDK 边界
来自根 `AGENTS.md` 的硬规则:
> 插件 prod 代码:no core `src/**`, `src/plugin-sdk-internal/**`, other
> plugin `src/**`, or relative outside package.
插件只能通过 `openclaw/plugin-sdk/*` 跟 core 交互。`defineToolPlugin`
`register(api)` 拿到的 `OpenClawPluginApi` 就是 plugin 看得见的所有
host 能力(LLM 推理、存储、其他 plugin 句柄等),core 内部的具体实现
对 plugin 透明。
这条边界让 plugin tool 既能用上 host 内部能力(不像 MCP server 那样
完全隔离),又不会被 core 实现变更直接打死(只要 SDK 不破坏)。
## 仓库里有哪些 plugin tool
`grep "contracts.*tools" extensions/*/openclaw.plugin.json` 找到的:
| 插件 | 提供的能力 |
| ----------------- | ----------------------------------- |
| `browser` | 浏览器自动化 |
| `canvas` | 画图 |
| `diffs` | 大文件 diff |
| `llm-task` | "调一次 LLM 跑结构化任务" |
| `memory-core` | 知识/记忆存取 |
| `memory-lancedb` | 向量记忆 |
| `memory-wiki` | wiki 风格记忆 |
| `tavily` | Web 搜索 |
| `firecrawl` | Web 抓取 |
| `feishu` | 飞书集成 |
| `qqbot` | QQ 集成 |
| `google-meet` | Google Meet 集成 |
| `file-transfer` | 文件传输 |
| `codex-supervisor` | Codex 任务编排 |
| `lobster` | (内部任务) |
`extensions/` 里**大多数其实不是 tool plugin**,而是另外两类——见下节。
## 它在更大的 plugin 生态里的位置
`extensions/` 里有三种主要 plugin,用不同的 `define*Plugin` 入口:
| 入口 | 用途 | 例子 |
| ------------------------------- | ---------------------- | ------------------------------ |
| `defineToolPlugin` | 给 agent 注册工具 | `llm-task`, `tavily`, `browser` |
| `defineChannelPluginEntry` | 接入消息渠道 | `telegram`, `discord`, `imessage` |
| `defineSingleProviderPluginEntry` | 接入 LLM provider | `anthropic`, `openai`, `google` |
| `defineSetupPluginEntry` | 安装/onboarding 流程 | 各 plugin 的 setup 部分 |
不同入口在 SDK 内部都收敛到 `definePluginEntry`(`src/plugin-sdk/plugin-entry.ts`)
拿到统一的 plugin lifecycle(activation、register、config)。
**channel / provider plugin 不直接给 agent 注册工具**——它们暴露的是
消息渠道或模型推理能力。tool plugin 是这个生态里**专门给 agent 加
function 的那一类**。
(理论上 channel plugin / provider plugin 也可以通过 `register(api)`
顺手注册 tool,但很少这么干;一旦这么做就既是 channel/provider plugin
又有 tool plugin 的属性。)
## 它怎么进到模型的 tool 列表
跟 MCP tool 走同一条路径(见
[mcp-client-internals.md](./mcp-client-internals.md) 的 "Attempt 引爆点"):
```
plugin 启动时(activation.onStartup 或按需)
→ tools 列表里每个 tool({...}) 被注册成 AnyAgentTool
→ 加进 agent 的可用 tool 池(在 host 进程内存里)
每次 attempt 开始
→ tools = [...native, ...plugin, ...bundleMcp, ...bundleLsp]
→ 走 provider adapter
→ 发给 LLM
```
模型看见的就是一个名字 `llm-task` 或者 `tavily_search`,没有任何
"这是 plugin"的标识。execute 时框架找到对应 tool,调它的 `execute`
函数,**这一切都在 OpenClaw 同一个 Node 进程里**,跟 MCP 走 stdio
子进程或 HTTP 完全不同。
## 跟 MCP tool 的关键对比
| 维度 | plugin tool | MCP tool |
| -------------- | ---------------------------------- | ---------------------------------- |
| 语言 | TypeScript(锁死 Node) | 任意(JSON-RPC) |
| 进程 | 同进程 | 子进程 / 远端 |
| 启动开销 | 几乎 0(import 即可) | spawn + 握手 + listTools |
| 故障隔离 | 一个 plugin 崩了可能影响整个进程 | 子进程崩了不影响别人,有失败退避 |
| 访问 host 服务 | 通过 `OpenClawPluginApi` | 完全隔离,什么都拿不到 |
| 跨应用复用 | OpenClaw 专属 | 任何 MCP host 都能用 |
| 分发 | npm package(`@openclaw/plugin-*`) | 任何二进制 / 服务 |
| 适合做什么 | 深度集成 OpenClaw 内部能力的工具 | 通用、跨应用、可独立运行的工具 |
例子:`llm-task` 必须是 plugin tool,因为它要复用 OpenClaw 的
provider/auth/catalog 体系跑一次内嵌 LLM 推理;
`@modelcontextprotocol/server-filesystem` 适合做 MCP server,因为它
跟 OpenClaw 没耦合,Claude Code 也能用。
## 在"两根轴"里的位置
回顾 [mcp-overview.md](./mcp-overview.md) 的所有权 × 时机两根轴,
**plugin tool 占了"用户/第三方拥有 + 编译时确定"那一格**:
```
开发者拥有 用户/第三方拥有
┌──────────────────┬──────────────────┐
编译时确定 │ native tool │ plugin tool ★ │
├──────────────────┼──────────────────┤
运行时发现 │ (动态 native) │ MCP tool │
└──────────────────┴──────────────────┘
```
这个反例的价值在于:它证明了 "动态/静态" 跟 "开发者/用户" 是
**两个独立的轴**——不能简单说 "用户拥有的就一定是运行时发现"。
plugin tool 是用户拥有的(插件作者写、用户装),但因为还是 npm
package 同语言同进程,完全可以编译时确定。
## 相关
- [mcp-overview.md](./mcp-overview.md) — MCP 概览、与 tool use 的关系
- [mcp-client-internals.md](./mcp-client-internals.md) — MCP client
实现细节(plugin tool 跟 MCP tool 在 attempt 里的拼装)