MCP 是什么?为什么 Function Call 之后还需要它
本文以 OpenClaw 开源源码为例
(基于 2026.6.2 版本),结合实际代码说明 MCP 的工作原理和定位。
涉及源码路径均可在仓库中直接查阅。
如果你看过上一篇 Function Call 是什么?LLM 如何学会使用工具,应该已经
知道 LLM 怎么"用工具"了:开发者把工具的 JSON Schema 塞进请求,模型决定
调哪个、传什么参数,代码负责真正执行。
那为什么 2024 年 Anthropic 又搞了一个叫 MCP(Model Context Protocol)
的东西?它跟 Function Call 是同一个东西吗?还是说 Function Call 不够用?
本文回答这两个问题。
从一个老问题说起
先看一个跟 AI 没关系的故事。
2016 年微软在做 VSCode 的时候,碰到一个头疼的事:
scss
编辑器(M 个) 语言(N 种)
┌────────────┐ ┌──────────┐
│ VSCode │ ↔↔↔↔ │ Python │
│ Vim │ ↔↔↔↔ │ Go │
│ Emacs │ ↔↔↔↔ │ TypeScript│
│ Sublime │ ↔↔↔↔ │ Rust │
│ JetBrains │ ↔↔↔↔ │ Java │
└────────────┘ └──────────┘
每个编辑器都要给每种语言写一遍"hover 显示类型"、"跳转到定义"、
"查找所有引用"、"实时报错"...M 个编辑器 × N 种语言 = M × N 份
重复实现。这显然不可持续。
微软的解法是 LSP(Language Server Protocol) :把语言能力抽出来
变成一个独立的服务器进程,编辑器跟服务器用一套标准协议通信:
javascript
┌────────┐ LSP ┌──────────────┐
│ VSCode │ ←──JSON-RPC→│ tsserver │ ← TypeScript
└────────┘ └──────────────┘
┌────────┐ ┌──────────────┐
│ Vim │ ←──────────│ rust-analyzer│ ← Rust
└────────┘ └──────────────┘
现在变成 M + N:每个编辑器实现一次 LSP client,每种语言实现一次
LSP server。rust-analyzer 一份能给所有支持 LSP 的编辑器用。
记住这个故事。
AI 工具集成的同样问题
现在回到 2024 年。当时已经有不少 AI 应用支持 Function Call,但每个
应用都在重复造轮子:
scss
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 是模型和应用之间的约定 :模型怎么"申请"调用一个
函数,应用怎么"执行"它再把结果回传。
sql
┌─────────────────────────────┐
│ LLM (Claude / GPT 等) │
│ 输出 tool_use 请求 │ ← Function Call 层
└──────────────┬──────────────┘
│
▼
┌─────────────────────────────┐
│ 你的应用 │
│ 接到请求,跑函数,回传结果 │
└─────────────────────────────┘
这一层只回答了"模型怎么表达调用意图"。它没规定函数本身是怎么
来的、是谁写的、怎么真正执行。
MCP 是什么层面
MCP 是应用和工具实现之间的协议 :这个函数从哪儿来,代码在哪个
进程里跑,参数怎么传过去。
sql
┌─────────────────────────────┐
│ 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 里:
perl
{
"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 列表里。
scss
// 简化版,真实代码在 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 长得完全一样:
css
[ { "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 是什么关系?
vbnet
Function Call:模型 ←→ 应用 之间怎么调函数
MCP: 应用 ←→ 工具 之间怎么提供函数
Function Call 解决"模型怎么用工具"。MCP 解决"工具从哪儿来、怎么跨应用
复用"。两者各管一层,组合起来才是完整的故事。
LSP 当年解决了编辑器 × 语言的 M × N 问题,让 rust-analyzer 这种
高质量语言服务器能服务所有编辑器。MCP 试图解决 AI 应用 × 工具的同样
问题,让一个 MCP server 能服务所有 AI 助手。
它能不能像 LSP 那样成为事实标准,还要看生态------但思路是清晰的:
把工具实现从单个应用里抽出来,变成可复用的独立组件。
想深入了解?
-
MCP 规范:modelcontextprotocol.io
-
看 OpenClaw 怎么实现 MCP client 的:
- 配置和发现机制
- 何时连接、catalog 怎么暴露给模型
- 缓存、失效、安全细节
这部分在 OpenClaw 仓库的
src/agents/agent-bundle-mcp-*.ts,以及
notes/mcp-client-internals.md。