1. 从一次工具调用翻车说起:MCP 和 Function Calling 到底差在哪
如果你正在用 Claude Code、Cline、Cursor 这类本地 AI 工具接外部能力,大概率遇到过这种困惑:明明模型支持 Function Calling,为什么还要折腾 MCP?或者反过来,MCP Server 配好了,为什么模型还是不会主动调工具?这两个词经常被混着用,但它们在调用链路里的位置完全不同。
先把结论摆出来:Function Calling 是模型侧的能力,解决的是“模型怎么把一句话翻译成结构化参数”;MCP 是协议侧的标准,解决的是“工具怎么被描述、被发现、被安全地调用”。一个管“说人话转 JSON”,一个管“JSON 怎么送到正确的工具手里并拿回结果”。你可以只用 Function Calling 不用 MCP,也可以只用 MCP 而底层不依赖某家厂商的 Function Calling 特性,但两者叠在一起才是目前本地 AI 工具最顺的工程组合。
这篇文章面向的是已经在本地跑 AI 工具、想搞清楚该用哪种方案接工具的开发者。我会用同一个 TaoToken Key,分别跑通一次纯 Function Calling 调用和一次 MCP 工具调用,把 Base URL、auth.json、请求体、返回结构全部贴出来对比。看完你能判断:什么时候写个函数就够了,什么时候必须上 MCP Server。
核心检索词先明确:MCP 与 Function Calling 的区别与联系,本质是协议层与模型能力的边界问题。适合谁?适合正在给本地 AI 工具接数据库、文件系统、Web 自动化,却卡在“工具注册了但调不动”或者“每次换模型就要重写适配”的人。
2. 前置准备:用 TaoToken 统一 Key 打通两种调用
在动手之前,先把“钥匙”统一。本地 AI 工具最烦的一点是每个模型厂商一套 Key、一套 Base URL,切换模型就要改配置。TaoToken 的做法是给你一个统一的 API 入口,Base URL 固定为https://taotoken.net/api,模型 ID 按需填,Key 在控制台生成一次即可。这样无论你后面跑 Function Calling 还是 MCP Server 背后的模型请求,都走同一个出口,排障时只需要盯一个地方。
先拿 Key。打开 https://taotoken.net/api-keys ,登录后创建一个 API Key,复制出来形如sk-xxxxxxxx。这个 Key 后面会同时出现在两处:一是本地工具的 auth.json,二是 MCP Server 启动时的环境变量。统一 Key 的好处是,当你在 Function Calling 和 MCP 之间切换验证时,不用怀疑是不是 Key 权限问题。
接着确认模型 ID。TaoToken 的模型对话页 https://taotoken.net/models 可以查到当前可用的模型标识,比如claude-sonnet-4-20250514这类。Function Calling 场景下,模型必须支持 tools 参数;MCP 场景下,模型只需要支持标准对话即可,因为工具描述是通过 MCP 协议注入的。这一点很关键:MCP 降低了对模型 Function Calling 能力的硬依赖,但如果你想让模型主动决定调哪个工具,底层还是靠模型的工具选择能力。
然后是本地工具的接入配置。以 Claude Code 为例,它的配置文件通常在~/.claude/settings.json或项目级.claude/settings.json。如果你用的是 Codex 系工具,配置在~/.codex/auth.json。Cline 则在 VS Code 设置里填 Base URL 和 Key。不管哪种,核心三件套是:Base URL 填https://taotoken.net/api,Key 填刚才复制的,Model ID 填你选的模型。
这里给一个 Codex 的auth.json片段,路径是~/.codex/auth.json:
{ "OPENAI_API_KEY": "sk-你的TaoTokenKey", "OPENAI_BASE_URL": "https://taotoken.net/api", "model": "claude-sonnet-4-20250514" }注意 Base URL 不要带末尾斜杠,也不要加/v1,TaoToken 的入口已经处理了版本路由。填错最常见的报错是 404 或local proxy failed,后面排障章节会细说。
MCP Server 这边,配置通常写在工具的 MCP 配置文件里,比如 Claude Code 的~/.claude/mcp.json或 Cline 的 MCP 设置面板。一个最小的 MCP Server 配置长这样:
{ "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/workspace"], "env": { "TAOTOKEN_API_KEY": "sk-你的TaoTokenKey", "TAOTOKEN_BASE_URL": "https://taotoken.net/api" } } } }看到没,MCP Server 自己也可以走 TaoToken 的 Key 去请求模型,这样整个链路只有一个计费出口。前置准备做到这里就够了:一个 Key、一个 Base URL、一个 Model ID,剩下的是两种调用方式的写法差异。
3. 可复制配置:同一 Key 下两种调用的完整片段
这一节直接给可复制的配置和请求体,你照着改路径就能跑。先讲 Function Calling 的配置,再讲 MCP 的配置,最后对比它们的请求结构。
Function Calling 的本质是在请求体里加一个tools数组,每个工具用 JSON Schema 描述参数。模型返回时,如果决定调用,会在tool_calls字段里给出函数名和参数。下面是一个完整的 curl 请求,走 TaoToken 的 Base URL:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoTokenKey" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [ {"role": "user", "content": "北京现在天气怎么样?"} ], "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的当前天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } } } ], "tool_choice": "auto" }'返回里你会看到choices[0].message.tool_calls,里面有function.name是get_weather,arguments是{"city":"北京"}。注意,模型只负责生成这个结构化请求,真正执行get_weather的是你的代码。这就是 Function Calling 的边界:它到“生成调用意图”为止,执行和回传结果都是开发者的事。
MCP 的配置则完全不同。MCP 不要求你在每次请求里塞 tools 数组,而是由 MCP Client(比如 Claude Code)在启动时连接 MCP Server,通过 JSON-RPC 2.0 拉取工具列表。工具的描述、参数、执行都在 Server 侧。下面是一个自定义 MCP Server 的核心片段,用 Node.js 写,暴露一个query_order工具:
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const server = new Server( { name: "order-server", version: "1.0.0" }, { capabilities: { tools: {} } } ); server.setRequestHandler("tools/list", async () => ({ tools: [ { name: "query_order", description: "根据订单号查询订单状态", inputSchema: { type: "object", properties: { order_id: { type: "string", description: "订单编号" } }, required: ["order_id"] } } ] })); server.setRequestHandler("tools/call", async (request) => { const { name, arguments: args } = request.params; if (name === "query_order") { const status = await fakeQueryOrder(args.order_id); return { content: [{ type: "text", text: `订单 ${args.order_id} 状态:${status}` }] }; } throw new Error(`未知工具: ${name}`); }); const transport = new StdioServerTransport(); await server.connect(transport);对应的 MCP 客户端配置,以 Claude Code 的~/.claude/mcp.json为例:
{ "mcpServers": { "order": { "command": "node", "args": ["/Users/you/mcp-servers/order-server.js"], "env": { "TAOTOKEN_API_KEY": "sk-你的TaoTokenKey", "TAOTOKEN_BASE_URL": "https://taotoken.net/api" } } } }对比一下就很清楚:Function Calling 的配置在“每次请求”里,MCP 的配置在“工具启动时”。Function Calling 的 tools 数组是请求的一部分,MCP 的 tools/list 是连接建立时的一次握手。Function Calling 的执行逻辑在你自己的代码里,MCP 的执行逻辑在 Server 里。这就是为什么 MCP 更适合“一次开发,多个 AI 工具复用”,而 Function Calling 更适合“快速给某个模型加一个原子能力”。
如果你用 Cline,它的 MCP 配置在设置面板的 MCP Servers 区域,填 command 和 args 即可,Base URL 和 Key 走 Cline 自己的模型配置。Cline 同时支持 Function Calling 和 MCP,所以你可以两种都开,让模型自己选。
4. 验证请求:同一 Key 下两种调用的返回对比
配置写完,必须验证。这一节我用同一个 TaoToken Key,分别发一次 Function Calling 请求和一次 MCP 工具调用,把返回结构摊开对比。
先验证 Function Calling。用上面的 curl 命令,实际返回(截取关键部分)大致是:
{ "id": "chatcmpl-xxx", "choices": [ { "index": 0, "message": { "role": "assistant", "content": null, "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"北京\"}" } } ] }, "finish_reason": "tool_calls" } ] }看到finish_reason是tool_calls,说明模型决定调工具而不是直接回答。你的代码拿到这个返回后,需要执行get_weather("北京"),然后把结果作为role: "tool"的消息再发一次请求,模型才会生成最终的自然语言回答。这是两轮请求,Function Calling 的上下文管理靠开发者自己串。
再验证 MCP。MCP 的验证不在 HTTP 请求层,而在 MCP Client 和 Server 的握手层。启动 Claude Code 后,它会自动连接mcp.json里配置的 Server。你可以在对话里直接说“帮我查一下订单 12345 的状态”,Claude Code 会先通过 MCP 协议发tools/list拿到query_order的描述,然后模型决定调用,Client 再发tools/call给 Server,Server 执行后返回文本结果。整个过程对用户是一次对话,但底层是 JSON-RPC 的多轮消息。
如果你想手动验证 MCP Server 是否正常,可以用 MCP Inspector:
npx @modelcontextprotocol/inspector node /Users/you/mcp-servers/order-server.js打开浏览器界面后,点“List Tools”应该能看到query_order,点“Call Tool”填入{"order_id":"12345"},返回里会有content数组。这一步能过,说明 MCP Server 本身没问题,剩下的就是 Client 配置。
两种验证的差异很明显:Function Calling 的验证是“看模型有没有生成正确的 tool_calls”,MCP 的验证是“看 Server 有没有正确响应 tools/list 和 tools/call”。前者验证模型能力,后者验证协议实现。如果你发现模型不调工具,先检查 tools 数组的 description 是否清晰;如果 MCP 工具不出现,先检查 Server 是否启动成功、mcp.json路径是否正确。
实测下来,同一个 Key 在两种调用下都能正常计费和返回,说明 TaoToken 的入口对两种模式是透明的。你不需要为 MCP 单独申请 Key,也不需要为 Function Calling 换 Base URL。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
这一节按真实报错来。你在配 Function Calling 或 MCP 时,大概率会撞上下面几个。
第一个,401 Unauthorized。这个最常见,原因是 Key 没填对或者 Base URL 写错。检查auth.json里的OPENAI_API_KEY是不是sk-开头,有没有多余空格。MCP 场景下检查mcp.json的env里 Key 有没有传进去,有些工具不会自动继承系统环境变量,必须在env里显式写。如果 Key 确认没错还是 401,去 https://taotoken.net/api-keys 看这个 Key 是否被禁用或额度耗尽。
第二个,local proxy failed。这个报错通常出现在本地工具有代理层的时候,比如某些工具会起一个本地转发。原因一般是 Base URL 填了https://taotoken.net/api/v1导致路径重复,或者工具本身要求 Base URL 不带/v1。把 Base URL 改成https://taotoken.net/api再试。另外检查系统环境变量里有没有残留的HTTP_PROXY,有的话清掉。
第三个,reading choices相关报错,比如Cannot read properties of undefined (reading 'choices')。这说明返回体里没有choices字段,通常是请求根本没到模型层,或者返回的是错误对象。先看完整返回体,如果是{"error": {...}},按 error message 排查。常见原因是 model ID 写错,比如把claude-sonnet-4-20250514写成了不存在的名字。去 https://taotoken.net/models 核对准确的 Model ID。
第四个,OAuth 相关报错。有些工具(比如某些 Claude Code 版本)默认走 OAuth 登录而不是 API Key,这时候你填了 Key 也没用,它会尝试走 OAuth 流程然后失败。解决办法是在工具设置里显式切换到 API Key 模式,或者设置环境变量ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL指向 TaoToken。Claude Code 的接入文档在 https://taotoken.net/doc 有详细说明,包括怎么关掉 OAuth 走 Key。
第五个,MCP Server 启动了但工具不出现。先确认mcp.json的command和args路径是绝对路径,相对路径在不同工作目录下会失效。然后手动跑一遍node /path/to/server.js,看有没有报错。如果 Server 正常但 Client 看不到工具,检查 Client 的 MCP 日志,Claude Code 的日志在~/.claude/logs/下。还有一个坑是 MCP Server 的tools/list返回格式不对,必须严格符合 JSON-RPC 2.0,inputSchema必须是合法的 JSON Schema。
第六个,Function Calling 返回了 tool_calls 但你的代码执行后模型不继续回答。这是因为你第二次请求时没有把 tool 结果正确回传。第二次请求的 messages 里必须包含原始 user 消息、assistant 的 tool_calls 消息、以及 role 为tool的结果消息,且tool_call_id要和第一次返回的id对上。少一个字段模型就不知道你在回应哪个调用。
这些报错覆盖了 90% 的接入问题。排障时记住一个原则:先确认 Key 和 Base URL,再确认 Model ID,最后确认请求体结构。三步都过还不行,去 https://taotoken.net/doc 对照接入文档逐项检查。
6. 该用哪种:判断标准与统一 Key 的长期价值
回到最初的问题:MCP 和 Function Calling 怎么选。我的判断标准很简单,看三个维度。
第一,看工具是否需要跨 AI 工具复用。如果你写的工具只给一个模型用,Function Calling 够了,写个 tools 数组,模型返回参数,你执行。如果你希望同一个工具在 Claude Code、Cline、Cursor 里都能用,写 MCP Server,一次开发到处接入。MCP 的协议层价值就在这里,它把工具描述和执行从模型请求里解耦出来。
第二,看调用是否需要多轮上下文和状态。Function Calling 是单次请求-响应,模型生成调用意图后,执行和后续对话由开发者串。MCP 支持多轮、支持 Server 侧维护状态,适合需要连续交互的场景,比如先查订单再改地址再确认。如果你的任务是一锤子买卖,Function Calling 更轻。
第三,看安全边界。MCP 的数据本地化处理和用户授权控制更细,适合访问本地文件系统、数据库这类敏感资源。Function Calling 的权限管理依赖 API Key,粒度粗一些。如果你要接的是生产库,MCP 的授权层更可控。
但两者不是替代关系。实际工程里,MCP Server 内部可以用 Function Calling 来让模型决定调哪个子工具,Function Calling 的执行结果也可以回传给 MCP Client 做后续编排。分层协作才是常态:MCP 管连接和协议,Function Calling 管模型侧的意图生成。
统一 Key 的长期价值在于,不管你用哪种方案,计费、限流、模型切换都在一个地方。今天用 Function Calling 跑通了,明天想换成 MCP,Key 不用换,Base URL 不用换,只改工具配置。这对本地 AI 工具的长期维护来说,省掉的是每次换模型就要重新配一遍的麻烦。
如果你还在选型阶段,建议先用 Function Calling 跑一个最小工具,感受一下模型生成参数的过程。然后把这个工具改写成 MCP Server,对比一下配置量和复用性。跑完这两步,你自然就知道下一个工具该用哪种方式了。需要长期跑编码 Agent 的话,Coding Plan 的入口在 https://taotoken.net/coding-plan ,模型对话验证在 https://taotoken.net/models ,接入文档在 https://taotoken.net/doc ,按需取用。