背景
在人工智能技术日新月异的今天,MCP(Model Context Protocol)的协议正在悄然重塑AI应用的开发范式。它的核心优势在于通过标准化协议安全高效地连接AI模型与多元数据工具,打破数据孤岛并实现灵活扩展。由于使用的是标准协议,使得不同厂商的模型、工具和数据源能够无缝对接,编写的MCP服务可以无缝集成到任何支持MCP协议的客户端中,比如Claude Desktop、Cline、5ire等客户端。
▍ 实际应用案例
- 获取实时时间:通过开发和部署获取当前时间的MCP server,AI能准确回答当前时间。
- 电商直播管理:AI通过MCP server访问本地数据库,管理商品库存和价格。
- 素材整理:AI利用MCP server整理视频素材,提高视频剪辑效率。
- 阅读网页:通过部署MCP server,AI能读取并总结网页内容。
▍ 疑惑
尽管MCP展现出巨大潜力,但像对于上篇文章MCP初体验,也留下了一个疑惑:对于DeepSeek R1这类大模型,它本身是不支持Function Calling,也不支持像Claude模型的Tool use能力,那它是怎么打通MCP协议,从而可以调用mcp定义的各种能力呢?
; 本文将简单总结下常见的MCP客户端的实现原理,主要列举了5ire
和我们上篇使用的Cline
的实现原理,顺便也解答了上篇留下的疑惑。
官方MCP客户端实现
根据官方文档For Client Developers,开发MCP客户端主要有以下几个步骤:
- 服务器连接配置
- 建立通信会话
- 实现查询处理逻辑
- 构建交互流程oooo
一条Query的查询如下:
常见的客户端实现
5ire
查看源码可以看出来,5ire
做了一层封装,将OpenAI和Claude的接口做了统一(NextCharService虚类),然后在下层实现中来区分是OpenAI还是Claude接口。
OpenAI兼容协议实现
- 对于OpenAI兼容协议接口,工具调用如下:
ts
// src/intellichat/services/OpenAIChatService.ts
export default class OpenAIChatService
extends NextChatService
implements INextChatService {
...
protected makeTool(
tool: IMCPTool,
): IOpenAITool | IAnthropicTool | IGoogleTool {
return {
type: 'function',
function: {
name: tool.name,
description: tool.description.substring(0, 1000), // some models have a limit on the description length, like gpt series, so we truncate it
parameters: {
type: tool.inputSchema.type,
properties: tool.inputSchema.properties,
required: tool.inputSchema.required,
additionalProperties: tool.inputSchema.additionalProperties,
},
},
};
}
protected makeToolMessages(
tool: ITool,
toolResult: any,
): IChatRequestMessage[] {
return [
{
role: 'assistant',
tool_calls: [
{
id: tool.id,
type: 'function',
function: {
arguments: JSON.stringify(tool.args),
name: tool.name,
},
},
],
},
{
role: 'tool',
name: tool.name,
content:
typeof toolResult === 'string' ? toolResult : toolResult.content,
tool_call_id: tool.id,
},
];
}
...
}
可以看到OpenAI兼容协议接口,使用的是Function Calling的能力,因此对于不支持Function Calling的大模型,是没法开启MCP的
请求示例
- 请求体
json
{
"model": "deepseek-chat",
"messages": [
{"role": "user", "content": "查询北京的天气"},
// 可能包含历史工具调用消息...
],
"temperature": 1,
"stream": true,
"tools": [
{
"type": "function",
"function": {
"name": "Weather--getCurrent",
"description": "获取当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称"
}
},
"required": ["location"]
}
}
}
],
"tool_choice": "auto"
}
- 响应体
json
[
{
"role": "assistant",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "Weather--getCurrent",
"arguments": "{\"location\":\"北京\"}"
}
}
]
}
]
- 系统执行工具后生成新消息:
json
[
{
"role": "user",
"content": "查询北京的天气"
},
{
"role": "assistant",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "Weather--getCurrent",
"arguments": "{\"location\":\"北京\"}"
}
}
]
},
{
"role": "tool",
"name": "Weather--getCurrent",
"content": "北京 晴 25℃",
"tool_call_id": "call_abc123"
}
]
Anthropic协议实现
- 对于Anthropic协议接口,工具调用如下:
ts
// src/intellichat/services/AnthropicChatService.ts
export default class AnthropicChatService
extends NextChatService
implements INextChatService
{
...
protected makeToolMessages(
tool: ITool,
toolResult: any
): IChatRequestMessage[] {
return [
{
role: 'assistant',
content: [
{
type: 'tool_use',
id: tool.id,
name: tool.name,
input: tool.args ?? {},
},
],
},
{
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: tool.id,
content:
typeof toolResult === 'string' ? toolResult : toolResult.content,
},
],
},
];
}
protected makeTool(tool: IMCPTool): IOpenAITool | IAnthropicTool {
return {
name: tool.name,
description: tool.description,
input_schema: {
type: tool.inputSchema.type,
properties: tool.inputSchema.properties,
required: tool.inputSchema.required,
additionalProperties: tool.inputSchema.additionalProperties,
},
};
}
...
}
同样的Anthropic的Claude模型使用的是Tool Use能力。
请求示例
- 请求体
json
{
"model": "claude-3-5-sonnet-20240620",
"messages": [
{"role": "user", "content": "查询上海未来三天的天气"},
// 可能包含历史工具调用消息...
],
"temperature": 0.7,
"stream": true,
"tools": [
{
"name": "Weather--getForecast",
"description": "获取天气预报",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "城市名称"},
"days": {"type": "number", "description": "预报天数"}
},
"required": ["location"]
}
}
],
"tool_choice": {
"type": "auto",
"disable_parallel_tool_use": true
}
}
- 响应
json
{
"content": [
{
"type": "tool_use",
"id": "toolu_01",
"name": "Weather--getForecast",
"input": {
"location": "上海",
"days": 3
}
}
]
}
- 系统执行工具后生成新消息:
json
[
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01",
"name": "Weather--getForecast",
"input": {
"location": "上海",
"days": 3
}
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": "上海未来三天:晴转多云,25-32℃"
}
]
}
]
DeepSeek的实现
比如下面的DeepSeek的实现中,就可以看出来DeepSeek Reaonser
由于不支持Function Calling
,它的toolEnabled=false
,因此它在5ire
中就是不能开启MCP的。
yaml
// src/providers/DeepSeek.ts
export default {
name: 'DeepSeek',
apiBase: 'https://api.deepseek.com',
currency: 'CNY',
options: {
apiBaseCustomizable: true,
apiKeyCustomizable: true,
},
chat: {
apiSchema: ['base', 'key', 'model'],
presencePenalty: { min: -2, max: 2, default: 0 },
topP: { min: 0, max: 1, default: 1 },
temperature: { min: 0, max: 2, default: 1 },
options: {
modelCustomizable: true,
},
models: {
'deepseek-chat': {
name: 'deepseek-chat',
contextWindow: 65536,
maxTokens: 8192,
defaultMaxTokens: 2048,
inputPrice: 0.0006,
outputPrice: 0.002,
isDefault: true,
description: `60 tokens/second, Enhanced capabilities,API compatibility intact`,
toolEnabled: true,
group: 'DeepSeek',
},
'deepseek-reasoner': {
name: 'deepseek-reasoner',
contextWindow: 65536,
maxTokens: 8192,
defaultMaxTokens: 2048,
inputPrice: 0.003,
outputPrice: 0.016,
isDefault: true,
description: `Performance on par with OpenAI-o1`,
toolEnabled: false,
group: 'DeepSeek',
},
},
},
} as IServiceProvider;
RooCode\Cline
在RooCode\Cline中可以它们使用的是不同的方式,它没有使用模型自带的FunctionCalling或者ToolUse能力,而是完全使用Prompt来实现,整个MCP的System Prompt
非常长(这也是为什么它非常费token的原因之一,如果请求中不用MCP的话,关闭MCP可以大大节约tokne),详细的可以参考Cline System Prompt。
实现原理
- Cline调用MCP的流程图
比如对于下面大模型返回结果:
xml
1. 任务要求获取指定URL的文章内容并总结要点
2. 可以使用MCP服务器的fetch工具来获取网页内容
3. 需要提供URL参数,用户已明确给出
4. 其他参数可以使用默认值
<use_mcp_tool>
<server_name>fetch</server_name>
<tool_name>fetch</tool_name>
<arguments>
{
"url": "https://juejin.cn/post/7430620710605422644"
}
</arguments>
</use_mcp_tool>
- 解析后的block数组如下
csharp
[
{
type: "text",
content: "1. 任务要求获取指定URL的文章内容并总结要点...4. 其他参数可以使用默认值",
partial: false
},
{
type: "tool_use",
name: "use_mcp_tool",
params: {
server_name: "fetch",
tool_name: "fetch",
arguments: '{"url": "https://juejin.cn/post/7430620710605422644"}'
},
partial: false
}
]
多种类型时,text类型直接展示给用户(只是展示),tool_use会继续调用工具,然后请求大模型
其中content Blocks解析的流程如下:
cline是将流式文本按照字符遍历来处理的,没有使用正则表达式。
请求示例
- 请求体
json
{
"model": "deepseek-r1",
"temperature": 0,
"messages": [
{
"role": "system",
"content":"You are Roo,...(long text)"
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "<task>\n测试下,获取https://juejin.cn/post/7430620710605422644正文,总结要点\n</task>"
},
{
"type": "text",
"text": "<environment_details>\n# VSCode Visible Files\n..."
}
]
}
],
"stream": true,
"stream_options": {
"include_usage": true
}
}
- 响应
json
<thinking>
1. 任务要求获取指定URL的文章内容并总结要点
2. 可以使用MCP服务器的fetch工具来获取网页内容
3. 需要提供URL参数,用户已明确给出
4. 其他参数可以使用默认值
</thinking>
<use_mcp_tool>
<server_name>fetch</server_name>
<tool_name>fetch</tool_name>
<arguments>
{
"url": "https://juejin.cn/post/7430620710605422644"
}
</arguments>
</use_mcp_tool>
只把content中的内容摘取出来
- 再次请求
swift
{
"messages": [
// 历史消息...
{
"role": "assistant",
"content": "\n\n<use_mcp_tool>\n<server_name>fetch</server_name>\n<tool_name>fetch</tool_name>\n<arguments>\n{\n \"url\": \"https://juejin.cn/post/7430620710605422644\",\n \"raw\": false\n}\n</arguments>\n</use_mcp_tool>"
},
{
"role": "user",
"content": [
{ "type": "text", "text": "[use_mcp_tool for 'github'] Result:" },
{
"type": "text",
"text": "Contents of https://juejin.cn/post/7430620710605422644:\n \n## 环境安装\n\n### 安装ollama..."}
]
},
{
"type": "text",
"text": "<environment_details>\n# VSCode Visible Files\n..."
}
]
}
其实通过Claude Tool use system prompt的官方文档可以发现,它也是通过设置了
System Prompt
来实现的,只不过如果是使用了tools参数,系统帮你自动加上了这些System Prompt
。其实和Cline是类似的思路。
方案对比与选型建议
实现方式对比
5ire技术方案
Cline技术方案
核心差异对比
维度 | 5ire方案 | Cline方案 |
---|---|---|
技术路径 | 模型原生能力 | Prompt工程 |
协议兼容性 | 严格遵循OpenAI/Anthropic规范 | 自定义交互协议 |
工具发现 | 动态获取MCP服务端注册工具 | 硬编码在System Prompt中 |
执行效率 | 单次交互完成工具调用 | 需多轮消息交互 |
适用模型 | GPT-4/Claude等新一代模型 | 任意文本生成模型 |
选型决策树
开发者建议
- 🚀 短期项目优先考虑Cline的灵活性
- ⚖️ 折中方案可组合使用两种客户端
总结
- 对文解答了上篇文章【AIGC】MCP初体验留下的疑问:
对于DeepSeek R1这类大模型,它本身是不支持Function Calling,也不支持像Claude模型的Tool use能力,那它是怎么打通MCP协议,从而可以调用mcp定义的各种能力呢?
,分析了RooCode\Cline是如何实现MCP调用的; - 对比分析了两种MCP客户端实现方案:基于模型原生工具调用能力的5ire方案(OpenAI/Claude适配)和通过Prompt工程实现的Cline方案(通用模型适配),解析了二者的技术原理、优劣差异及适用场景。
参考
官方实现的客户端示例
通过prompt来实现MCP,强制其返回json数据格式,和Cline类似。
使用OpenAI SDK来实现,从而可以让
DeepSeek Chat
等支持Function Calling的大模型可以接入MCP,和5ire
的原理类似,通过代码来将Claude的协议兼容到OpenAI标准协议接口中。但是对于不支持Function Calling的大模型,是没法支持的。
将不同模型统一成OpenAI兼容的FunctionCalling定义格式,使用的是原生的FunctionCalling或者ToolUse能力,然后再统一调用Tool
参考了上面mcp-cli的实现,也是使用了原生的FunctionCalling或者ToolUse能力