MCP Server 与 Function Calling:AI 工具调用的两层架构
一篇讲清楚"模型怎么想调工具"和"工具怎么被真正执行"的区别。
1. Function Calling 与 Model Context Protocol
你跟 AI 说:"帮我查一下订单 NB-666 的状态。"
AI 看起来"自己调了工具、读了数据库、给了你答案"。这个过程里,至少藏着两套机制:
- Function Calling :模型输出一段结构化意图------"我要调
get_order,参数是NB-1001"。 - MCP(Model Context Protocol):真正有个服务收到这个调用,去数据库查,把结果送回来。
很多人以为这是"同一个东西的两种说法",其实它们根本不在一层。
Function Calling 是模型"嘴上说要做什么";
MCP 是"旁边谁真的去把它做了,并且让所有 AI 应用都能复用这套做法"。
1.1. Function Calling(函数调用)
这是大模型自己的能力,不是协议。
流程很简单:
- 你给模型一份"工具清单"(名字 + 描述 + 参数 JSON Schema)
- 模型根据用户问题,决定调哪个、传什么参
- 模型输出一段结构化调用请求(不是真执行)
- 你的代码拿到请求,自己跑函数
- 把结果塞回对话,模型继续生成回答
模型侧看到的样子(以 OpenAI 风格为例):
json
{
"type": "function_call",
"name": "get_order",
"arguments": "{\"order_id\": \"NB-1001\"}"
}
关键点:模型永远不碰数据库。它只"提议",执行者是你写的应用代码。
特点:
- 快、轻、简单
- 工具定义跟着每次请求走(有上下文开销)
- 各厂商 Schema 格式不完全兼容(OpenAI / Claude / Gemini 各一套)
- 工具逻辑和你的应用强耦合,换一个应用就得重写
1.2. MCP Server(模型上下文协议服务)
MCP 是 Anthropic 在 2024 年提出的开放协议,现在已捐给 Linux 基金会旗下 Agentic AI Foundation,OpenAI、Google、Microsoft、AWS 都参与支持 。
它解决的是另一个问题:
工具那么多、AI 应用那么多,难道每个组合都要写一遍对接代码?
MCP 的答案是:把工具做成"标准插座"。
1.2.1 架构三角色
| 角色 | 是什么 | 例子 |
|---|---|---|
| Host | 用户直接用的 AI 应用 | Claude Desktop、Cursor、VS Code、你自研的 Agent |
| Client | Host 内部的小连接器,一对一连一个 Server | Host 里每个 MCP Server 配一个 Client |
| Server | 独立运行的服务,暴露工具/资源/提示词 | 数据库服务、文件系统服务、Jira 服务 |
1.2.2 MCP Server 能暴露三种能力
- Tools(工具) :可执行的动作,模型主动调用
query_db()、send_email()、search_code() - Resources(资源) :只读上下文数据,按 URI 标识
file:///config.yaml、db://schema/users - Prompts(提示词模板) :可复用的交互模板
"代码审查模板""周报生成模板"
1.2.3 通信底层
- 消息格式:JSON-RPC 2.0
- 传输方式:
stdio:本地进程,通过标准输入输出(默认、零部署)Streamable HTTP:远端服务,走 HTTP + SSE
一次典型工具调用在协议层长这样:
json
// Client → Server
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "get_order",
"arguments": { "order_id": "NB-1001" }
}
}
// Server → Client
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [{ "type": "text", "text": "{\"status\": \"shipped\"}" }]
}
}
Server 自己从不跟模型对话,它只等调用、做事情、回结果 。
2. 核心关系:不是替代,是分层
这是全文最重要的图,建议直接记下来:
┌─────────────────────────────────────────────┐
│ AI Host 应用 │
│ │
│ 模型(LLM) │
│ │ │
│ │ ① Function Calling:我要调 get_order │
│ ▼ │
│ 适配器(把 MCP 工具转成模型能看的 Schema) │
│ │ │
│ │ ② MCP Client │
│ ▼ │
│ ╔═══════════════════════╗ │
│ ║ MCP Server ║ │
│ ║ - 工具发现 ║ │
│ ║ - 真正执行业务逻辑 ║ │
│ ║ - 调底层 API / DB ║ │
│ ╚═══════════════════════╝ │
└─────────────────────────────────────────────┘
一句话:
Function Calling 是"模型到应用的契约";
MCP 是"应用到工具服务的协议"。
生产系统里,它们通常一起用:模型用 Function Calling 表达意图,Host 把它翻译成 MCP 调用。
3. 完整调用时序(真实发生的事)
以"查订单"为例,端到端走了这几步:
- 启动发现 :Host 启动 MCP Client,连上 Server,发
server/discover或tools/list - 能力协商 :Server 返回"我有
get_order,参数长这样(JSON Schema)" - 塞给模型:Host 把这个工具描述,转成当前模型支持的 Function Calling 格式,放进模型请求
- 模型决策 :用户问"订单咋样了",模型输出
function_call: get_order(NB-666) - 协议转发 :Host 收到后,通过 MCP Client 发
tools/call - 执行:MCP Server 真去查库
- 回传:Server 返回结果,Client 交给 Host
- 二次推理:Host 把结果塞回对话,模型生成自然语言回答
模型从头到尾不知道数据库密码,也不知道 MCP 存在。
4. 、写一个最小可运行的 MCP Server
用官方 Python SDK(FastMCP 风格),十几行就能跑起来:
python
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("order-server")
ORDERS = {
"NB-1001": {"status": "shipped", "items": 2},
"NB-1002": {"status": "processing", "items": 1},
}
@mcp.tool()
def get_order(order_id: str) -> dict:
"""根据订单号查询订单状态"""
return ORDERS.get(order_id, {"status": "not_found"})
@mcp.resource("order://{order_id}")
def order_resource(order_id: str) -> str:
"""把订单暴露成可读资源"""
return str(ORDERS.get(order_id, {}))
if __name__ == "__main__":
mcp.run(transport="stdio")
运行:
bash
pip install "mcp[cli]"
python server.py
在支持 MCP 的客户端(Cursor / Claude Desktop / Cherry Studio)里配一下:
json
{
"mcpServers": {
"order": {
"command": "python",
"args": ["/绝对路径/server.py"]
}
}
}
AI 应用启动后会自动发现 get_order 工具,模型就能自己决定什么时候调它。
💡 SDK 会根据函数名、docstring、类型注解自动生成 JSON Schema,你不用手写。
5. 两者对比一览
| 维度 | Function Calling | MCP Server |
|---|---|---|
| 本质 | 模型能力(输出格式) | 通信协议 + 服务形态 |
| 所在层 | 模型 ↔ 应用 | 应用 ↔ 工具服务 |
| 工具定义在哪 | 每次请求里塞给模型 | Server 启动后自动暴露 |
| 执行位置 | 你的应用代码里 | 独立的 MCP Server 进程/服务 |
| 跨应用复用 | 差,要重写 | 好,一次写好处处连 |
| 跨模型兼容 | 各厂商格式不同 | 协议统一,Client 做适配 |
| 上下文开销 | 工具越多,每次请求越重 | 按需发现,不占主对话 |
| 运行状态 | 无状态、请求级 | 可保持连接、连接池、缓存 |
| 部署成本 | 零额外组件 | 要跑/管一个服务进程 |
6. 选型建议
✅ 只用 Function Calling 就够
- 工具只有 2~5 个,且只在你自己的应用里用
- 不打算给别的 AI 客户端复用
- 快速原型、单一模型、单一业务
✅ 值得上 MCP
- 多个 AI 应用(IDE、客服、运营后台)要共用同一批工具
- 工具要连数据库、文件系统、内部微服务,需要独立鉴权
- 工具多(几十个以上),Function Calling 的 Schema 上下文成本扛不住
- 想做"工具产品化",给别人或其他 Agent 用
✅ 生产系统常见做法:两者叠加
- 模型侧:用 Function Calling 表达意图
- 架构侧:Host 把 MCP 工具映射成 Function,调用转发给 MCP Server
- 这样既保留模型原生能力,又获得标准化工具治理
7. 工程与安全注意点(踩坑必看)
MCP 不是"接上就安全",它本质是能执行代码的代理,权限越大风险越高。
-
最小权限
Server 不要直连 root 数据库。单独建账号,只给必要表、必要操作。
-
写操作必须加确认
send_email/delete_record/create_ticket这类动作:- 要求
confirm=true显式参数 - 或在 Host 层弹确认框,不让模型"顺手就发"
- 要求
-
超时与降级
MCP Server 是独立进程,会挂、会卡。Client 一定加 timeout,Server 返回结构化错误,让模型能自我恢复,而不是无限等待 。
-
把工具返回当"不可信输入"
第三方 Server 返回的内容可能是注入载荷。别直接拼进系统提示,要做清洗。
-
环境隔离
stdio 模式启动子进程时,工作目录、环境变量、配置文件路径要用绝对路径。一个相对路径错误,可能让
tools/list返回空,排错排到怀疑人生。 -
密钥隔离
API Key 放 Server 侧,别散落在 Host 和各模型请求里。
8. 一个更直白的类比
把整套体系想象成公司协作:
- 模型(LLM):老板,只会说"我要这份数据"
- Function Calling:老板在群里 @你 的那句话
- Host:项目经理,听懂老板意思,安排人干活
- MCP Client:项目经理手里的对讲机
- MCP Server:各个专业小组(财务组、IT 组、法务组)
- Tools/Resources:小组能提供的服务
没有 MCP 时:老板每换一个项目,项目经理就得重新教一遍"财务组怎么对接"。
有 MCP 后:财务组装好对讲机,所有项目经理都能直接呼叫。
9.总结
- Function Calling ≠ MCP,它们是不同层。
- Function Calling 解决"模型怎么表达想调工具"。
- MCP 解决"工具怎么被标准化发现、连接、执行、复用"。
- 真实生产架构里,它们通常一起出现:模型用 Function Calling 决策,MCP 负责落地。
- 小项目先用 Function Calling;工具要跨应用、要治理、要规模化,再抽成 MCP Server。
- MCP 不自带安全,权限、确认、超时、审计都得自己设计。
模型负责"想",MCP 负责"做",Function Calling 是它们之间那句"我来安排"。
这篇文章的基础框架已经完整,接下来可以选一个方向继续深化: