news 2026/10/16 1:35:57

MCP 和传统 Function Call 的对比:从 JSON-RPC 到 Client-Server 的 LLM 工具调用演进

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MCP 和传统 Function Call 的对比:从 JSON-RPC 到 Client-Server 的 LLM 工具调用演进

1. 从一次“工具爆炸”的深夜排障说起:MCP 和 Function Call 到底差在哪

如果你正在做 LLM 工具调用,大概率遇到过这种局面:一开始只接了一个查天气的 Function Call,代码清爽得像刚洗完的白衬衫。两周后产品说“再加个查订单、发邮件、读数据库、调内部工单系统”,于是你的tools数组膨胀到二十几个,每个模型供应商的 schema 写法还不一样,OpenAI 的function字段、Anthropic 的tool_use块、国内某模型的tools结构,改一处要动三处。这就是典型的“工具爆炸 + 接口碎片”。

MCP(Model Context Protocol)和传统 Function Call 的核心差异,用一句话概括:Function Call 解决的是“模型怎么表达我要调哪个函数”,MCP 解决的是“模型和工具之间怎么标准化地发现、协商、调用、维持会话”。前者是模型输出层的一个约定,后者是一套 Client-Server 的通信协议,底层用 JSON-RPC 2.0 承载。

这篇文章面向三类人:正在用 Function Call 做 Agent 但被多模型适配折磨的开发者、想引入 MCP 但不确定值不值得的架构同学、以及刚听说 MCP 想跑通第一条调用链的新手。我会把两种方式的配置、代码、验证命令都摊开,你可以直接复制到本地跑。实测下来,理解这两者的边界,比盲目上 MCP 重要得多。

先给一个直观对照,后面每一节都会展开:

维度传统 Function CallMCP
定位模型输出结构化调用意图模型与工具/资源的标准化协议
通信应用侧自行解析执行JSON-RPC 2.0 over stdio / HTTP+SSE
角色模型 → 函数Host / Client / Server 三角色
工具发现无,靠代码硬编码注册tools/list动态发现
会话一次性,状态最小支持 session、多步调用链
跨模型复用差,各家 schema 不同好,协议层统一

2. 前置准备:用 TaoToken 统一拿到模型入口与 API Key

在对比两种调用方式之前,得先有一个能稳定调模型的入口。我这边习惯用 TaoToken 做统一网关,原因是它把模型对话、Coding Plan、API Key 管理放在一个控制台里,切换模型不用改一堆环境变量。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后在控制台生成 Key。

拿到 Key 之后,你需要记下三个东西,后面所有配置都围绕它们:Base URL 用https://taotoken.net/api(注意这个地址不加 UTM 参数,直接写进配置),API Key 形如sk-xxxx,Model ID 比如claude-sonnet-4-5或gpt-4o之类,具体以控制台模型列表为准。这三个要素就是所谓的“三件套”,无论你是接 Claude Code、Cline、还是自己写脚本,都逃不掉。

如果你只是想先验证模型能不能通,可以直接去模型对话页面发一条消息试试:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。这一步能排除掉后面 90% 的“到底是网络问题还是配置问题”。

对于长期做编码和 Agent 的场景,Coding Plan 会更划算,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。API Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

注意:Base URL 一定要区分清楚,https://taotoken.net/api是给 SDK 和工具用的接口地址,不要和官网首页混用。很多 401 报错就是因为把首页地址填进了base_url。

环境变量建议这样设,后面 Python 和 Node 脚本都读它:

export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_MODEL="claude-sonnet-4-5"

Windows PowerShell 用$env:TAOTOKEN_API_KEY="sk-你的key"。设完echo $TAOTOKEN_API_KEY确认一下,别带着空值往下走。

3. 可复制配置:Function Call 示例与 MCP Server 配置对照

这一节是全文的核心,我把两种方式的完整配置都放出来,你可以对照着改。

3.1 传统 Function Call 的完整调用

先看 Function Call。它的本质是:你在请求里塞一个tools数组,模型判断需要调用时,返回一个tool_calls结构,你的代码解析后执行本地函数,再把结果作为tool角色消息塞回对话。下面是一个能跑的 Python 示例:

import os, json from openai import OpenAI client = OpenAI( base_url=os.environ["TAOTOKEN_BASE_URL"], api_key=os.environ["TAOTOKEN_API_KEY"], ) tools = [{ "type": "function", "function": { "name": "get_weather", "description": "查询指定城市某天的天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名,如 Singapore"}, "date": {"type": "string", "description": "日期,如 2025-06-01"} }, "required": ["city", "date"] } } }] def get_weather(city, date): # 真实场景替换为你的 API 调用 return {"city": city, "date": date, "temp_c": 29, "condition": "sunny"} messages = [{"role": "user", "content": "查一下新加坡明天的天气"}] resp = client.chat.completions.create( model=os.environ["TAOTOKEN_MODEL"], messages=messages, tools=tools, tool_choice="auto", ) msg = resp.choices[0].message if msg.tool_calls: call = msg.tool_calls[0] args = json.loads(call.function.arguments) result = get_weather(**args) messages.append(msg) messages.append({ "role": "tool", "tool_call_id": call.id, "content": json.dumps(result, ensure_ascii=False) }) final = client.chat.completions.create( model=os.environ["TAOTOKEN_MODEL"], messages=messages, ) print(final.choices[0].message.content)

这段代码里,模型只负责“说要调get_weather,参数是 city 和 date”,真正的执行、结果回传、二次请求全是你手写的。工具一多,这个if msg.tool_calls的分支就会变成一坨。

3.2 MCP Server 的配置片段

MCP 把上面那坨胶水代码抽到了协议层。一个 MCP Server 通常用 stdio 或 HTTP+SSE 暴露工具,Client 通过 JSON-RPC 调用。下面是一个最小 MCP Server 的配置,以常见的mcpServers结构为例(Claude Desktop / Cline 都认这个格式):

{ "mcpServers": { "weather": { "command": "python", "args": ["/Users/you/mcp_weather_server.py"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "sk-你的key" } } } }

对应的 Server 端用官方 Python SDK 写,核心是注册工具和启动 stdio:

from mcp.server.fastmcp import FastMCP mcp = FastMCP("weather") @mcp.tool() def weather_query(city: str, date: str) -> dict: """查询指定城市某天的天气""" return {"city": city, "date": date, "temp_c": 29, "condition": "sunny"} if __name__ == "__main__": mcp.run(transport="stdio")

注意这里的差别:Function Call 里你要手写parameters的 JSON Schema,而 MCP 里@mcp.tool()装饰器会从函数签名和 docstring 自动生成 schema,Client 通过tools/list就能拿到。新增工具只要加一个函数,不用改 Client 代码。

如果你用的是 Claude Code 这类工具,配置会落在~/.claude/settings.json或项目级.mcp.json,结构类似。Cline 的 MCP 配置在插件设置里,格式也是mcpServers。Codex 的auth.json则用于存凭证,路径通常在~/.codex/auth.json,里面放 API Key 和 Base URL。这三件套(Base URL + Key + Model ID)在任何一种工具里都要对齐,缺一个就是 401 或 model not found。

4. 本地验证:跑通 Function Call 与 MCP 两条调用链路

配置写完不算完,得验证。我习惯分两步:先验证模型入口通不通,再验证工具调用链路通不通。

4.1 验证模型入口

用 curl 打一发最朴素的请求,确认 Key 和 Base URL 没问题:

curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "'"$TAOTOKEN_MODEL"'", "messages": [{"role": "user", "content": "只回复 ok"}] }'

返回里能看到choices[0].message.content就说明入口通了。如果这里就报 401,别往下走,先去 API Keys 页面确认 Key 有没有复制全、有没有多余空格。

4.2 验证 Function Call 链路

跑上面那段 Python,观察输出。成功的话你会看到模型先返回tool_calls,然后你的代码执行get_weather,最后模型基于结果生成一句自然语言回答,类似“新加坡明天晴,29 度,适合户外活动”。这个过程有两个网络往返,token 消耗会翻倍,这是 Function Call 的固有成本。

4.3 验证 MCP 链路

MCP 的验证稍微不同,因为它是 Client-Server 结构。你可以用官方提供的 inspector 工具,或者直接写一个最小 Client:

import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): params = StdioServerParameters( command="python", args=["/Users/you/mcp_weather_server.py"], ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print("发现工具:", [t.name for t in tools.tools]) result = await session.call_tool( "weather_query", {"city": "Singapore", "date": "2025-06-01"} ) print("调用结果:", result.content) asyncio.run(main())

跑通后你会看到先打印“发现工具: ['weather_query']”,再打印调用结果。这一步的关键在于session.initialize()完成了能力协商,list_tools完成了动态发现,call_tool走的是 JSON-RPC 的tools/call方法。整个过程你一行解析tool_calls的代码都没写。

对比下来,Function Call 的验证是“看模型有没有吐出正确的 JSON”,MCP 的验证是“看 Client 能不能发现并调用 Server 的工具”。前者验证的是模型能力,后者验证的是协议链路。

5. 常见报错排查:401、local proxy failed、reading choices、OAuth

这一节按真实报错来,都是我踩过的坑。

401 Unauthorized:最常见。九成是 Key 错了或 Base URL 填成了首页。检查base_url是不是https://taotoken.net/api,Key 有没有sk-前缀,环境变量有没有被 shell 覆盖。MCP 场景下还要检查mcpServers的env里有没有把 Key 传进去,Server 进程读不到环境变量也会 401。

local proxy failed / connection refused:这个报错通常出现在 MCP 用 HTTP+SSE 传输时,Client 连不上 Server 的端口。先确认 Server 进程起来了,curl http://localhost:端口/sse能不能通。如果是 stdio 传输,检查command和args路径对不对,Python 解释器路径建议写绝对路径。

reading 'choices' of undefined:这是 Node/JS 侧解析响应时的经典错误,说明返回体里没有choices字段。原因可能是请求根本没成功(返回的是错误对象),或者你用的 SDK 版本和 API 格式不匹配。打印完整response再解析,别直接.choices[0]。

OAuth 相关报错:部分 MCP Server 或远程工具需要 OAuth 授权,报错里会出现invalid_token或authorization required。检查 token 有没有过期,scope 对不对。如果是 Codex 的auth.json,确认文件权限和字段名,auth.json里通常需要OPENAI_API_KEY和base_url两个字段对齐三件套。

model not found:Model ID 写错了。去控制台模型列表复制准确的 ID,别自己拼。不同工具对 Model ID 的格式要求可能不同,有的要带前缀有的不带。

排查顺序建议:先 curl 验证入口 → 再验证单次 Function Call → 最后验证 MCP 链路。逐层排除,别一上来就怀疑协议。

6. 选型建议与下一步:什么时候该从 Function Call 迁到 MCP

回到最初的问题:到底用哪个。我的经验是分三个阶段。

工具少于 5 个、只用一个模型、调用流程是单步的,Function Call 完全够用,上手快,调试直观,没必要为了“先进”而引入 MCP 的复杂度。这个阶段你的瓶颈是产品逻辑,不是协议。

工具超过 10 个、开始接第二个模型、出现多步调用链(比如“查天气 → 判断 → 预约活动”),Function Call 的维护成本会陡增。每加一个工具要改 schema、改解析分支、改错误处理,而且换模型时 schema 可能要重写。这时候 MCP 的价值就出来了:工具注册一次,所有支持 MCP 的 Client 都能发现和调用,跨模型复用。

再往上,如果你要做的是平台级 Agent,需要工具市场、权限治理、调用审计,那 MCP 几乎是必选项。它的 session 管理、能力协商、transport 抽象,都是为规模化准备的。

迁移路径建议:先把现有 Function Call 的工具抽成一个独立 MCP Server,用 stdio 跑通,Client 侧先用 inspector 验证。确认链路没问题后,再把 Host 里的硬编码调用替换成 MCP Client。这样风险可控,不会一次性推翻现有系统。

想动手的话,先去 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 拿一个 Key,然后照着第 3 节的配置把 MCP Server 跑起来。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有各工具的详细配置。如果你主要做编码 Agent,Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 有更省额度的方案。最后提醒一句:MCP 的扩展性越强,安全治理的要求越高,工具权限和数据边界一定要在设计阶段就想清楚,别等出事再补。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 23:57:08

Hadoop+SSH框架实现HDFS网盘:从环境搭建到Java API实战

简介:这份资源面向大数据与分布式系统方向的开发者及高校学生,提供一套基于Hadoop平台、借助SSH框架实现HDFS网盘的完整项目源码,帮助读者理解分布式存储与远程安全访问的落地方式。压缩包共629个文件,约37.4MB,涵盖60…

作者头像 李华
网站建设 2026/10/15 13:59:49

Java+SpringBoot+SSM智能医疗辅助系统实战解析

智能医疗辅助系统这个项目,我从去年开始陆续带过好几个版本的开发,从纯SSM架构到SpringBoot整合,从单机部署到前后端分离,踩过的坑加起来能写一本小册子。回头看,“基于JavaSpringBootSSM的智能医疗辅助系统”这个选题…

作者头像 李华
网站建设 2026/10/14 13:21:09

纯Dart PDF解析库在OpenHarmony上的适配实践与性能调优

1. 为什么我决定把 dart_pdf_reader 搬到 OpenHarmony 上在 OpenHarmony 上做 Flutter 开发,最让人头疼的就是 PDF 怎么处理。dart_pdf_reader这个库进入我视野的时候,正好卡在我试遍了 PDF 渲染方案都不太行的关口。鸿蒙生态的三方支持本来就不像 Andro…

作者头像 李华
网站建设 2026/10/15 4:02:23

国产替代电子元器件平台怎么选?300万+替代型号库怎么查

国产替代不是简单换型号,是换一套完整的"选型-验证-量产-追溯"链路。2026年真正能落地国产替代的平台,需要同时具备4个能力:≥300万替代型号库、FAE工程师全程对接、上机验证支持、量产同源供货能力。判断一个平台能不能承接国产替…

作者头像 李华
网站建设 2026/10/15 18:57:36

APP测试面试必知:从adb命令到弱网用例设计的实战攻略

这几年我一直在软件测试一线摸爬滚打,也面试过不少应聘APP测试岗位的候选人。说实话,很多人的简历写得漂漂亮亮,结果一聊技术就露怯:问Android的ANR是什么,答“程序没响应”,问弱网怎么测,答“用…

作者头像 李华