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:
+370
@@ -0,0 +1,370 @@
|
||||
# MCP 是什么?为什么 Function Call 之后还需要它
|
||||
|
||||
> 本文以 [OpenClaw](https://github.com/openclaw/openclaw) 开源源码为例
|
||||
> (基于 2026.6.2 版本),结合实际代码说明 MCP 的工作原理和定位。
|
||||
> 涉及源码路径均可在仓库中直接查阅。
|
||||
|
||||
如果你看过上一篇 [Function Call 101](./function-call-101.md),应该已经
|
||||
知道 LLM 怎么"用工具"了:开发者把工具的 JSON Schema 塞进请求,模型决定
|
||||
调哪个、传什么参数,代码负责真正执行。
|
||||
|
||||
那为什么 2024 年 Anthropic 又搞了一个叫 **MCP(Model Context Protocol)**
|
||||
的东西?它跟 Function Call 是同一个东西吗?还是说 Function Call 不够用?
|
||||
|
||||
本文回答这两个问题。
|
||||
|
||||
---
|
||||
|
||||
## 从一个老问题说起
|
||||
|
||||
先看一个跟 AI 没关系的故事。
|
||||
|
||||
2016 年微软在做 VSCode 的时候,碰到一个头疼的事:
|
||||
|
||||
```
|
||||
编辑器(M 个) 语言(N 种)
|
||||
┌────────────┐ ┌──────────┐
|
||||
│ VSCode │ ↔↔↔↔ │ Python │
|
||||
│ Vim │ ↔↔↔↔ │ Go │
|
||||
│ Emacs │ ↔↔↔↔ │ TypeScript│
|
||||
│ Sublime │ ↔↔↔↔ │ Rust │
|
||||
│ JetBrains │ ↔↔↔↔ │ Java │
|
||||
└────────────┘ └──────────┘
|
||||
```
|
||||
|
||||
每个编辑器都要给每种语言写一遍"hover 显示类型"、"跳转到定义"、
|
||||
"查找所有引用"、"实时报错"...M 个编辑器 × N 种语言 = M × N 份
|
||||
重复实现。这显然不可持续。
|
||||
|
||||
微软的解法是 **LSP(Language Server Protocol)**:把语言能力抽出来
|
||||
变成一个独立的服务器进程,编辑器跟服务器用一套标准协议通信:
|
||||
|
||||
```
|
||||
┌────────┐ LSP ┌──────────────┐
|
||||
│ VSCode │ ←──JSON-RPC→│ tsserver │ ← TypeScript
|
||||
└────────┘ └──────────────┘
|
||||
┌────────┐ ┌──────────────┐
|
||||
│ Vim │ ←──────────│ rust-analyzer│ ← Rust
|
||||
└────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
现在变成 M + N:每个编辑器实现一次 LSP client,每种语言实现一次
|
||||
LSP server。**`rust-analyzer` 一份能给所有支持 LSP 的编辑器用。**
|
||||
|
||||
记住这个故事。
|
||||
|
||||
---
|
||||
|
||||
## AI 工具集成的同样问题
|
||||
|
||||
现在回到 2024 年。当时已经有不少 AI 应用支持 Function Call,但每个
|
||||
应用都在重复造轮子:
|
||||
|
||||
```
|
||||
AI 应用(M 个) 工具(N 种)
|
||||
┌────────────────┐ ┌──────────────┐
|
||||
│ Claude Desktop │ ↔↔↔│ 读文件 │
|
||||
│ ChatGPT App │ ↔↔↔│ 查数据库 │
|
||||
│ Cursor │ ↔↔↔│ 调 GitHub API │
|
||||
│ Codex │ ↔↔↔│ 操作 Notion │
|
||||
│ 你的 agent │ ↔↔↔│ 控制浏览器 │
|
||||
└────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
每个 AI 应用都要给每种工具写一遍 Function Call 的接入代码:
|
||||
读文件、调 API、解析响应、错误处理。M × N 又来了。
|
||||
|
||||
你想要的是这样:把"读文件"这个能力写一次,Claude Desktop、Cursor、
|
||||
你的 agent 都能用。
|
||||
|
||||
Anthropic 给的答案就是 MCP——**它对 AI 工具集成做的事,跟 LSP 对
|
||||
编辑器语言支持做的事是一模一样的**。
|
||||
|
||||
---
|
||||
|
||||
## MCP 是什么
|
||||
|
||||
一句话:**MCP 是一个开放协议,让任何 AI 应用都能用一套标准的方式
|
||||
接入任何外部工具。**
|
||||
|
||||
具体来说,MCP 定义了两个角色之间怎么通信:
|
||||
|
||||
- **Host**(主机):跑 LLM 的那一侧——Claude Desktop、Cursor、你写的
|
||||
agent 框架
|
||||
- **Server**(服务器):提供工具的那一侧——可以是本地子进程
|
||||
(读文件、控浏览器),也可以是远端 HTTP 服务(Notion 集成、
|
||||
GitHub 集成)
|
||||
|
||||
通信用 JSON-RPC 2.0,跑在 stdio 或 HTTP 之上。MCP server 暴露三类能力:
|
||||
|
||||
- **tools**:模型可以调的函数
|
||||
- **resources**:可读的数据源
|
||||
- **prompts**:可被引用的预制 prompt 模板
|
||||
|
||||
我们今天主要讲 tools。
|
||||
|
||||
跟 LSP 长得很像对吧?**stdio + JSON-RPC + 服务器自报能力**——某种意义上
|
||||
MCP 就是 LSP 思路在 AI 时代的延续。
|
||||
|
||||
---
|
||||
|
||||
## 但是……Function Call 不是已经能干这件事了吗?
|
||||
|
||||
这是新手最容易卡住的地方。我们仔细看看。
|
||||
|
||||
### Function Call 是什么层面
|
||||
|
||||
Function Call 是**模型和应用之间的约定**:模型怎么"申请"调用一个
|
||||
函数,应用怎么"执行"它再把结果回传。
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ LLM (Claude / GPT 等) │
|
||||
│ 输出 tool_use 请求 │ ← Function Call 层
|
||||
└──────────────┬──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 你的应用 │
|
||||
│ 接到请求,跑函数,回传结果 │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
这一层只回答了"模型怎么表达调用意图"。**它没规定函数本身是怎么
|
||||
来的、是谁写的、怎么真正执行**。
|
||||
|
||||
### MCP 是什么层面
|
||||
|
||||
MCP 是**应用和工具实现之间的协议**:这个函数从哪儿来,代码在哪个
|
||||
进程里跑,参数怎么传过去。
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ LLM │
|
||||
│ 输出 tool_use 请求 │ ← Function Call 层(没变)
|
||||
└──────────────┬──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 你的应用 │
|
||||
│ - 收 tool_use │
|
||||
│ - 转成 MCP 调用 │
|
||||
└──────────────┬──────────────┘
|
||||
│ JSON-RPC over stdio/HTTP
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ MCP server (子进程/远端) │ ← MCP 层
|
||||
│ 跑函数,回结果 │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
注意:**模型完全不知道 MCP 的存在**。它看到的还是一个普通的 tool 定义,
|
||||
还是按 Function Call 的方式输出 `tool_use`。MCP 是中间那一层应用代码
|
||||
的事——它把 tool 的"实现"从应用进程里挪到了独立的 MCP server 里。
|
||||
|
||||
### 一个类比
|
||||
|
||||
- Function Call ≈ "我要打个电话"——这是个抽象动作
|
||||
- MCP ≈ "电话怎么拨通对方公司的总机,问到分机号,再接到具体的人"
|
||||
——这是个具体协议
|
||||
|
||||
或者:
|
||||
|
||||
- Function Call ≈ 函数调用这个概念本身
|
||||
- MCP ≈ gRPC / OpenAPI——让函数能跨进程跨语言被发现和调用的具体协议
|
||||
|
||||
**MCP 不替代 Function Call,它给 Function Call 加了一个"工具供应链"。**
|
||||
|
||||
---
|
||||
|
||||
## MCP 实际带来了什么
|
||||
|
||||
讲了这么多概念,看看实际效果。
|
||||
|
||||
### 1. 工具一次写好,所有 AI 应用都能用
|
||||
|
||||
`@modelcontextprotocol/server-filesystem` 是社区的一个 MCP server,
|
||||
让 AI 能读文件、列目录、搜文件。装一次:
|
||||
|
||||
```bash
|
||||
npx @modelcontextprotocol/server-filesystem ~/Documents
|
||||
```
|
||||
|
||||
然后 Claude Desktop、Cursor、OpenClaw 都能直接用上它,不用各自实现
|
||||
一遍文件操作工具。**这就是 M + N 而不是 M × N。**
|
||||
|
||||
### 2. 工具的提供者跟 AI 应用作者解耦
|
||||
|
||||
Notion 想给 AI 助手提供集成?以前要等每家 AI 应用主动接入,或者发
|
||||
SDK 给开发者集成。现在 Notion 自己跑一个 MCP server,任何支持 MCP
|
||||
的应用都能用。
|
||||
|
||||
### 3. 工具能跨语言、跨进程
|
||||
|
||||
MCP server 可以是 Python、Go、Rust、任何能讲 JSON-RPC 的语言写的。
|
||||
你的 AI 应用是 Node.js,但工具实现可以是 Python——只要双方说 MCP 就行。
|
||||
|
||||
### 4. 故障隔离
|
||||
|
||||
MCP server 跑在独立进程里(或者远端),一个 server 崩了不会拖死整个
|
||||
AI 应用。可以单独重启、单独限流、单独配置超时。
|
||||
|
||||
---
|
||||
|
||||
## 看看 OpenClaw 怎么做的
|
||||
|
||||
OpenClaw(一个开源 AI agent 框架)的代码可以当作 MCP 的具体落地示例。
|
||||
|
||||
### MCP server 怎么"加进来"
|
||||
|
||||
OpenClaw 不去网络扫描发现 MCP server,**全靠声明式 config**。两个来源:
|
||||
|
||||
**1. 用户写在 `~/.openclaw/openclaw.json` 里:**
|
||||
|
||||
```json
|
||||
{
|
||||
"mcp": {
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-filesystem", "/home/user"]
|
||||
},
|
||||
"github": {
|
||||
"url": "https://mcp.github.com",
|
||||
"transport": "streamable-http",
|
||||
"auth": "oauth"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. 插件作者预置:** 装一个 OpenClaw plugin 时,它可能自带几个
|
||||
MCP server 配置(`src/plugins/bundle-mcp.ts`)。
|
||||
|
||||
两个来源在 `src/agents/bundle-mcp-config.ts` 里合并,**用户 config
|
||||
能覆盖 plugin 默认**。
|
||||
|
||||
### 什么时候真的连接 MCP server?
|
||||
|
||||
这是个有意思的设计选择。OpenClaw 的策略:**进程启动时不连接、每次
|
||||
模型轮次开始时连接**。
|
||||
|
||||
为什么?因为模型必须在请求里就看到全部 tool 才能决定调哪个,所以
|
||||
连接不能拖到模型真的调用某个 tool 时再做(那时候 tool 列表都没
|
||||
组装好)。但又没必要在 OpenClaw 启动时就把所有 MCP server 都拉起来
|
||||
(可能配了 20 个,这次对话其实只用 2 个)。
|
||||
|
||||
折衷点是 **agent 一轮模型请求开始时**:这时候必须把所有 MCP server
|
||||
都 spawn、`tools/list`、拿到工具目录,然后把工具塞进发给模型的
|
||||
tool 列表里。
|
||||
|
||||
```ts
|
||||
// 简化版,真实代码在 src/agents/embedded-agent-runner/run/attempt.ts
|
||||
const mcpRuntime = await materializeBundleMcpToolsForRun({...});
|
||||
// 这里 MCP server 已经 spawn 完了,tool 也都拿到了
|
||||
|
||||
const tools = [...nativeTools, ...mcpRuntime.tools];
|
||||
// 发给模型
|
||||
sendRequest({ tools, ... });
|
||||
```
|
||||
|
||||
### 模型看到的是什么
|
||||
|
||||
**一个扁平的 tool 数组,跟 native tool 长得完全一样:**
|
||||
|
||||
```json
|
||||
[
|
||||
{ "name": "Read", "description": "...", "input_schema": {...} },
|
||||
{ "name": "Bash", "description": "...", "input_schema": {...} },
|
||||
{ "name": "filesystem__read_file", "description": "...", "input_schema": {...} },
|
||||
{ "name": "github__list_issues", "description": "...", "input_schema": {...} }
|
||||
]
|
||||
```
|
||||
|
||||
注意 `filesystem__read_file` 这种 `serverName__toolName` 的命名是为了
|
||||
防止重名。模型完全不知道哪些来自 MCP、哪些是 OpenClaw 写死的;它只
|
||||
根据 `description` 和 schema 做语义匹配,挑一个最合适的。
|
||||
|
||||
### 模型调用时发生什么
|
||||
|
||||
模型返回 `tool_use: filesystem__read_file`,OpenClaw 在 tool 列表里
|
||||
找到对应条目,它的 `execute` 函数会调 `client.callTool("read_file", input)`,
|
||||
通过早就建立好的 stdio 连接发给 MCP server,server 跑完返回结果,
|
||||
OpenClaw 再把结果回传给模型。
|
||||
|
||||
整个过程模型一无所知——它只觉得自己"调了个工具,得到了结果"。
|
||||
|
||||
---
|
||||
|
||||
## 关键澄清:几个常见误解
|
||||
|
||||
### 误解 1:"MCP 是给模型用的协议"
|
||||
|
||||
**不是。MCP 是给应用用的协议。** 模型那一侧永远是 Function Call,
|
||||
看到的就是普通 tool 定义。MCP 是应用怎么"获取这些 tool 定义、怎么
|
||||
真正执行 tool"的事。
|
||||
|
||||
### 误解 2:"用了 MCP 就不用 Function Call 了"
|
||||
|
||||
**Function Call 永远在用。** MCP server 提供的 tool,最终还是要包装
|
||||
成 Function Call 的格式发给模型。两者不是 A 替代 B,而是 A 之上又加
|
||||
了一层 B。
|
||||
|
||||
### 误解 3:"native tool 落后了,MCP 才是未来"
|
||||
|
||||
**不一定。** 跟应用深度耦合的工具(比如 OpenClaw 的 `Read`、`Bash`)
|
||||
做成 native tool 启动快、故障少、能直接用 host 内部能力;MCP 的优势在
|
||||
**跨应用复用** 和 **可独立分发**。两者各有适用场景。
|
||||
|
||||
OpenClaw 自己就同时有 native tool、plugin tool、MCP tool,模型看见
|
||||
的是同一个扁平列表,不区分。
|
||||
|
||||
---
|
||||
|
||||
## 那 MCP 现在能用来干什么
|
||||
|
||||
一些实际的应用方向:
|
||||
|
||||
- **本地工具**:文件系统、Git、Docker、SQLite——`@modelcontextprotocol/server-*`
|
||||
系列已经有不少现成的
|
||||
- **远端 SaaS 集成**:Notion、Linear、Slack、GitHub——越来越多 SaaS
|
||||
在做官方 MCP server
|
||||
- **企业内部工具**:把内部数据库查询、运维脚本包装成 MCP server,
|
||||
团队所有 AI 助手都能用
|
||||
- **跨工具协作**:同一个 agent 同时接文件系统 + GitHub + Notion 的 MCP server,
|
||||
可以读本地代码、查 issue、写文档
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
回到开头的问题:**MCP 跟 Function Call 是什么关系?**
|
||||
|
||||
```
|
||||
Function Call:模型 ←→ 应用 之间怎么调函数
|
||||
MCP: 应用 ←→ 工具 之间怎么提供函数
|
||||
```
|
||||
|
||||
Function Call 解决"模型怎么用工具"。MCP 解决"工具从哪儿来、怎么跨应用
|
||||
复用"。两者各管一层,组合起来才是完整的故事。
|
||||
|
||||
LSP 当年解决了编辑器 × 语言的 M × N 问题,让 `rust-analyzer` 这种
|
||||
高质量语言服务器能服务所有编辑器。MCP 试图解决 AI 应用 × 工具的同样
|
||||
问题,让一个 MCP server 能服务所有 AI 助手。
|
||||
|
||||
它能不能像 LSP 那样成为事实标准,还要看生态——但思路是清晰的:
|
||||
**把工具实现从单个应用里抽出来,变成可复用的独立组件**。
|
||||
|
||||
---
|
||||
|
||||
## 想深入了解?
|
||||
|
||||
- MCP 规范:<https://modelcontextprotocol.io>
|
||||
- 看 OpenClaw 怎么实现 MCP client 的:
|
||||
- 配置和发现机制
|
||||
- 何时连接、catalog 怎么暴露给模型
|
||||
- 缓存、失效、安全细节
|
||||
|
||||
这部分在 OpenClaw 仓库的 `src/agents/agent-bundle-mcp-*.ts`,
|
||||
以及 `notes/mcp-client-internals.md`。
|
||||
Reference in New Issue
Block a user