从 Function Calling 到 MCP:AI Agent 工具调用架构的演进与迁移指南
Function Calling 为什么不够用了?MCP 解决了什么问题?如何把已有的工具调用代码迁移到 MCP 架构
AinoCode 编辑部
从 Function Calling 到 MCP:AI Agent 工具调用架构的演进与迁移指南
2024 年,Function Calling 横空出世。OpenAI 在 GPT-4 的 API 中引入了结构化函数调用能力,开发者只需定义 JSON Schema,模型就能自动决定调用哪个函数、传入什么参数。一时间,所有 AI 应用都加上了”工具调用”功能。
但到了 2026 年,情况变了。
Function Calling 的痛点越来越明显:每个 LLM 提供商的工具调用格式不兼容;每加一个新工具就要改 Agent 代码;多个 Agent 之间无法共享工具定义;工具调用时的 token 开销随工具数量线性增长。
MCP(Model Context Protocol)就是为了解决这些问题而生的。到 2026 年初,MCP SDK 的月下载量已经超过 9700 万次,公开的 MCP Server 超过 15,000 个。Anthropic 在 2025 年 12 月把 MCP 捐赠给了 Linux Foundation 旗下的 Agentic AI Foundation——它已经不再是一个公司的协议,而是整个行业的基础设施。
这篇文章回答三个问题:
- Function Calling 到底哪里不够用了?
- MCP 的架构设计解决了什么问题?
- 如何把已有的 Function Calling 代码迁移到 MCP?
一、Function Calling 的局限性
1.1 工具定义与 LLM 紧耦合
用 Function Calling 时,你的工具定义直接写在应用代码里,然后通过 API 传给 LLM:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools # 每次调用都要带
)
三个问题:
- 每次 API 调用都要携带完整的工具定义。10 个工具意味着每次请求多消耗 2-3K tokens。按每月 10 万次调用算,这是实打实的成本。
- 换模型就要改代码。OpenAI 的 tools 格式、Anthropic 的 tool_use 格式、Google 的 function_declarations 格式各不相同。想做多模型支持,就要维护多套适配层。
- 工具定义无法复用。A 团队的搜索工具、B 团队的搜索工具,定义几乎一样,但代码各写各的。
1.2 工具数量膨胀后的性能问题
随着 Agent 能力增强,工具数量从最初的 3-5 个膨胀到 20-30 个。这时候 Function Calling 的问题集中爆发:
- 首 token 延迟增加:模型需要解析所有工具定义才能决定调用哪个,工具越多决策越慢
- 幻觉率上升:当工具列表过长时,模型更可能调用不存在的工具或传入错误参数
- 维护成本指数增长:每加一个工具,所有使用这个工具的 Agent 都要更新工具定义
1.3 多 Agent 场景下的工具共享
在单 Agent 架构下,工具定义写在代码里没问题。但当你有多个 Agent(研究 Agent、编码 Agent、部署 Agent)都需要访问同一组工具时,问题就来了:
- 每个 Agent 都要独立维护工具定义
- 工具更新时需要同步所有 Agent
- 无法动态发现其他 Agent 的工具
二、MCP 的架构设计
MCP 的核心思路是把工具从 Agent 代码中解耦出来,变成独立的服务。
2.1 三层架构
┌──────────────────────┐
│ Host(宿主应用) │ Claude Desktop / Cursor / 你的 Agent
│ ┌──────────────┐ │
│ │ MCP Client │ │ 协议客户端,管理连接和会话
│ └──────┬───────┘ │
└──────────┼───────────┘
│ JSON-RPC over stdio/HTTP
│
┌──────────┼───────────┐
│ MCP Server │ 独立的工具服务
│ ┌──────────────┐ │
│ │ Tools │ │ 工具定义和执行逻辑
│ │ Resources │ │ 可读取的数据源
│ │ Prompts │ │ 预定义的 prompt 模板
│ └──────────────┘ │
└──────────────────────┘
关键设计决策:
Host 是运行 LLM 的应用(Claude Desktop、Cursor、或你自己的 Agent 运行时)。它内嵌一个 MCP Client,负责跟 MCP Server 通信。
MCP Server 是独立的进程,暴露一组工具(Tools)、数据源(Resources)和 prompt 模板(Prompts)。它不知道也不关心 Host 用的是什么 LLM。
通信协议 是 JSON-RPC,传输层支持 stdio(本地进程)和 HTTP(远程服务)。这意味着同一个 MCP Server 既可以本地调用,也可以部署到远端。
2.2 工具发现(Tool Discovery)
MCP Server 启动时,会向 Host 广播自己能提供哪些工具:
Client → Server: tools/list
Server → Client: {
"tools": [
{
"name": "get_weather",
"description": "获取指定城市的天气",
"inputSchema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
]
}
Host 收到后,把这些工具信息整合进 LLM 的上下文中。当模型决定调用工具时:
Client → Server: tools/call {name: "get_weather", arguments: {city: "Beijing"}}
Server → Client: {
"content": [{"type": "text", "text": "晴天,22°C"}]
}
这个设计的关键优势是:工具定义只传输一次(初始化时),后续每次调用只传工具名和参数。对于 20 个工具的场景,每次调用节省约 2K tokens。
2.3 生态现状
截至 2026 年初的 MCP 生态:
| 指标 | 数据 |
|---|---|
| 月下载量 | 97M+ |
| 公开 Server 数量 | 15,000+ |
| 官方 Server(GitHub/Slack/Postgres 等) | 30+ |
| 支持 MCP 的框架 | LangChain, LlamaIndex, AutoGen, CrewAI, OpenClaw |
| 协议状态 | 已捐赠给 Linux Foundation (AAIF) |
主流 coding agent(Cursor、Claude Code、 Devin、GitHub Copilot)都通过 MCP 接入工具。AGENTS.md 规范(OpenAI 2025 年 8 月发布)已被 60,000+ 开源项目采用,为 coding agent 提供项目级别的行为指引。
三、迁移实战:从 Function Calling 到 MCP
3.1 最小迁移:一个工具的改造
假设你有一个用 Function Calling 实现的天气查询工具:
改造前(Function Calling):
import openai
import requests
def get_weather(city: str) -> str:
resp = requests.get(f"https://api.weather.com/v1/{city}")
return resp.json()["description"]
# 工具定义
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
def run_agent(user_input: str):
messages = [{"role": "user", "content": user_input}]
response = openai.ChatCompletion.create(
model="gpt-4", messages=messages, tools=tools
)
# 处理 tool_calls...
改造后(MCP Server + MCP Client):
先创建 MCP Server:
# weather_server.py
from mcp.server.fastmcp import FastMCP
import requests
mcp = FastMCP("weather")
@mcp.tool()
def get_weather(city: str) -> str:
"""获取指定城市的天气"""
resp = requests.get(f"https://api.weather.com/v1/{city}")
return resp.json()["description"]
if __name__ == "__main__":
mcp.run()
再创建 MCP Client(在你的 Agent 中):
# agent.py
from mcp import ClientSession
from mcp.client.stdio import stdio_client
import asyncio
async def run_agent(user_input: str):
async with stdio_client(
command="python", args=["weather_server.py"]
) as (read, write):
async with ClientSession(read, write) as session:
# 1. 发现工具
await session.initialize()
tools = await session.list_tools()
# 2. 把工具定义传给 LLM
llm_tools = [
{
"type": "function",
"function": {
"name": t.name,
"description": t.description,
"parameters": t.inputSchema,
}
}
for t in tools
]
# 3. 调用 LLM
response = await call_llm(user_input, llm_tools)
# 4. 如果 LLM 决定调用工具,通过 MCP Client 执行
if response.tool_calls:
for tool_call in response.tool_calls:
result = await session.call_tool(
tool_call.name, tool_call.arguments
)
# 处理结果...
改造的核心变化:
- 工具定义和执行逻辑移到了独立的 MCP Server 进程中
- Agent 通过 MCP Client 发现工具、调用工具
- 工具定义只在初始化时传输一次
3.2 中等迁移:多工具 + 多模型支持
当你有多个工具且需要支持多个 LLM 时,MCP 的优势更明显:
# multi_tool_server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-tools")
@mcp.tool()
def search_docs(query: str) -> str:
"""搜索内部文档"""
# ...
@mcp.tool()
def create_ticket(title: str, description: str) -> str:
"""创建工单"""
# ...
@mcp.tool()
def get_system_status() -> str:
"""获取系统当前状态"""
# ...
@mcp.resource("config://{service_name}")
def get_config(service_name: str) -> str:
"""获取指定服务的配置"""
# ...
一个 MCP Server 暴露所有工具。你的 Agent 代码只需要连接一个 Server(或几个 Server),不需要为每个 LLM 适配不同的工具格式——MCP Client 帮你做了格式转换。
3.3 大规模迁移:MCP 网关模式
当你有 10+ 个 MCP Server 时,直接连接所有 Server 会带来管理复杂度。这时候引入 MCP Gateway:
┌─────────────┐
│ Agent │
│ (MCP Client)│
└──────┬──────┘
│
│ 一个连接
▼
┌─────────────┐
│ MCP Gateway │ 统一入口,路由请求到后端 Server
└──┬──┬──┬────┘
│ │ │
▼ ▼ ▼
S1 S2 S3 ...SN
MCP Gateway 负责:
- 聚合所有后端 Server 的工具发现结果
- 路由工具调用到正确的 Server
- 统一的认证和限流
- 工具调用的日志和监控
Cloudflare 已经在 2026 年推出了托管的 MCP Server 支持,允许开发者在全球边缘网络部署和扩展 MCP Server,无需自建基础设施。
四、迁移决策树
你有几个工具?
│
├── ≤ 5 个,且只用一个 LLM 提供商
│ └── 继续用 Function Calling。MCP 增加复杂度但收益有限。
│
├── 5-15 个,或多 LLM 支持需求
│ └── 开始迁移。优先把最常被调用的工具改造成 MCP Server。
│
├── > 15 个
│ └── 必须迁移。Function Calling 的 token 开销和维护成本已经不可持续。
│ 考虑引入 MCP Gateway 模式。
│
└── 多个 Agent 需要共享工具
└── 必须迁移。把共享工具抽成独立的 MCP Server,
所有 Agent 通过 MCP Client 访问。
五、迁移的常见坑
5.1 传输层选择
MCP 支持 stdio 和 HTTP 两种传输:
- stdio:适合本地部署,MCP Server 作为 Agent 的子进程运行。零网络延迟,但无法跨机器调用。
- HTTP(Streamable HTTP):适合远程部署,MCP Server 独立运行。支持多客户端连接,但有网络延迟(通常 5-20ms)。
经验法则:coding agent 用 stdio(工具在本地),云服务用 HTTP。
5.2 错误处理
Function Calling 的错误由你的代码直接处理。MCP 的错误需要经过协议层传递:
try:
result = await session.call_tool("get_weather", {"city": "Invalid"})
except McpError as e:
# e.error.code 和 e.error.message 来自 Server 端
if e.error.code == -32603: # Internal Error
# 工具执行出错
return f"工具执行失败: {e.error.message}"
elif e.error.code == -32601: # Method Not Found
# 工具不存在
return f"工具不存在"
MCP 使用 JSON-RPC 错误码。建议在 MCP Server 端做充分的错误处理,返回有意义的错误信息,而不是让协议层的默认错误暴露给用户。
5.3 状态管理
MCP Server 默认是无状态的。如果你的工具需要维护状态(比如数据库连接池),需要在 Server 端显式管理:
from mcp.server.fastmcp import FastMCP
from contextlib import asynccontextmanager
mcp = FastMCP("db-tools")
@asynccontextmanager
async def server_lifespan(server):
# 启动时初始化
db_pool = await create_pool(DSN)
yield {"db_pool": db_pool}
# 关闭时清理
await db_pool.close()
mcp = FastMCP("db-tools", lifespan=server_lifespan)
@mcp.tool()
def query_db(sql: str, ctx) -> str:
pool = ctx.request_context.lifespan_context["db_pool"]
# 使用连接池执行查询
5.4 安全性
MCP Server 暴露的工具本质上是可以被 LLM 调用的任意代码。必须做安全加固:
- 权限控制:工具执行时使用的身份应该是最小权限原则
- 输入校验:不要信任 LLM 传入的参数,所有参数都要校验
- 速率限制:防止 Agent 循环调用导致资源耗尽
- 审计日志:记录所有工具调用,用于事后追溯
六、MCP 之外的协议生态
MCP 不是唯一的 Agent 协议。2026 年的协议生态分为四个层次:
| 协议 | 层级 | 解决的问题 |
|---|---|---|
| MCP | Agent → Tool | 标准化工具调用 |
| A2A | Agent → Agent | Agent 之间的任务委派和协调 |
| ACP | Agent ↔ Agent | 能力协商和发现 |
| UCP | Agent ↔ Commerce | 商业交易(支付、合同) |
一个完整的 Agent 系统通常会同时使用多个协议:
- 用 MCP 访问工具(GitHub、数据库、搜索)
- 用 A2A 做 Agent 之间的任务委派(研究 Agent → 规划 Agent → 执行 Agent)
- 用 ACP 做能力协商(“你会写 Python 吗?” “会,我能调用这 5 个工具”)
七、总结
Function Calling 是工具调用的起点,不是终点。当你的 Agent 从”能调用几个函数”进化到”需要管理几十个工具、跨模型运行、多 Agent 协作”时,MCP 的解耦架构就变成了必需品,而不是可选项。
迁移的时机判断很简单:
- 如果你的工具数量超过 10 个,开始规划迁移
- 如果需要支持多个 LLM 提供商,开始规划迁移
- 如果多个 Agent 需要共享工具,开始规划迁移
迁移的工作量取决于你的工具数量——一个工具的改造大约 1-2 小时,10 个工具大约 1-2 天。但迁移之后,新增工具的成本从”改 Agent 代码”降低到”写一个 MCP Server”,这个长期的 ROI 是正向的。