mcp 和 function call 的区别

接入与开发

讲清 MCP 与传统函数调用在工程落地上的差别:连接方式、能力发现、超时并发、降级与日志该怎么分别设计。

很多人第一次写工具调用,是在请求体里塞一个 tools 数组,模型返回参数,自己再执行函数。后来接触到 MCP,发现多了一层服务进程和连接管理,就困惑:这不还是调函数吗,为什么要多绕一圈?这篇不重复讲概念入门,而是从开发者的实际排错经验出发,把两者的连接模型、能力发现方式、超时与并发处理、失败降级路径、日志要记什么逐条对照,给你一份能直接照着改代码的判断清单。

很多人第一次写工具调用,是在请求体里塞一个 tools 数组,模型吐回一段参数,自己再执行本地函数,跑通就完事。后来接触到 MCP,发现多了一层服务进程和连接管理,脑子里第一反应就是:这不还是调函数吗,为什么要多绕一圈?

这篇不重复讲概念入门,而是从实际排错的经验出发,把两者的连接模型、能力发现方式、超时与并发处理、失败降级路径、日志要记什么逐条对照。你可以直接当成一份改代码前的判断清单,先想清楚自己的场景落在哪一档,再决定要不要引入那一层进程。

先分清谁在调谁:调用链上多了哪一层

函数调用是「模型吐参数、你的进程执行本地函数」,从头到尾都在你自己的进程空间里完成,没有网络、没有握手、没有额外进程。MCP 则是「模型吐调用意图、客户端通过一条连接把意图转发给独立服务进程去执行」,中间实实在在多了一次跨进程通信和一套连接生命周期。

最直接的判断依据就一条:执行体到底在不在你这个进程内。本地纯计算、读写自己内存或自己进程已经持有的句柄,函数调用更直接,没必要加中间层。反过来,如果这个能力要跨语言复用、要跨机器部署,或者同一套能力要同时喂给多个客户端,MCP 的独立进程才划算。

  • 执行体在自己进程内、只在一种语言里跑:优先函数调用
  • 执行体需要被多语言、多客户端复用:考虑 MCP
  • 调用方和实现方由不同人维护:MCP 的契约边界更清楚
  • 需要动态增减工具但不希望改调用方代码:MCP 更顺手

能力是写死的还是可发现的

函数调用模式下,工具定义通常由你在代码里拼进请求参数,模型当次能看到哪些工具,完全由你这一次的组装决定。工具集变了,就得改代码、重新部署。它的好处是确定性强,坏处是变更要动调用方。

MCP 的客户端可以先向服务端询问它能提供哪些工具,再从中挑选要暴露给模型的部分。工具集增删不必同步改调用方代码,能力边界由服务端自己声明。代价是你在运行时要多处理一步发现动作,以及发现结果和缓存的一致性。

对比项函数调用MCP
工具定义来源调用方代码里写死服务端声明后由客户端发现
工具集变更改代码并重新部署服务端改,调用方通常不动
模型看到什么由本次请求组装决定由客户端筛选后决定
确定性高,所见即所传略低,要多处理发现与缓存
适合场景工具长期稳定工具需要动态增减

那什么时候这个差别不重要?工具集长期稳定、只有两三个函数的场景。这种情况下动态发现带来的复杂度——缓存什么时候失效、发现失败怎么兜底——往往超过它替你省下的事。判断标准就是:过去一段时间里你的工具定义改动过几次,如果基本没动过,就不必为动态发现买单。

连接生命周期:一次请求 vs 常驻会话

函数调用是短连接思维,每次对话重新描述一遍工具、重新给一遍上下文,两轮之间没有任何会话状态可言。你把参数校验写好、把函数执行完就结束了,不存在「保活」这件事。

MCP 是长连接思维。进程拉起来之后有初始化、能力协商、保活与断开,开发中真正耗时间的往往不是把调用调通,而是处理断线重连和启动时序——客户端先起来了服务端还没就绪,或者服务端重启后客户端还拿着旧连接。

  1. 第一步:确认进程先确认服务进程是否真的在运行、端口是否在监听。这一步没过,后面所有的握手和调用排查都是白费。
  2. 第二步:看握手确认初始化与能力协商是否完成,客户端拿到的工具清单是否为空。清单为空通常不是没工具,而是握手没走完。
  3. 第三步:再看调用只有前两步都正常,再去纠结工具参数和返回。反过来从上往下查,很容易越查越远,最后发现只是进程没起来。
MCP 的多数「工具没反应」,根因都在连接层而不在工具本身。

超时该分几档:别再用一个总超时兜住所有环节

用一个总超时兜住整条链路,是最常见也最难查的写法。函数调用至少要拆出「模型生成参数」和「本地函数执行」两段;如果本地函数里还有外部请求,那要再拆一层。哪一段慢、哪一段超,日志里必须能分辨出来。

MCP 在此基础上还要多出连接建立、握手和服务端执行三段。这三段任何一段卡住,外部表现都像是「工具没反应」,但你要改的地方完全不同:连接段问题要去看网络和进程,握手段问题要去看版本协商,执行段问题才轮到工具逻辑。

具体秒数没有通用答案,取决于你的模型服务、网络质量和服务端本身。建议先到 /tools/speed-test 量出你自己链路的延迟基线,再按基线留出余量,而不是拍脑袋写一个数。另外,慢工具不要靠单纯加大超时硬扛,宁可改成异步提交加轮询,把长等待从同步链路里挪走。

  • 为每一段单独设超时,而不是设一个总和
  • 超时触发时记录是哪一段超的,这是排查依据
  • 执行段超时要区分是工具真的慢,还是它卡在下游
  • 能改成异步的慢工具,优先异步,不要用超时兜

并发与状态:从无状态函数到有状态进程

本地函数调用基本没有并发概念,你自己控制调用顺序就行,函数之间互不干扰。MCP 服务进程则是有状态的,多个会话同时打进来时,要考虑工具执行是否串行、共享资源要不要加锁、一个会话的失败会不会污染另一个会话。

一个常见的坑是:一个服务进程被多个客户端共用,其中某个工具执行时间长,把连接占满了,别人的调用就进不来。表现是「刚才还好好的,突然全都调不动」。解决办法是给长任务单独开队列或独立进程,不要和轻量工具挤在同一条通道上。

维度函数调用MCP
状态基本无状态服务进程有状态
并发控制调用方自己排顺序要考虑会话隔离与资源竞争
典型故障函数内部报错一条慢连接拖垮其他会话
应对拆函数、加校验拆队列、拆进程、限制单会话占用
判断标准调用之间是否有隐含依赖同一工具连续调用是否有先后要求

判断标准其实很简单:同一个工具连续两次调用之间有没有隐含依赖。有依赖就老老实实串行,别为了压测数字好看去并发,那只会让偶发问题变得更难复现。

失败之后往哪退:两条不同的降级路径

函数调用失败时,退路通常是「把错误原样告诉模型,让它换个参数再试一次」。因为参数和执行都在你手里,重试成本低,也不涉及外部状态,模型往往第二次就绕过去了。

MCP 失败则要先区分是连接层挂了,还是工具本身报错了。这两类错误的重试策略完全不同:连接层挂掉时,重试通常没用,应该降级成不带该工具的普通对话,先保证主流程能走;工具本身报错,才值得让模型换参数再来一次。把两者混在一起统一重试,只会放大故障。

  • 连接层错误:不重试,直接降级到无工具模式
  • 工具执行错误:可以把错误交回给模型重试
  • 参数校验错误:属于调用方问题,修正定义后再上线
  • 不确定的错误:先记录再降级,别着急自动重试

日志要记哪几个字段,才能复盘一次工具调用

工具调用出问题,事后能救你的往往是日志字段够不够。至少要记:会话标识、工具名与版本或摘要、入参、出参或错误码、每段耗时、是否发生过重试。缺了入参和分段耗时,复盘时基本只能靠猜。

MCP 还要额外记连接标识和握手结果。否则同一个工具名在不同连接上的表现差异,你根本分不清是工具行为不一致,还是某条连接本身有问题。这一点在排查「偶尔失败」类问题时尤其关键。

  • 会话标识:把同一轮对话的多次调用串起来
  • 工具名与版本摘要:区分定义变更前后的行为
  • 入参与出参:脱敏后再落库,便于复现
  • 分段耗时:对应前面拆出的每一档超时
  • 重试标记:看出是不是靠重试才成功
  • 连接标识与握手结果:MCP 场景必记

字段怎么选,也可以参考 /mcp 里的服务端条目,对照它们对外暴露的能力粒度来定你的日志表结构。粒度对不上,日志再全也用不上。

选型对照表:按这几个条件对号入座

把前面的判断收敛成几条条件,落到自己场景里就能对号入座。不用追求一步到位,很多团队其实处在中间状态,先把结构设计对,以后要包一层服务端并不难。

你的情况建议
工具在自己进程内、只有一个调用方函数调用
工具集长期稳定、只有两三个函数函数调用
同一套能力要被多个客户端或多种语言复用MCP
工具集需要动态增减MCP
现在简单、以后可能要复用先抽独立模块,再包服务端
团队没人维护常驻服务或不能开端口不要上 MCP

中间状态其实很实用:把工具执行抽成独立模块,参数校验和日志按 MCP 的粒度设计好,以后要迁移时,外面包一层服务端就行,前面的判断和记录都不用推翻重来。

也有明确不适用的情况:团队里没人愿意维护常驻服务,或者部署环境不允许开监听端口,那 MCP 的连接模型就是纯粹的负担,这时候老老实实用函数调用更稳。

下一步先量清楚自己链路的真实延迟基线,再决定超时怎么分层、要不要引入常驻进程;有了基线,前面这些判断才不会停留在纸上。

常见问题

MCP 是不是就是函数调用的升级版,学了 MCP 就不用管函数调用?

不是替代关系。MCP 解决的是「工具从哪来、怎么被发现和复用」,真正执行时底层还是函数调用那一套:模型给参数、程序执行、结果回传。所以函数调用的参数校验、错误回传、超时控制这些基本功,在 MCP 里一样要用,只是执行体挪到了另一个进程。

我只想给自己的项目加两个工具,有必要上 MCP 吗?

多数情况没必要。工具数量少、只有一个调用方、工具集短期内不变,直接在请求里写工具定义最省事,少一个要拉起和保活的进程就少一类故障。等出现「同一套工具要给第二个客户端用」或者「工具集需要热插拔」时再迁,迁移成本也不会因为你晚做而变大。

为什么我的工具调用有时候没反应,日志里也看不到报错?

先分清卡在哪一段。函数调用常见是模型没按格式返回参数,或本地函数在等一个外部请求;MCP 还多出连接没建立、握手没完成这两种可能。按「服务进程是否在跑 → 连接是否握手成功 → 参数是否合法 → 执行是否超时」的顺序查,每段单独打耗时,别用一个总超时兜住全部环节。

工具调用的并发该怎么设,越高越快吗?

不是。并发上限取决于最慢的那个外部依赖,而不是你本地能开多少协程。工具之间有隐含依赖时必须串行,共享同一账号或同一配额的接口要留出退避空间。稳妥做法是先按较低并发跑通,观察错误率和尾延迟,再一档一档往上加,加不动就说明瓶颈在下游而不在你的调用端。

工具多了之后模型经常选错工具,是 MCP 的问题吗?

通常不是连接方式的问题,而是工具描述的重叠度问题。两个工具功能相近、描述又都写得很泛,模型只能靠猜。做法是把工具名和描述写具体,明确各自适用和不适用的情况,工具总数控制在一个模型能稳定区分的范围内,必要时按场景分组、只挂当前相关的那几个。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。