- 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 索引更新
12 KiB
MCP(Model Context Protocol)概览
基于 OpenClaw 2026.6.2 版本源码。 本文讲 MCP 是什么、OpenClaw 怎么用它、它和 tool use 是什么关系。
OpenClaw 作为 MCP client 的内部实现(发现、连接、catalog、缓存等) 单独写在 mcp-client-internals.md。
MCP 是什么
MCP 是一个开放协议,定义了 LLM 应用(host)与外部工具/数据源(server) 之间的通信格式。可以理解为"AI 应用的 USB-C 接口":一个 host 通过 MCP 可以同时接入文件系统、数据库、第三方 API 等任意 server,而不需要为每个 工具单独写 adapter。
通信用 JSON-RPC 2.0,MCP server 暴露三类能力:
- tools — 模型可以调用的函数(
tools/list、tools/call) - resources — 可读的数据源(
resources/list、resources/read) - prompts — 可被引用的预制 prompt 模板
OpenClaw 里 MCP 的两个方向
OpenClaw 同时扮演 MCP server 和 MCP client(见 docs/cli/mcp.md):
┌──────────────────────┐
│ External MCP client │
│ (Codex / Claude Code)│
└─────────┬────────────┘
│ stdio JSON-RPC
▼
┌──────────────────────────────────────┐
│ openclaw mcp serve │
│ (src/mcp/channel-server.ts) │ ← OpenClaw as MCP server
│ ChannelBridge → Gateway WS │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ OpenClaw agent runtime │
│ (src/agents/agent-bundle-mcp-*.ts) │ ← OpenClaw as MCP client
└─────────┬────────────────────────────┘
│ stdio / SSE / streamable-http
┌─────────▼──────────┐ ┌──────────────┐
│ third-party MCP │ │ remote HTTP │
│ server (filesystem,│ │ MCP server │
│ memory, ...) │ │ (OAuth) │
└────────────────────┘ └──────────────┘
方向 1:OpenClaw 当 server(openclaw mcp serve)
把 OpenClaw 的 channel 会话(Telegram/Discord/iMessage 等)通过 MCP 暴露给外部 client(Codex、Claude Code 等)。
入口在 src/cli/mcp-cli.ts,核心装配在 src/mcp/channel-server.ts:
McpServer(@modelcontextprotocol/sdk)
+ OpenClawChannelBridge (连到本地/远端 Gateway WebSocket)
+ registerChannelMcpTools(server, bridge)
src/mcp/channel-tools.ts 注册的 tool:
conversations_list/conversation_getmessages_read/attachments_fetchevents_poll/events_waitmessages_sendpermissions_list_open/permissions_respond
传输是 stdio:外部 client 启动 openclaw mcp serve 子进程,两边通过
stdin/stdout 跑 JSON-RPC。OpenClaw 内部再用 WebSocket 连到 Gateway 取
真实会话。
方向 2:OpenClaw 当 client
openclaw mcp add/set/configure/probe/login/... 把第三方 MCP server
保存到 openclaw.json 的 mcp.servers 下;OpenClaw 的 agent runtime
在跑的时候把这些 server spawn 起来,并把它们的 tool 合到模型可调用的
工具列表里。
这一侧的详细实现(server 怎么被发现、何时连接、catalog 怎么暴露给模型、 怎么缓存与失效、几个安全/兼容细节)单独写在 mcp-client-internals.md。
MCP 与 tool use 的区别
这两个经常被混在一起,其实是 不同层的概念。
一句话:
- Tool use(function calling):模型 ↔ 应用之间的约定—— 模型怎么"申请调用一个函数"
- MCP:应用 ↔ 工具实现之间的协议—— 这个函数从哪儿来、怎么真正执行
模型不知道 MCP 的存在,MCP server 也不知道是哪个模型在调它。
分层看
┌─────────────────────────────────────────┐
│ LLM (Claude / GPT / Gemini) │
│ 只认 tool_use / function_call │ ← Tool Use 层
└─────────────────────┬───────────────────┘
│ provider API
┌─────────────────────▼───────────────────┐
│ OpenClaw agent runtime │
│ - 收集所有可用 tool │
│ - 打平成 provider 期望的格式 │
│ - 收到 tool_use 后分发执行 │
└──┬───────────────┬──────────────┬───────┘
│ │ │
┌──▼──────┐ ┌─────▼─────┐ ┌─────▼──────┐
│ native │ │ plugin │ │ MCP server │ ← Tool 来源
│ Read/ │ │ tool │ │ (stdio/HTTP)│
│ Bash... │ │ │ │ │
└─────────┘ └───────────┘ └─────────────┘
模型那一层永远是 tool use;MCP 只是 OpenClaw 拿到工具实现的一种方式。
所有 tool 最后都被归一化成同一个类型 AnyAgentTool
(src/agents/tools/common.ts):
- Native tool:代码里直接定义,例如
src/agents/tools/read.ts - MCP tool:
buildBundleMcpToolsFromCatalog把Client.listTools()返回的远端 catalog 包装成AnyAgentTool,execute里调client.callTool(...)
关键差异表
| 维度 | Tool use(原生 function calling) | MCP |
|---|---|---|
| 谁定义 | 应用代码内联定义 schema | 远端 MCP server 自己声明 |
| 谁执行 | 应用代码同进程里调用 | 远端 server(子进程 stdio,或 HTTP) |
| 发现方式 | 编译时静态注册 | 运行时 tools/list 动态拉取 |
| 跨语言 | 绑死宿主语言 | JSON-RPC 任何语言都能写 server |
| 跨应用复用 | 每个应用各写一遍 | 同一个 server 给 Claude Code、Codex、OpenClaw 都用 |
| 协议层 | 没有协议,就是个函数 | JSON-RPC 2.0 + 标准 schema |
| 能力 | 只有 function | tools / resources / prompts / notifications |
| 生命周期 | 跟着进程 | 独立进程,需要管 spawn/health/timeout/idle reclaim |
类比
- Tool use ≈ 编程语言里"函数调用"这个概念本身
- MCP ≈ gRPC / OpenAPI ——一种让函数能跨进程/跨语言被发现和调用的具体协议
或者:
- Tool use ≈ "我要打个电话"
- MCP ≈ "电话怎么拨通对方公司的总机,问到分机号,再接到具体的人"
实际后果
- 不用 MCP 也能 tool use:OpenClaw 的
Read、Bash、Edit这些都是 原生 tool,模型直接调,没 MCP 什么事。 - 同一个 MCP server 给多个 host 复用:
@modelcontextprotocol/server-filesystem一次装好,Claude Code 和 OpenClaw 都能用;换成原生 tool 就得在每个 host 里各实现一遍。 - MCP server 挂掉不影响其他 tool:远端调用有网络/进程/超时故障模式, OpenClaw 用失败退避隔离(见 mcp-client-internals.md 的 "几个实现细节")。
- MCP tool 对模型完全透明:模型看到的就是
filesystem__read_file(path), 跟 native tool 长得一样,__前缀只是为了避免重名,不是给模型暗示 这是 MCP。
编译时 vs 运行时:典型用法不是协议本质
常见直觉是 "native tool use 是编译时静态、开发者固定;MCP 是运行时 动态、用户自定义"。方向对,但这是 典型用法,不是 协议本质。
实践中的分布确实如此:
| 维度 | Native tool use | MCP tool |
|---|---|---|
| 谁定义 | 开发者写在代码里 | 用户在 config 里加 |
| 何时确定 | 构建时 / 启动时 | 运行时 tools/list 拉来 |
| 变更成本 | 改代码、重发版 | 改 openclaw.json + mcp reload |
| 列表稳定性 | 一个版本里固定 | session 内可变(MCP listChanged) |
但严格说要分两层:
- Tool use(协议层) 没规定 tool 必须是静态的。应用完全可以每次请求 前动态生成 tool 列表(按用户权限、session 状态等)塞给模型。模型只看到 一个数组,不在乎是 hardcode 还是即时拼的。
- MCP 协议本身强制动态:server 自己声明
tools/list,host 必须 运行时去问——因为 tool 在另一个进程里,编译时无从知晓。
所以更准的说法:
- Tool use 允许静态也允许动态,大多数应用选静态(因为简单)
- MCP 强制动态(协议天然属性)
更本质的轴:所有权
真正的分水岭是 谁拥有 tool 实现:
- Native tool = 应用拥有 → 自然静态、自然由开发者掌控
- MCP tool = 第三方/用户拥有 → 必须动态发现、自然由用户配置
"编译时 vs 运行时"是这个所有权差异的自然后果,不是原因。
一个反例:plugin tool
OpenClaw 的 plugin tool(extensions/* 里那些)既不是 native 也不是
MCP,正好用来校准这两根轴:
- 像 native 一样:用 TypeScript 写,跟主进程同语言,编译时就在
- 像 MCP 一样:不在 core 里,由"插件作者"(广义的用户/第三方)拥有
它们走 src/plugin-sdk/*,跟 native tool 一样进 AnyAgentTool,跟 MCP tool
一起被打平。
这说明 "动态/静态" 和 "开发者/用户" 是两个独立轴,plugin tool 占了 "用户拥有 + 编译时确定" 那一格:
开发者拥有 用户/第三方拥有
┌──────────────────┬──────────────────┐
编译时确定 │ native tool │ plugin tool │
├──────────────────┼──────────────────┤
运行时发现 │ (动态生成的 │ MCP tool │
│ native tool) │ │
└──────────────────┴──────────────────┘
心智模型一句话
Tool use 是模型层的调用约定;native / plugin / MCP 是这个约定下 tool 实现的几种供应来源,一部分由应用开发者编译时固化,一部分由 用户或第三方运行时供应。MCP 不替代 tool use,它给 tool use 加了一个 "工具供应链":模型继续按 tool use 协议出请求,应用按 MCP 协议去远端 把工具 供应 回来,放到模型看到的 tool 列表里。
继续阅读
- mcp-client-internals.md — OpenClaw 作为 MCP client 的内部实现:server 发现、连接时机、catalog 暴露、缓存与 失效、安全/兼容细节