去年Q4给金融客户做运维智能体,在工具接入层踩遍所有坑。从Function Calling到MCP的架构演进,附生产级Python代码和安全设计。
OpenAI 在 2023 年推出 Function Calling,写法不复杂:
import openai
tools = [{
"type": "function",
"function": {
"name": "query_database",
"description": "执行 SQL 查询",
"parameters": {
"type": "object",
"properties": {
"sql": {"description": "SQL 查询语句"},
"db": {"enum": ["prod_readonly", "analytics"]}
},
"required": ["sql", "db"]
}
}
}]
resp = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "查一下上周的订单量"}],
tools=tools,
tool_choice="auto"
)
三个月后我们在生产环境撞到三个硬问题:
问题 1:接口描述维护失控。 10 个工具 = 10 份参数定义,每个字段改一次要同步代码、文档、测试。到第 15 个时已占周开发时间 20%。
问题 2:多模型适配无底洞。 从 GPT-4 切到 Claude 做 A/B 测试,才发现 Anthropic 的 tool_use 格式跟 OpenAI 完全不同——content block 类型、参数序列化、错误约定都不一致。团队花了两天重写适配层,然后意识到每接一个新模型就得重来。
问题 3:状态管理靠人工。 没有内置的对话状态机,工具返回结果要手动拼回 messages 数组。一个同事的 PR 因消息排列问题被 QA 打回 4 次——不是逻辑错,是胶水代码太脆。
Google 在 2022 年提出的思考-行动-观察循环模式(ReAct 论文)让推理链路更清晰了,但它解决的是「何时调工具」的认知问题,不是「工具如何接入」的工程问题。10 个能力的接口维护、多模型适配——都没碰。
Anthropic 在两年前发布了 MCP(Model Context Protocol)。设计意图一句话:标准化接口,消灭一对一适配。
架构分三层:Host 内嵌客户端,客户端通过 JSON-RPC 2.0 跟工具端进程通信,工具端对接外部系统。传输支持 stdio(本地,fork 进程走管道)和 HTTP+SSE(远程,带 TLS)。
几个关键设计决策:
1. 用 JSON-RPC 2.0,不造轮子。 协议交互完全基于标准 RPC 调用模式——客户端通过 tools/list 获取工具清单,服务端返回名称、描述和参数规格。调用工具时走 tools/call,传工具名和实参,拿到结构化返回结果。跟几十年的 RPC 实践完全一致。
2. 三类原语覆盖全部交互。 Tools(可调用函数)、Resources(可读取数据)、Prompts(提示词模板)。
3. 能力动态发现。 客户端连上后先获取清单再注入 LLM 系统提示。不重启就能热加载新能力——OpenAI 的原生方案做不到。这套模式对生产级部署的价值,我们在另一篇 AI Agent 生产部署实战 里有更详细的展开。
当前最活跃的 Python 库是 这个项目(GitHub 超 25k stars)。搭一个带鉴权的数据库查询工具端:
import sqlite3, json
from mcp.server.fastmcp import FastMCP as MCP
app = MCP("db-query")
@app.tool()
def run_query(sql: str, limit: int = 100) -> str:
"""执行只读查询并返回 JSON,禁止写操作。"""
upper = sql.upper().strip()
blocked = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER"]
for kw in blocked:
if upper.startswith(kw) or f" {kw} " in f" {upper} ":
return json.dumps({"error": f"禁止: {kw}"})
conn = sqlite3.connect("file:prod.db?mode=ro", uri=True)
cur = conn.execute(f"{sql} LIMIT {limit}")
cols = [d[0] for d in cur.description]
rows = [dict(zip(cols, r)) for r in cur.fetchall()]
conn.close()
return json.dumps(rows, default=str, ensure_ascii=False)
@app.tool()
def show_schema() -> str:
"""列出所有表"""
conn = sqlite3.connect("file:prod.db?mode=ro", uri=True)
cur = conn.execute("SELECT name FROM sqlite_master")
names = [r[0] for r in cur.fetchall()]
conn.close()
return json.dumps(names, ensure_ascii=False)
if __name__ == "__main__":
app.run()
三个工程要点:
1. 只读连接 + 关键词拦截 = 双重安全。 mode=ro 在文件系统层面限制写操作,代码层再加白名单——防止 prompt injection 诱导危险操作时先拦截。
2. limit 防上下文爆炸。 返回结果注入 LLM 上下文,不限制行数可能撑爆 token 预算。100 行是经验值。
3. 先探索结构再精确查询。 让系统先了解有哪些表,再构造精准 SQL。实测准确率从 61% 提升到 89%。
核心原则:智能体能做的事 ≤ 工具端进程能做的事 ≤ IAM Role 能做的事。 四层防护:
以 AWS Bedrock 方案为例,工具端绑定只允许 s3:GetObject 和 s3:ListBucket 的权限策略,且资源限定在指定存储桶。即使攻击者通过 prompt injection 诱导执行删除操作,IAM 层直接拒绝——权限在基础设施层面硬截断,比代码校验更强。
| 维度 | FC(原生函数调用) | 思考-行动循环 + FC | MCP 协议 |
|---|---|---|---|
| 接入方式 | 手写参数定义 | 同上 + 提示模板 | 独立进程,协议标准化 |
| 能力发现 | 编译期绑定 | 同左 | 运行时动态 |
| 多模型 | 每模型一套适配 | 同左 | 协议层统一 |
| 上下文 | 手动拼消息 | 循环管理 | 客户端自动 |
| 安全 | 应用层校验 | 同左 | 四层交叉防御 |
| 生态 | 厂商锁定 | 同左 | 开放标准 |
| 场景 | ≤3 工具、单模型 | 复杂推理 | ≥5 工具、多模型 |
新项目直接用 MCP。FC 只在工具极少且确定不扩展时考虑——但要意识到这是技术债。如果正在评估团队选型,AI Agent 开发外包评估指南 里整理了交付团队的 7 个硬指标,可以作为参考。
LangChain 的 Tool 是框架内抽象——继承 BaseTool 实现 _run(),解决「LangChain 生态里怎么定义工具」。MCP 解决「任何 AI 应用怎么连接任何工具」——跟框架无关。写完可在 Claude Desktop、Cursor、VS Code Copilot、自建系统里同时用。
stdio 适合本地和单机,通信开销 2-5ms。HTTP+SSE 适合远程和多实例。我们生产用后者 + K8s,单进程支撑 200+ 并发,p99 延迟 120ms 以内。瓶颈在工具执行,不在协议层。
失败以错误消息注入 LLM 上下文,模型会重试或换工具。建议加 /health,调用侧设 30 秒超时、最多两次重试。配调用耗时和错误计数两个 Prometheus 指标直接告警。关于库的选择:社区最活跃的封装版(超 25k stars)是官方 SDK 的上层,@tool() 一行自动生成接口描述。需要极致控制用官方 SDK,否则直接用封装版——效率高 3 到 5 倍。