上个月我帮某团队把一个内部知识库接进大模型做问答最初只写了三个函数调用Function Calling查文档、查权限、取摘要模型用起来很顺。可等到要接第五个、第八个工具时代码开始变得很难收拾我不得不停下来认真想一个问题——到底是继续在 Function Calling 这条路上修修补补还是干脆上 MCPModel Context Protocol这套新协议。这篇文章不打算给你一份可以照抄的代码模板而是把我在这次选型里的判断逻辑、实测数据和踩坑过程完整摊开最终聊清楚一件事给AI装手脚到底值不值。1. 为什么给AI装手脚成了绕不开的话题1.1 大模型天生只有脑子没有手脚所有用过纯对话模型的人应该都有同感模型再聪明面对帮我查一下订单表里最近七天的退款率这种请求时它也只能给你一段写好的 SQL而不是真的去查。原因很简单大模型本质是一个概率推理引擎它不知道你的数据库在哪、API 的鉴权怎么带、返回结果长什么样。它缺的不是智力而是一套能触达外部世界的执行通道。所以过去两年出现了两条技术路线。一条是把工具以函数定义的形式塞给模型让它在推理时自己决定调用哪个函数再把你执行完函数后的结果喂回去这条路线就是被普遍称为 Function Calling 的能力。另一条是近一年逐渐火起来的 MCP思路完全换了一个层面不追求让模型自己认识每一个工具而是定义一套工具服务的接入协议让模型所在的应用通过统一客户端去发现、调用远端的各种能力。这两条路线解决的是同一个问题但一个是在模型侧用力一个是在架构侧用力。要判断哪个值不能只看演示视频里的效果得回到自己项目的规模、团队和维护场景里去算账。1.2 两个方案的本质区别先记住一句话用一句话概括我的理解Function Calling 是给模型定制一双手套手套是照着你这双手的尺寸做的戴起来服帖但换一个人就没法用MCP 是给工具定一套统一的插头标准插座是标准的任何符合标准的设备插上就能用但前提是你得接受这套标准的约束也得布置插座本身。这个区别决定了后续所有争论。Function Calling 的优势在于近——它和模型推理主循环紧紧耦合几乎没有额外跳转调试起来也直观。MCP 的优势在于通——它把工具提供方和工具使用方彻底解耦一个宿主应用可以接无数个工具服务工具服务也可以被不同应用复用。听起来 MCP 明显更高级但通的代价是协议、进程、网络、鉴权、版本管理这些工程问题会全部浮出水面。值不值实质是在问你当前的工具规模能不能消化掉这些工程开销。2. Function Calling 的本质与天花板2.1 它是怎么工作的一个最简示例先把 Function Calling 的机制讲透。它的核心是把工具的 JSON Schema 声明在请求里模型在生成回复时如果判断需要某个工具就会返回一个结构化的调用意图——包含函数名和参数——而不是普通文本。应用程序拿到这个意图后自己执行对应函数再把执行结果作为一条新的消息回传给模型模型基于结果继续作答。拿最常见的查询天气举例你需要声明一个这样的函数定义{ name: query_weather, description: 查询指定城市的实时天气信息, parameters: { type: object, properties: { city: {type: string, description: 城市名称例如上海}, unit: {type: string, enum: [celsius, fahrenheit]} }, required: [city] } }请求时把这个 schema 放进工具列表模型就能在适当时机返回类似{name: query_weather, arguments: {\city\: \上海\}}的结构。应用侧的执行循环大致长这样messages [{role: user, content: 上海今天穿什么合适}] while True: resp llm.chat(messagesmessages, tools[WEATHER_TOOL_SCHEMA]) if resp.tool_calls: for call in resp.tool_calls: result execute_tool(call.function.name, call.function.arguments) messages.append({ role: tool, tool_call_id: call.id, content: json.dumps(result, ensure_asciiFalse) }) continue break # 没有工具调用说明模型要直接回复了 print(resp.content)这套机制本身不复杂实测下来单次工具调用的额外开销几乎可以忽略因为工具调用的往返是紧跟在大模型推理之后的没有多余的中间节点。对于三五个工具的小型应用这几乎是零成本的方案。2.2 真正困住它的不是性能是集成债Function Calling 的天花板不在推理速度上而在工程维护上。当你从三个工具涨到二十个工具而且这些工具分别来自订单系统、库存系统、客服工单、数据仓库的时候问题就来了。第一个问题是 N 乘 M 的集成负担。每个外部系统都需要你单独写一层适配代码鉴权方式不同有的用签名有的用 OAuth有的用内部 token返回结构不同有的直接返回 JSON有的要解析分页和错误码接口版本也不同对方升级一个字段你的适配代码就要跟着改。所有工具的逻辑都堆在应用代码里新增一个工具意味着改代码、走发布流程、重新联调这个成本是线性甚至指数上涨的。第二个问题是上下文膨胀。每个工具的 JSON Schema 都会被序列化进模型请求工具一多光 schema 就可能吃掉几千甚至上万 token。我们头一次把八个工具的完整定义一次性塞进上下文时明显感觉到模型在非工具相关的问题上变懒了回答质量下降排查半天才发现是上下文被工具定义塞满了。第三个问题是权限边界模糊。函数一旦暴露给模型模型可能在多轮对话里用各种方式组合调用它们这时候你没法在单个函数里做全局的权限校验。谁允许查哪些库、哪些字段不能返回这些策略散落在各个函数实现里审计起来非常痛苦。2.3 什么时候它是够用的说了这么多我并不是要否定 Function Calling。如果你的场景满足三个条件——工具数量少而固定、所有工具只被当前一个应用使用、单次调用延迟敏感——那 Function Calling 依然是最合理的选择。实现简单、调试直接、无额外进程和网络依赖这些优势在项目早期特别值钱。我见过不少团队在只有两三个工具的阶段就盲目上 MCP结果协议学习成本和架构复杂度把一个小项目压得喘不过气这是典型的过度设计。3. MCP 到底改了什么从认识工具到连接世界3.1 三个角色和三种原语MCP 把所有东西重新拆成了三个角色。宿主Host是你的 AI 应用本身它负责发起对话、调用模型、把工具结果交给模型客户端Client是宿主内部的一个连接器负责和远端维护会话服务端Server是真正提供工具的一方它把能力包装成标准接口。你可以把 MCP 理解成一套工具界的 USB 标准Server 是各种外设Host 是电脑Client 是操作系统里的驱动框架。这套标准定义了三种能力原语工具Tools是可执行的函数模型通过宿主调用它来完成动作资源Resources是可读取的数据对象类似文件或数据库记录通过 URI 定位提示词Prompts是可复用的提示模板。大多数入门项目只用到 Tools但 Resources 和 Prompts 在复杂场景里能把让模型知道有什么数据可用这件事也标准化。3.2 一次 MCP 接入长什么样实践里接入一个 MCP Server 的体验和配置一个数据源差不多。以连接一个内部订单查询服务为例先定义好 Server 的连接配置{ mcpServers: { order-service: { command: python, args: [order_server.py], env: { ORDER_API_BASE: http://internal-order-api.example, ORDER_API_TOKEN: ${ORDER_API_TOKEN} } } } }然后在宿主应用里通过客户端 SDK 连接它列出并调用工具from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async with stdio_client(StdioServerParameters( commandpython, args[order_server.py] )) as (read, write): async with ClientSession(read, write) as session: tools await session.list_tools() print([tool.name for tool in tools]) result await session.call_tool( query_refund_rate, {days: 7, region: east} ) print(result.content)注意这里一个关键变化宿主应用不再关心query_refund_rate到底调用了哪个内部 HTTP 接口、签名怎么算、返回怎么解析这些全被封装在 Server 里。Server 的开发者只需要遵循 MCP 协议把自己的内部能力暴露出来宿主的开发者只需要遵循协议去连接任意 Server两边不再需要互相知道实现细节。3.3 它真正解决的是生态与分工问题MCP 带来的最大改变不是技术是分工。没有 MCP 之前每个工具接入方都要自己面对 N 个外部系统的差异有了 MCP 之后外部系统的能力只需要被封装一次任何支持 MCP 的宿主应用都能直接复用。这意味着工具的开发者和 AI 应用的开发者第一次可以完全并行工作工具方不需要理解你的模型用的是什么应用方也不需要理解工具的鉴权细节。这一点在团队协作里感受特别明显。我们后来把数据查询、告警通知、工单流转分别封装成了三个 MCP Server业务组只需要维护各自的 Server应用组只需要维护 Host 和 Client 这层公共底座两边通过协议契约交付联调冲突一下子少了很多。4. 值不值的账本我把四个维度单独拆开算4.1 开发效率账我拿手头一个真实项目做了横向对比接入五个内部工具再模拟接入二十个横跨四个系统的工具。结果如下表这是我个人项目的实测参考值不同团队会有差异但量级可以参考对比项Function Calling五个工具MCP五个工具Function Calling二十个工具四个系统MCP二十个工具四个系统初始接入开发量约 1~2 天约 2~3 天含学习协议约 8~12 天约 4~6 天新增一个工具的耗时1~2 小时含改代码发版约 1~2 小时1~2 小时 发布窗口0.5~1 小时新增 Server 配置跨系统复用能力无各应用各写一套有Server 可被多宿主复用无维护成本随应用数翻倍有一次封装多处使用结论很明确工具少的时候MCP 的协议学习成本和初期配置反而拖后腿但一旦工具数量超过十到十五个或者同一个工具需要被多个应用使用MCP 的开发效率优势立刻反超。这个拐点比我想象的来得更早。4.2 运行开销账技术选型不能只看开发还得看运行时开销。Function Calling 在主推理循环里直接执行MCP 则多出宿主到 Server这一跳这一跳可能是本地进程间通信也可能是远程网络请求。对比项Function CallingMCP本地 stdioMCP远程 HTTP单次工具调用额外延迟几乎为 0约 10~50ms约 100~300ms与网络环境强相关是否引入独立进程否是是上下文 token 消耗所有 schema 一次性注入可动态按需加载部分工具可动态按需加载部分工具本地 stdio 模式的额外延迟基本可以忽略但远程模式的网络跳转在工具链特别长时会放大。我实测过一个五步工具链的复杂任务Function Calling 的总耗时约 6 秒MCP 远程模式约 8.5 秒差出来的两块多秒几乎全在网络往返上。如果你的场景是实时客服这类对首包延迟极其敏感的业务这个差距需要考虑进去。4.3 维护成本账维护成本是账本里最难量化但往往最致命的一项。Function Calling 的维护痛点是每个新工具都要发版而且工具逻辑和应用逻辑一旦耦合后续无论改工具还是改应用都要做全量回归。MCP 的维护痛点则是另一面协议版本在快速演进Server 和 Client 的 SDK 版本必须对齐跨进程排查问题比单进程难得多。比较下来我的体感是Function Calling 的维护成本是均匀分布的每次改动都不大但频次高MCP 的维护成本是尖峰分布的平时很省心但遇到协议升级或连接异常时一次排障可能就要耗掉一整天。两种痛不一样你得想清楚自己的团队更怕哪种。4.4 安全与权限账安全维度上两者各有侧重。Function Calling 的安全边界在函数即权限——你的代码里写了什么函数模型就只能碰什么这个边界直观、容易审计适合单机或内部小范围使用。MCP 的安全边界在Server 即信任域——每一个 Server 都是一个独立的程序它可能在本地启动也可能在远端运行你连接的每个 Server 都需要单独做信任评估。远程 MCP Server 的安全问题尤其要注意传输层的加密、Server 自身的鉴权、允许模型调用哪些工具、高危工具比如删除操作、写操作的单独授权这些东西都要在架构里提前设计。我见过一个团队把所有内部 Server 都接进来之后才发现某个开发环境的信息泄露 Server 被随意挂在了公网这属于架构引入之后必须同步补齐的安全功课。协议本身还支持标准的授权流程但启用和配置的责任在你自己团队身上。5. 我的选型建议什么场景别折腾什么场景值得上5.1 三个场景三条路经过这次实际项目的对比我给团队的建议分成了三种情况。第一工具数量在五个以内、只服务于当前应用、不需要跨团队交付老老实实用 Function Calling。这个阶段追求的是快和稳任何额外的架构层次都是负担。我一个朋友在个人博客里加了最近文章和站点统计两个工具用 Function Calling 半小时就搞定让他上 MCP 纯粹是浪费时间。第二工具超过十个、分布在多个系统、或者你做的是面向多 Agent 的平台底座值得上 MCP。这种情况的核心诉求已经从让模型调用工具升级为让整个组织高效地生产和管理工具MCP 的分工优势会直接转化为交付速度优势。第三还有一种混合路线也是我目前最推荐的在模型对话的核心回路里继续用 Function Calling 管理少量高频工具把那些低频、长尾、跨系统的工具统一用 MCP Server 包起来通过一个桥接函数暴露给模型。这样既保住了高频路径的低延迟和调试便利又拿到了 MCP 的长尾生态和复用能力。桥接函数本身也就是一个普通工具def bridge_mcp_tool(tool_name: str, arguments: dict): 把 MCP Server 里的某个工具包装成函数调用入口 return mcp_client.call_tool(tool_name, arguments)5.2 判断清单可直接抄作业如果你现在正在选型我建议直接用下面五个问题过一遍基本就能得出答案工具总数会不会超过十个不会倾向 Function Calling会且增速快考虑 MCP。同一个工具是否需要被多个应用或 Agent 复用是MCP 解耦价值大否Function Calling 更直接。单次工具链的延迟是否敏感非常敏感优先 Function Calling 或本地 stdio 模式 MCP。团队里是否有专门的工具维护方有独立工具团队MCP 的分工模型很合适大家既写应用又写工具反而可以先用 FC 统一管理。是否有跨团队、跨系统的整合需求这是 MCP 的强项场景别犹豫。这套清单不是纸上谈兵而是我这次项目里实实在在踩过界之后提炼出来的。你可以不信我的账本但这五个问题会逼你把最关键的变量提前想清楚。6. 生产环境实测后才知道的坑6.1 坑一工具一多上下文先爆了这是我在 MCP 迁移过程中遇到的第一个坎。刚开始我把二十来个工具全部让 Server 暴露出来宿主统一 list 之后全挂进模型上下文结果单次请求的 token 消耗直接翻倍模型反而在简单任务上频频出岔子。后来想明白一件事MCP 让你接入工具极度容易但能接入不等于该全量加载。解决方案是两级过滤。第一级在 Server 端按业务域把工具拆到不同 Server不是所有任务都连所有 Server第二级在 Host 端根据对话内容做一个简单的意图预判只把相关性最高的工具 schema 注入当前请求。实测下来同样的二十个工具场景加上两级过滤后上下文 token 消耗减少了约一半模型在非工具问题上的回复质量也明显回升。6.2 坑二远程 MCP Server 的鉴权与超时要一起设计远程模式的坑比本地模式深得多。本地 stdio 模式下Server 进程由 Host 拉起环境变量可以直接从父进程继承鉴权相对简单远程模式下Server 部署在另一台机器上Host 和 Server 之间是真实的网络会话超时、重试、连接池这些老问题全部回来了。我遇到的实际问题是某个远程 Server 偶尔响应超过 30 秒模型那边已经等不及开始胡言乱语了而 Host 这边还在傻等。后来给 Client 层加了超时控制和快速失败机制把工具调用的超时从默认值缩短到了具体场景需要的阈值同时在 Server 端给查询类工具加上缓存把 P95 响应时间从 20 多秒压到了 3 秒以内。这个教训是设计工具本身的同时一定要把网络语义一并设计进去超时、重试、幂等、降级一个都不能少。6.3 坑三协议还在快速演进版本锁定很重要MCP 是个还在快速迭代的协议这一点既是活力也是风险。我们最初用的某个版本 SDK在升级协议版本后出现了工具参数类型的兼容性问题排查了很久才发现是宿主和 Server 两端的 SDK 版本不一致导致的。从那以后我们立了一条规矩宿主和所有 Server 的协议版本、SDK 版本必须写进系统清单升级必须走统一的发布窗口不允许任何人单独升级。这条规矩看起来保守但在工具规模大了之后特别值钱。协议底下的一个小变更可能让十几个 Server 同时失效而每个 Server 的维护者分散在不同团队如果没有统一的版本管理排障成本会高到难以承受。6.4 坑四工具幻觉比想象中更难排查模型在 Function Calling 里偶尔会给出错参数的调用这在 MCP 场景里被放大了因为工具的提供方和应用方已经分离模型一旦调用错参数错误信息要跨过进程、甚至跨过网络才能回到你的视野。有一次模型把订单查询的日期参数从下单日期传成了发货日期Server 端报了空结果排查时我们一度以为是 Server 的问题后来靠加日志链路追踪才定位到是模型生成参数时的歧义。对这个问题我的经验是两件事一是给每个工具的参数 description 写得足够细明确字段口径能有效降低模型的误传概率二是在宿主侧建立跨进程的链路追踪给每次工具调用一个 trace id把模型生成的原始参数、Server 收到的参数、最终返回结果串成一条链出了问题能一查到底。否则你在嵌套了几层工具的复杂任务里会淹没在不知道是哪一环出错的深渊里。这次从 Function Calling 到 MCP 的迁移让我对值不值有了一个很具体的答案如果你的工具只是几个孤立的 API不值得如果你的目标是把 AI 应用做成一个能不断接新能力的平台值得而且越早越省。以我个人的体会最舒服的做法是让两者共存Function Calling 做主力出口MCP 做后备工具总线中间用一层薄的桥接层隔开既不被单一协议的复杂度绑架也不被集成债拖死。最后分享一个小技巧无论你选哪条路都别忘了给你的每个工具写清楚什么情况用、参数什么口径、失败怎么反馈这三行字能帮你省下后面数不清的排障时间。