从 Function Calling 到 MCP:Agent 工具调用的协议演进与架构实践
专栏第6篇:第五篇我们讲了工具调用的四层防御机制,以及 Function Calling 如何让 LLM 输出结构化的工具调用意图。但 Function Calling 有一个局限:它是模型厂商的私有协议——OpenAI 的格式、Anthropic 的格式、Google 的格式各不相同。如果我想让同一个工具被不同模型调用,难道要为每个模型写一套适配代码?今天我们要聊的 MCP(Model Context Protocol),正是为了解决这个"N 个模型 × M 个工具"的碎片化问题。
目录
- 一、Function Calling 的局限性:N 个模型 × M 个工具
- 二、MCP 是什么?
- 三、MCP 的三层架构:Host / Client / Server
- 四、MCP 与 Function Calling 的本质区别
- 五、传输类型:stdio vs HTTP
- 六、开发一个 MCP Server
- 七、MCP 在 Agent 生态中的定位
- 八、总结
一、Function Calling 的局限性:N 个模型 × M 个工具
第五篇我们讲了 Function Calling:LLM 根据工具描述,输出结构化的 JSON,表示"需要调用哪个工具、传什么参数"。这套机制在单个模型内部工作得很好,但当你需要对接多个模型时,问题就来了。
1.1 每个模型的 Function Calling 格式都不一样
OpenAI 的格式:
{"name": "get_weather", "arguments": {"city": "北京"}}
Anthropic 的格式:
{"tool": "get_weather", "input": {"city": "北京"}}
Google 的格式:
{"function": {"name": "get_weather"}, "parameters": {"city": "北京"}}
同样的工具、同样的参数,三个模型输出三种不同的 JSON 结构。这意味着:
- 每对接一个新模型,就要重写一套解析逻辑
- 工具开发者要为每个模型维护不同的描述格式
- 切换模型时,工具代码要跟着改
1.2 工具的"碎片化"
假设你开发了一个"查天气"工具:
给 OpenAI 用 → 按 OpenAI 的 Schema 格式写描述
给 Claude 用 → 按 Anthropic 的格式写描述
给本地模型用 → 可能根本不支持 Function Calling,得用 ReAct 文本解析
结果:同一个工具,写了 3 套不同的"说明书"
这就是 Function Calling 的核心痛点:它是模型厂商的私有协议,不是跨模型的通用标准。
二、MCP 是什么?
MCP(Model Context Protocol) 是 Anthropic 于 2024 年底推出的开放协议,目标是标准化 LLM 与外部系统(工具、数据、提示词)的交互方式。
💡 一句话理解:Function Calling 是"某个模型怎么表达调用意图",MCP 是"外部系统怎么向模型暴露能力"的通用标准。
2.1 为什么叫"Model Context Protocol"?
拆解一下这个名字:
- Model(模型):指 LLM,是协议的"消费者"
- Context(上下文):指模型需要的外部信息——不只是工具,还包括数据、提示词等
- Protocol(协议):标准化的交互规则
"Context"这个词是关键。MCP 的原始设计意图不只是"工具调用协议",而是要让外部系统能向模型提供各种形式的"上下文":
| MCP 能力类型 | 说明 | 示例 |
|---|---|---|
| Tools(工具) | 可执行的函数,模型可以调用 | 天气查询、计算器、文件操作 |
| Resources(资源) | 只读的数据,模型可以读取 | 数据库记录、知识库内容、文件内容 |
| Prompts(提示词) | 预定义的 Prompt 模板 | 代码审查模板、翻译模板、总结模板 |
也就是说,MCP 最初想解决的是一个更宏大的问题:如何让外部系统(数据源、工具集、模板库)以标准化的方式,成为 LLM 的"上下文提供者"。
2.2 为什么现在主要被当作"工具调用协议"?
虽然 MCP 设计了三类能力,但目前生态中90% 的使用场景都是 Tools。原因很简单:
- Tools 是最刚性的需求:Agent 必须能执行操作,否则只是聊天机器人
- Resources 部分被 RAG 替代:很多数据检索场景直接用向量数据库 + RAG 实现
- Prompts 部分被系统提示词替代:预定义模板通常直接写在代码里
💡 现状:MCP 的协议框架支持三种能力,但生态发展不均衡,Tools 一枝独秀。不过随着生态成熟,Resources 和 Prompts 的使用也在增长。
2.3 MCP 的核心思想:解耦
- 工具开发者:只按 MCP 标准写一次能力描述和实现
- 模型/Agent 开发者:只按 MCP 标准对接一次协议
- 结果:任何支持 MCP 的模型,都能使用任何支持 MCP 的外部系统
三、MCP 的三层架构:Host / Client / Server
MCP 采用三层架构,但需要先明确一点:这三者是逻辑角色,不是三个独立的物理进程。一个应用可以同时承担多个角色。
💡 关键理解:Client 不是独立于 Host 的第三方应用,而是Host 内部负责连接 Server 的模块。就像浏览器(Host)内部有 HTTP 客户端(Client)用来连接 Web 服务器。
3.1 Host(宿主应用)
Host 是最终用户直接交互的终端应用,比如:
- 一个 Agent 平台(如 Claude Desktop、Cursor、openclaw、QoderWork等)
- 一个用 LangChain 搭建的聊天机器人 Web 服务
- 一个 IDE 插件或桌面客户端
💡 区分框架和 Host:LangChain、LlamaIndex 是框架(用来构建 Agent 的工具库),不是 Host。用 LangChain 写出来的那个"能跟用户聊天的应用"才是 Host。
Host 负责:
- 提供用户交互界面
- 管理内部 MCP Client 的生命周期
- 协调不同 Server 之间的工具调用
- 向用户展示最终答案
Host 本身也包含 Client:一个 Agent 平台作为 Host,其内部必然有 Client 模块来连接外部 Server。两者是"整体与部分"的关系,不是并列关系。
3.2 Client(MCP 客户端)
Client 是 Host 内部负责与 Server 通信的模块,每个 Client 实例对应一个 Server。
💡 关系澄清:一个 Host 内部可以有多个 Client(连接不同的 Server),一个 Server 也可以被多个 Client 连接(比如一个公共的天气查询 Server 被多个用户的 Agent 平台同时调用)。标准实现是一个 Client 实例只连接一个 Server。
Client 负责:
- 与 Server 建立连接(stdio 或 HTTP)
- 将 Host 的请求转换为 MCP 标准消息
- 将 Server 的响应返回给 Host
# Client 的核心职责:协议转换
class MCPClient:
def __init__(self, server_command: str):
# 启动 Server 进程,建立连接
self.server = subprocess.Popen(server_command, ...)
def list_tools(self):
# 向 Server 请求工具列表
return self.send_request("tools/list")
def call_tool(self, tool_name: str, arguments: dict):
# 调用 Server 上的某个工具
return self.send_request("tools/call", {"name": tool_name, "arguments": arguments})
3.3 Server(MCP 服务端)
Server 是实际提供能力的"供应商",每个 Server 专注一类能力:
| Server 类型 | 提供的能力 | 示例 |
|---|---|---|
| 工具 Server | 可执行的函数 | 天气查询、计算器、文件操作 |
| 资源 Server | 只读的数据访问 | 数据库查询、知识库检索 |
| 提示词 Server | 预定义的 Prompt 模板 | 代码审查模板、翻译模板 |
Server 的核心职责:
- 按 MCP 标准暴露自己的能力列表
- 接收 Client 的请求并执行
- 返回标准化的响应
四、MCP 与 Function Calling 的本质区别
⚠️ 限定范围:以下对比仅针对**工具调用(Tools)**这一交集领域。MCP 还支持 Resources(资源读取)和 Prompts(提示词模板),Function Calling 则不涉及这两类能力。
| 维度 | Function Calling | MCP |
|---|---|---|
| 协议层级 | 模型输出层(LLM 怎么表达意图) | 工具描述层(工具怎么定义自己) |
| 标准归属 | 各模型厂商私有 | Anthropic 主导的开放标准 |
| 工具位置 | 工具代码在 Agent 内部 | 工具代码在独立的 Server 进程中 |
| 工具发现 | 启动时静态注册 | 运行时动态发现(Server 随时可增删) |
| 跨模型 | 每模型一套适配 | 一次适配,多模型通用 |
| 隔离性 | 工具和 Agent 同进程 | 工具和 Agent 隔离(Server 独立运行) |
4.1 最关键的区别:协议层级
Function Calling 解决的是"LLM 怎么告诉系统它需要调用工具"——这是模型输出格式的问题。
MCP 解决的是"工具怎么描述自己、系统怎么发现工具、怎么调用工具"——这是工具接口标准的问题。
两者不是替代关系,而是互补关系:
实际流程:
- LLM 通过 Function Calling 输出"需要调用天气工具"
- Agent 框架(Host)把这个意图交给 MCP Client
- MCP Client 按 MCP 协议调用对应的 MCP Server
- MCP Server 执行实际的天气查询
- 结果返回,LLM 生成最终回答
4.2 多模型兼容性:谁来处理 Function Calling 的差异?
不同厂商的 Function Calling 输出格式确实不同(字段名、嵌套结构有差异),但这个差异不需要 Agent 平台自己处理——Agent 框架(如 LangChain)已经内部屏蔽了。
Function Calling 的结果不需要转换成 MCP 格式。因为两者在数据层面是一致的(都是工具名 + 参数),Agent 框架解析出工具名和参数后,直接传给 MCP Client 的 call_tool 方法即可。
OpenAI 输出 → LangChain 解析 → 提取{工具名, 参数} → 直接调用 MCP Server
Claude 输出 → LangChain 解析 → 提取{工具名, 参数} → 直接调用 MCP Server
Agent 平台只需要接入支持多模型的框架,框架会自动处理 Function Calling 的解析差异,然后直接调用 MCP Server。MCP 协议本身不规定 Function Calling 的格式,只负责工具和 Agent 之间的标准化通信。
4.3 为什么 MCP 需要独立的 Server 进程?
传统方式(无MCP):
Agent 代码 ──直接调用──→ 天气API
Agent 代码 ──直接调用──→ 数据库查询
Agent 代码 ──直接调用──→ 文件操作
问题:工具和 Agent 强耦合,代码混在一起
MCP 方式:
Agent 代码 ──MCP协议──→ 天气Server(独立进程)
Agent 代码 ──MCP协议──→ 数据库Server(独立进程)
Agent 代码 ──MCP协议──→ 文件Server(独立进程)
优势:
1. 工具和 Agent 解耦,可以独立开发、独立部署
2. 工具崩溃不影响 Agent
3. 不同语言实现的工具可以共存(Python Agent + Node.js Server)
4.4 工具发现:LLM 怎么知道有哪些工具可用?
前面讲了 MCP 架构,但有一个关键问题没提:LLM 是怎么知道有哪些工具可以调用的?
答案是:Agent 平台通过 MCP 协议的 list_tools() 动态获取,然后注入到 LLM 的上下文(System Prompt)中。
MCP Server 启动
↓
Agent 平台连接 Server,调用 list_tools()
↓
获取工具列表:[{name, description, parameters}, ...]
↓
把这些信息注入 System Prompt
↓
LLM 根据工具描述做决策 → 输出 Function Calling
Skill 文件的作用:
在实际项目中,你可能会看到类似这样的配置:
## 工具列表
- 工具名: weather-query
- 描述: 查询指定城市的天气
- 参数: {city: string}
但要注意:这个文件是给人看的开发文档,不是 LLM 获取工具信息的来源。 LLM 看到的工具信息来自 list_tools() 的实时返回,不是 Skill 文件里的静态配置。
对 LLM 完全透明:
LLM 不知道也不关心工具是 MCP 的还是本地的、是 Python 的还是 Node.js 的。它看到的始终是统一格式的工具描述:
你有一个工具叫 weather-query,描述是 xxx,参数是 yyy。
你有一个工具叫 file-read,描述是 aaa,参数是 bbb。
决策逻辑完全一样:看描述 → 判断要不要用 → 输出 Function Calling。工具的实现细节对 LLM 是透明的。
五、传输类型:stdio vs HTTP
MCP 支持两种传输方式,适用于不同场景:
5.1 stdio(标准输入输出)
原理:Host 启动 Server 作为子进程,通过标准输入输出进行 JSON-RPC 通信。
Host 进程
└── 启动 Server 子进程(如 python weather_server.py)
└── 通过 stdin 发送请求
└── 通过 stdout 接收响应
适用场景:
- 本地开发、调试
- Server 和 Host 在同一台机器上
- 需要进程隔离(Server 崩溃不影响 Host)
- Agent 平台从 MCP 市场/广场安装的大部分插件(平台读取配置中的命令,在本地启动 Server 子进程)
优点:
- 简单,不需要网络配置
- 天然进程隔离
- 适合本地工具(文件操作、本地命令)
缺点:
- 只能本地通信
- 每个 Server 占用一个进程
5.2 HTTP(Streamable HTTP)
原理:Server 作为独立的服务运行,Host 通过 HTTP 请求与其通信。
Host 进程 ──HTTP请求──→ MCP Server(独立服务)
可以是本地服务或远程服务
消息格式:HTTP 传输可以是普通的请求-响应模式(一次 POST 发请求,一次返回完整 JSON),也可以使用 SSE(Server-Sent Events) 或 JSON Stream 进行流式通信(服务器实时推送增量数据)。具体用哪种取决于 Server 的实现和场景需求。
适用场景:
- Server 部署在远程服务器上
- 多个 Host 共享同一个 Server
- 需要高可用、负载均衡
优点:
- 支持远程部署
- 多个 Host 可以共享 Server
- 可以使用现有的 HTTP 基础设施(网关、鉴权等)
缺点:
- 需要网络配置
- 增加了延迟
- 需要处理连接管理和错误恢复
5.3 如何选择?
| 场景 | 推荐传输 | 原因 |
|---|---|---|
| 本地开发调试 | stdio | 简单,即开即用 |
| 本地文件/命令工具 | stdio | 本地资源访问,不需要网络 |
| 团队共享工具 | HTTP | 统一部署,多 Host 共享 |
| 云端服务 | HTTP | 远程访问,弹性伸缩 |
| 高安全要求 | stdio | 进程隔离,不暴露网络端口 |
六、开发一个 MCP Server
开发 MCP Server 的核心是:按 MCP 标准实现几个固定的接口。
6.1 Server 必须实现的接口
from mcp.server import Server
from mcp.types import Tool, TextContent
# 1. 创建 Server
server = Server("weather-server")
# 2. 注册工具列表接口(让 Client 知道你有什么工具)
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="get_weather",
description="查询指定城市的天气信息",
inputSchema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
)
]
# 3. 注册工具调用接口(实际执行工具)
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "get_weather":
city = arguments["city"]
result = await query_weather_api(city) # 实际的天气查询逻辑
return [TextContent(type="text", text=result)]
raise ValueError(f"未知工具: {name}")
# 4. 启动 Server(stdio 模式)
if __name__ == "__main__":
server.run(transport="stdio")
6.2 Server 的"配置文件"
除了代码,MCP Server 还需要一个描述文件(通常是 package.json 或 mcp.json),告诉 Host 怎么连接它。
stdio 模式的配置(Host 在本地启动 Server 子进程):
{
"name": "weather-server",
"version": "1.0.0",
"command": "python",
"args": ["weather_server.py"],
"env": {
"WEATHER_API_KEY": "your-api-key"
}
}
HTTP 模式的配置(Host 连接一个已运行的远程/本地服务):
{
"name": "weather-server",
"version": "1.0.0",
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer token"
}
}
如何区分两种配置?
| 特征 | stdio 模式 | HTTP 模式 |
|---|---|---|
| 配置中的关键字段 | command + args |
url |
| Server 由谁启动 | Host 启动子进程 | 已独立运行,Host 只负责连接 |
| 适用场景 | 本地工具、MCP 市场插件 | 远程服务、团队共享 Server |
Host 读取配置后,根据有无 command 字段判断用 stdio 还是 HTTP 方式连接。
6.3 工具的发现与调用流程
七、MCP 在 Agent 生态中的定位
把 MCP 放在整个 Agent 技术栈中看,它的位置如下:
各层职责:
| 层级 | 代表技术 | 职责 |
|---|---|---|
| 推理层 | ReAct、Reflexion | 决定做什么、怎么做 |
| 意图表达层 | Function Calling | LLM 表达"需要调用什么工具" |
| 协议层 | MCP | 标准化工具的注册、发现、调用 |
| 执行层 | MCP Server | 实际执行工具逻辑 |
| 资源层 | API、数据库、文件 | 最终的数据和操作对象 |
MCP 的价值:
- 对工具开发者:写一次,到处用(任何支持 MCP 的 Agent 都能调用)
- 对 Agent 开发者:接一次,用所有工具(不需要为每个工具写适配代码)
- 对用户:Agent 的能力范围随 MCP Server 生态扩大而自动扩展
八、总结
本文从 Function Calling 的局限性出发,梳理了:
- 碎片化问题:N 个模型 × M 个工具,Function Calling 格式各不相同
- MCP 的定位:标准化的工具接口协议,解耦工具开发与 Agent 开发
- 三层架构:Host(应用)→ Client(Host 内部连接 Server 的模块)→ Server(能力提供)
- 与 Function Calling 的关系:互补而非替代——Function Calling 是 LLM 的"意图表达",MCP 是工具的"接口标准";多模型差异由 Agent 框架自动处理;LLM 对工具来源完全透明,不关心工具是 MCP 的还是本地的,需要调用时只输出 Function Calling
- 传输类型:stdio(本地、简单、隔离)vs HTTP(远程、共享、可扩展)
- Server 开发:实现
list_tools和call_tool两个核心接口 - 生态价值:工具一次开发,多模型/多 Agent 复用
参考资源:
- Anthropic MCP Specification(官方协议文档)
- MCP SDK Documentation(Python/TypeScript SDK)
- 《Model Context Protocol: A Standard for Connecting AI Assistants to Data》(Anthropic, 2024)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)