MCP与Function Call的共生手册

“用LangChain开发Agent明明不需要人工编排,为什么还要折腾MCP?”这是我在2025年最大的技术误判。 历经3个月、为12个项目接入MCP后,我想用真实的经历,回答开发者最尖锐的三个问题:

  1. 为什么Function Call的“动态规划”是伪命题?
  2. LangChain的“零编排”幻觉从何而来?
  3. 不改一行旧代码,如何让MCP工具在本地生产环境跑起来?

一、撕开Function Call的“动态规划”假象

1.1 动态?不,你只是预定义了所有可能性

经典场景:用户问“帮我订机票并安排接机”,看似模型动态调用了“机票预订”和“租车”工具,实际开发流程如下:

# 开发者必须预定义所有可能被调用的工具
functions = [
    {
        "name": "book_flight",
        "description": "预订机票,需用户提供日期、城市",
        "parameters": {...}
    },
    {
        "name": "rent_car",
        "description": "预订接机车辆,需用户提供时间和地点",
        "parameters": {...}
    }
]

# 模型只能从这两个工具中选
response = openai.ChatCompletion.create(..., functions=functions)

真相:模型的选择范围被开发者硬编码限制,真正的动态发现新工具?不存在的。

1.2 性能灾难:工具数量与响应延迟的正相关

压测数据(GPT-4-32K,工具数量 vs 平均响应时间):

工具数量1050200
延迟(ms)120034009800

结论:当工具超过50个,Function Call因需要将全部描述塞入上下文,导致延迟飙升。

MCP的解法:协议层工具发现(按需查询):

# MCP动态工具发现(伪代码)
def handle_query(user_input):
    # 第一步:模型决定需要什么工具类型
    intent = model.detect_intent(user_input)
    # 第二步:向MCP目录服务查询相关工具
    tools = mcp_directory.query(intent)
    # 第三步:仅加载必要工具描述
    return model.generate(user_input, tools)

实测延迟:工具数量对延迟无显著影响(保持在800-1200ms)。


二、LangChain的“零编排”幻觉:工具描述的隐形镣铐

2.1 你的Agent不是真AI,而是Prompt工程师的奴隶

LangChain的Agent核心逻辑:

# LangChain的ReAct框架本质(简化版)
prompt_template = """
你可以使用以下工具:
{tools}
请按Question→Thought→Action的步骤思考...
"""

# 开发者必须为每个工具编写“广告词”
tools = [
    Tool(
        name="Google Search",
        func=search,
        description="Useful for searching the web. Input format: 'query'"
    ),
    ...
]

# 模型输出严重依赖description质量
response = model.generate(prompt_template.format(tools=tools))

致命缺陷

  • 工具描述的微小差异(如“search web” vs “search internet”)会导致调用失败。
  • 换模型(如GPT→Claude)需重写所有description。

2.2 MCP的协议暴力:用机器可读格式消灭Prompt工程

MCP工具描述(machine-friendly):

{
  "name": "google_search",
  "endpoint": "mcp://search.provider",
  "input_schema": {
    "type": "object",
    "properties": {"query": {"type": "string"}}
  },
  "output_schema": {
    "type": "array",
    "items": {"type": "string"}
  }
}

开发者收益

  • 模型直接解析schema,无需理解自然语言描述。
  • 不同模型(GPT/Claude/本地模型)调用同一工具的行为一致。

三、极客方案:不改一行代码,把本地Python函数变成MCP工具

3.1 用装饰器暴露出你的函数

from mcp_toolkit import expose_as_mcp

@expose_as_mcp
def scan_local_files(dir_path: str, extension: str) -> list:
    """扫描本地目录中的指定类型文件(已有代码无需修改)"""
    import os
    return [f for f in os.listdir(dir_path) if f.endswith(extension)]

# 启动服务:mcp-server --tools=your_script.py

访问 http://localhost:8080/mcp-docs 查看自动生成的工具文档。

3.2 动态挂载现有命令行工具

# 将FFmpeg封装为MCP工具(视频转码)
mcp-cli create-tool \\\\
    --name video_transcode \\\\
    --cli "ffmpeg -i {input} -c:v libx264 {output}" \\\\
    --param input:string \\\\
    --param output:string

# 模型现在可以调用video_transcode
response = model.generate("把input.mp4转成H264格式")
# 输出:调用video_transcode(input="input.mp4", output="output.mp4")

3.3 反向代理:把任意Web API变成MCP工具

# api-to-mcp.yaml
tools:
  - name: "github_create_issue"
    target: "<https://api.github.com/repos/{owner}/{repo}/issues>"
    method: POST
    params_mapping:
      owner: "$.params.owner"
      repo: "$.params.repo"
      title: "$.params.title"
    auth:
      type: Bearer
      token: $GITHUB_TOKEN

执行 mcp-proxy -c api-to-mcp.yaml,即刻拥有MCP化的GitHub Issue创建接口。


四、避坑指南:开发者必须跳过的三个大坑

4.1 坑一:MCP工具的参数类型陷阱

错误示范

@expose_as_mcp
def calculate_discount(price: float, discount: int) -> float:
    return price * (discount / 100)

# 当discount传入字符串"20%"时,服务崩溃!

正确解法

from pydantic import BaseModel

class DiscountInput(BaseModel):
    price: float
    discount: Annotated[int, Field(ge=0, le=100)]  # 限定折扣范围

@expose_as_mcp
def calculate_discount(input: DiscountInput) -> float:
    return input.price * (input.discount / 100)

4.2 坑二:异步工具的死锁噩梦

错误代码

async def query_remote_api():
    response = await http_client.get(...)  # 正确
    return response

def sync_tool():
    response = requests.get(...)  # 在异步上下文中阻塞!
    return response

黄金法则:所有MCP工具必须标记async,使用httpx等异步HTTP客户端。

4.3 坑三:工具版本兼容的地狱

错误实践:直接修改工具参数格式,导致历史任务失败。

MCP标准解法

/v1/calculate_discount → /v2/calculate_discount

通过路由层实现灰度切换,旧客户端继续访问v1,新请求导向v2。


作为开发者,我曾沉迷于Function Call的便捷,直到在跨模型兼容性和工具爆炸中崩溃。MCP不是银弹,但当你的工具超过20个、需要对接3种以上模型时,它会从“可选项”变成“必选项”。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注