在快速发展的AI编程助手领域,有两个概念经常被一起提及,甚至被一些开发者和技术文章所混淆:Function Calling 和 Model Context Protocol (MCP)。是不是一定要LLM具备Function Calling能力,才能有效利用MCP与外部工具交互呢?答案可能和你想象的不一样。今天,我们就来澄清这个常见的误解,并深入流行的VSCode插件——Cline的源码,看看它是如何巧妙解决这个问题的。
Function Calling vs. MCP:先厘清概念
- Function Calling (函数调用): 这通常指的是大语言模型(LLM)本身的一种能力。当用户用自然语言提出请求时,具备Function Calling能力的LLM能够理解这个请求,并自主判断是否需要、以及需要调用哪些预定义的外部工具(函数/API)来完成任务。它还能生成符合特定格式(通常是JSON)的调用参数。简单说,Function Calling是LLM决定“用哪个工具”和“怎么用”的智能决策过程。
- Model Context Protocol (MCP,模型上下文协议): MCP则是一个标准化的交互协议。它定义了LLM Agent应用(如Cline这样的编程助手)与外部系统(MCP Server,提供各种工具或数据源)之间应该如何通信。它规范了请求和响应的格式,确保双方能够顺畅地“对话”。MCP关心的是“如何传递工具调用指令和结果”,而不是LLM如何做出调用决策。
看到这里,你可能已经有点明白了:MCP负责的是“通信管道”的标准,而Function Calling是某些LLM拥有的“智能大脑”的一部分。理论上,只要LLM应用能按照MCP规定的格式发出请求,MCP就能工作,并不强制要求LLM本身具备原生的Function Calling能力。
普遍的误解:为什么会认为MCP依赖Function Calling?
误解的产生往往源于一个看似合理的逻辑链:
- MCP需要执行外部工具(tools)。
- 调用哪个工具、传入什么参数,这个“决策”需要LLM来做。
- LLM做出这种结构化决策并输出调用指令,这不就是Function Calling吗?
这个逻辑没错,但它忽略了一点:LLM产生“调用哪个工具以及如何调用”的结构化输出,不一定非要依赖其内置的Function Calling能力。
Cline的解决方案:强大的System Prompt
VSCode上广受欢迎的Cline插件就是一个很好的例子。它支持通过OpenRouter这样的平台接入上百种不同的LLM。显然,这些LLM中只有少数具备原生的Function Calling能力,但这并不妨碍用户在Cline中使用各种MCP Server提供的强大功能。
Cline是如何做到的呢?答案就藏在它的开源代码里,具体来说是src/core/prompts/system.ts
文件中的SYSTEM_PROMPT
函数。这个函数构建了一个极其详尽、长达近千行的系统提示词(System Prompt)。
这个System Prompt就像一份详细的说明书,告诉LLM:
- “你有权使用工具”: 明确告知LLM它可以使用一系列工具来完成任务。
- “工具调用的标准格式是这样的”: Cline定义了一种基于XML的工具调用格式,并清晰地展示给LLM。
<tool_name>
<parameter1_name>value1</parameter1_name>
<parameter2_name>value2</parameter2_name>
...
</tool_name>
<!-- 示例 -->
<read_file>
<path>src/main.js</path>
</read_file>
LLM被要求严格遵守这种XML格式来发起工具调用。
- “这是你可以用的内置工具”: System Prompt预定义了一些Cline的基础工具,如执行命令行(
execute_command
)、读文件(read_file
)、写文件(write_to_file
)等,并详细说明了它们的用途、参数和使用格式。 (有趣的是,根据源码注释,Cline的这几个基础工具调用是通过VSCode API实现的,并未走MCP通道)。
<!-- 执行命令示例 -->
<execute_command>
<command>npm install react</command>
<requires_approval>true</requires_approval> <!-- 某些操作需要用户确认 -->
</execute_command>
<!-- 写文件示例 -->
<write_to_file>
<path>src/App.js</path>
<content>
// 完整的JS代码内容...
</content>
</write_to_file>
- “如果你启用了MCP,还可以这样调用外部工具”: 当用户在Cline中配置并启用了MCP Server时,System Prompt会动态地加入两个关键的MCP工具调用指令:
use_mcp_tool
(使用MCP服务器提供的工具)和access_mcp_resource
(访问MCP服务器提供的资源)。
<!-- 调用MCP工具示例 -->
<use_mcp_tool>
<server_name>example-weather-server</server_name>
<tool_name>get_forecast</tool_name>
<arguments>
{
"city": "北京",
"days": 3
}
</arguments>
</use_mcp_tool>
<!-- 访问MCP资源示例 -->
<access_mcp_resource>
<server_name>example-weather-server</server_name>
<uri>weather://San Francisco/current</uri>
</access_mcp_resource>
- “这是已连接的MCP Server提供的具体能力”: 最关键的一步!如果用户连接了MCP Server,Cline会读取这些Server提供的工具(Tools)和资源(Resources)的描述、输入/输出模式(Schema)等信息,并将这些信息动态地插入到System Prompt中。
例如,如果连接了一个天气查询的MCP Server,System Prompt里就会包含类似这样的描述:
## example-weather-server (`node /path/to/weather-server/build/index.js`)
### Available Tools
- get_forecast: Get weather forecast for a city
Input Schema:
{
"type": "object",
"properties": { ... }, // 详细的参数定义
"required": ["city"]
}
### Resource Templates
- weather://{city}/current (Current weather for a given city): ...
### Direct Resources
- weather://San Francisco/current (Current weather in San Francisco): ...
通过这种方式,即使LLM本身没有Function Calling能力,它也能通过阅读这份极其详尽的System Prompt,“学习”到当前可用的所有工具(包括来自MCP Server的)以及如何按照规定的XML格式来请求调用它们。Cline的后端代码再解析这个XML输出,转换成真正的MCP请求发送出去。
System Prompt 方式的利弊
Cline的这种做法非常聪明,它以兼容性换取了一些潜在的代价:
- 优点:
- 极高的兼容性: 几乎可以适配任何能理解并遵循指令的LLM,无论其是否具备原生Function Calling能力。这使得用户可以通过OpenRouter等平台自由选择模型。
- 缺点:
- 巨大的Token消耗: 近千行的System Prompt本身就占用了大量的上下文窗口(Context Window)。随着连接的MCP Server增多,这个Prompt还会进一步膨胀(可能达到60KB甚至更多)。对于那些Token限制较低的模型,留给用户输入和对话历史的空间就非常有限了。
- 潜在的效率和准确性问题? 有人可能会担心,强制LLM通过阅读长Prompt来模拟Function Calling,会不会比原生能力效率低、更容易出错?
System Prompt 效果如何?意外的发现
关于上述缺点的第二点,一个有趣的参考来自 Berkeley Function-Calling Leaderboard。这个排行榜评估了各种LLM在Function Calling任务上的表现。值得注意的是,排行榜也将“使用System Prompt引导LLM进行工具调用”的方式纳入了比较。
结果有些出人意料:对于像OpenAI的GPT-4o和o1这样的顶尖模型,使用System Prompt的方式在某些测试维度上,表现甚至优于或显著优于它们自身的原生Function Calling能力!
GPT-4o: Function Calling (FC) vs. System Prompt (Prompt)
当然,对于大多数其他LLM来说,原生Function Calling的表现通常还是更好一些,但差距并没有想象中那么大。这表明,只要System Prompt设计得足够好、足够清晰,通过Prompt引导的方式完全可以达到非常理想的工具调用效果。 Cline的做法在平衡通用性和准确性上是相当成功的。
(注:Cline团队未来或许可以考虑优化,比如先检测LLM是否支持原生Function Calling,如果支持则优先使用,以节省Token。)
总结
所以,回到最初的问题:MCP必须依赖Function Calling吗?答案是否定的。
MCP只是一个通信协议。虽然实现MCP交互需要LLM能够生成结构化的工具调用指令,但这并不一定需要LLM具备原生的Function Calling能力。通过像Cline那样精心设计、内容详尽的System Prompt,完全可以引导几乎任何LLM“学会”如何按照MCP的要求与外部世界互动。
理解了这一点,能帮助我们更清晰地认识当前LLM Agent架构,也更能体会到像Cline这样优秀工具在设计上的巧思。下一次当你使用或开发类似的AI应用时,就能更深刻地理解其背后的运作机制了。
想探索AI前沿?来 ChatTools (https://chat.chattools.cn)!聚合GPT-4o(支持图片编辑)、Claude 3.7 Sonnet、DeepSeek-R1、Gemini等顶尖模型。亮点:Midjourney免费无限生图!