AI AinoCode AI 工具与基础设施
AI教程 7 分钟

从 Function Calling 到 MCP:AI Agent 工具调用架构的演进与迁移指南

Function Calling 为什么不够用了?MCP 解决了什么问题?如何把已有的工具调用代码迁移到 MCP 架构

AinoCode 编辑部

Function Calling 到 MCP 迁移指南

从 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——它已经不再是一个公司的协议,而是整个行业的基础设施。

这篇文章回答三个问题:

  1. Function Calling 到底哪里不够用了?
  2. MCP 的架构设计解决了什么问题?
  3. 如何把已有的 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 年的协议生态分为四个层次:

协议层级解决的问题
MCPAgent → Tool标准化工具调用
A2AAgent → AgentAgent 之间的任务委派和协调
ACPAgent ↔ Agent能力协商和发现
UCPAgent ↔ 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 是正向的。