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 的时候,碰到一个头疼的事:
编辑器(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 能读文件、列目录、搜文件。装一次:
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 里:
{
"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 列表里。
// 简化版,真实代码在 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 长得完全一样:
[ { "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 规范:modelcontextprotocol.io
-
看 OpenClaw 怎么实现 MCP client 的:
- 配置和发现机制
- 何时连接、catalog 怎么暴露给模型
- 缓存、失效、安全细节
这部分在 OpenClaw 仓库的
src/agents/agent-bundle-mcp-*.ts,
以及notes/mcp-client-internals.md。