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 索引更新
This commit is contained in:
clz
2026-06-16 00:59:32 +08:00
parent bc510c269b
commit 3c0cd86352
14 changed files with 2009 additions and 6 deletions
+226
View File
@@ -0,0 +1,226 @@
# 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 里的拼装)