前言
对于 MCP 是什么以及如何开发一个 MCP Server 的文章现在可以说是汗牛充栋,不再拾人牙慧。我们通过使用 MCP 的一些场景,看看 MCP 到底有什么魔力,解决了什么问题。
MCP 可以做什么
MCP 和 Function Call
很多人说 MCP 就是一个协议,就是对传统 function calling 技术的一层封装。我们从 function calling 出发,看看 MCP 到底做了什么,解决了什么问题。
function call
众所周知,LLM (Large Language Model) 由于其架构和训练方式的限制,有两个非常致命的弱点
- 内部存储的知识是有限的,内容广度和时间范围有限制,训练完毕的那一刻记忆就封印了

可以看到最新的 GPT-5,最多也只有 2024 年十一国庆之前的知识。要知道过去的这接近大半年的时间,世界发生了很多事情。GPT-5 的知识结构里显然不会有 DeepSeek,更不要说对其的了解了。
- 不擅长做数学运算
这一点从他的实现原理来说是几乎无解的。基于 Transformer 架构的 LLM 只能基于输入不断预测输出,说白了他是在猜测一个合理的输出。这种模式对于输出没有确定答案的创作类型课题非常合适,比如写作、绘画之类的生成式任务,LLM 可以自由发挥。但是算术是一个纯粹的计算任务,无论是掰着手指头,还是借助算盘,亦或是使用计算器,算术的结果有唯一性,1+1=2
是颠之不破的真理,而 LLM 恰好不擅长这件事。
记得 ChatGPT 刚出来的时候,很多人让做数学题,遇到位数多一些小学生都会做的加减法他就会翻车,因此被很多人嘲讽。用户询问的问题千奇百怪,有问天气的,有用来算命,甚至还有把他朋友用来谈心的。对于这么多样的问题,只要问题陷入上面这两个缺陷时,ChatGPT 就无能为力了。面对这种类型的难题,function call 应运而生。
对于查询天气这种大模型原本无能为力的事情,借助 function call 得以优雅的解决。
openai gpt function call
python
def get_current_weather(location, unit="celsius"):
# 实际应用中这里会调用天气API
return json.dumps({"location": location, "temperature": "22", "unit": unit})
# 用户询问天气
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "北京的天气?"}],
functions=[{
"name": "get_current_weather",
"description": "获取指定位置的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "城市和地区"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
}],
)
response_message = response.choices[0].message
# 从返回的结果中确定是否需要 function call
if response_message.get("function_call"):
function_name = response_message["function_call"]["name"]
function_args = json.loads(response_message["function_call"]["arguments"])
# 调用本地函数
function_response = globals()[function_name](**function_args)
messages.append(response_message) # 添加助手的函数调用消息
messages.append(
{
"role": "function",
"name": function_name,
"content": function_response,
}
)
second_response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=messages,
)
这里的实现很简单,通过 functions 参数事先给 LLM 预置一个方法 ,其中包括这个方法用来做什么,以及方法的参数信息。LLM 在推理的过程中会参考这些信息,如果用户的提问恰好符合这个方法的描述,LLM 就会返回这个参数名称,让开发者调用对应的方法获取获取信息,并将这些信息再次传递给 LLM,LLM 在综合这些信息给出一个合适的回答。
通过给 functions
参数定义 json schema
格式的方法描述,模型就可以基于用户 query 返回合适的结果了。同时可以看到 functions
支持数组类型的输入,也就是说可以支持多个 function。实际实现中根据模型返回的 function name 做映射调用即可。
可以说function call 让 LLM 如虎添翼, function call解决了 ChatGPT 的两个主要限制:数据实时性问题(通过连接实时数据源)和与已有应用系统集成问题(通过调用业务系统API)。
function call 有什么问题
function call 看起来不错,但是他也有很多问题
- function call 不是协议,他是具体实现。他只是一种解题思路,其他大模型也可以支持 function call ,但是实现细节不一定(甚至为了避免被大家说是抄作业会刻意修改实现细节)相同,输入参数中 functions 如何定义,返回的 response 中从哪个字段获取可以调用的 function name,各个 LLM 都有不同的实现,因此切换不同的 LLM 之后,function call 的迁移成本非常高。
- function 的 json schema 描述是一个 json 字符串,很难维护和共享,无法形成生态。
- 当定义的 function 越来越多时,每次调用 LLM 的 input token 就会越来越多,用户问的问题可能都不需要 function call,但是还是会带上这些参数,无形中浪费了算力。
- 可以看到 function call 本身还是依赖 LLM 的能力,不同的 LLM 需要通过不同的 system prompt 进行约束,确保模型可以正常的返回 function name 和参数。不同的 LLM、不同的 function json schema 、不同的 system prompt 约束,随着业务的发展,这将会是一个越来越复杂的问题。
- 最后,如果一个 LLM 不支持 function call,面对用户诸如
北京今天的天气
,483939x998439
这类模型无法处理的问题,就得添加一个中间层,想办法绕过 LLM 用传统的方式解决问题, 或者添加更多的提示词让模型去调用预置的工具。
MCP
面对 function call 存在的问题,MCP 应用而生, 对于一个典型的 MCP 使用场景, 核心实现如下
python
async def connect_to_server(self):
"""连接到 MCP 服务器"""
server_params = StdioServerParameters(
command='python',
args=['math_server.py'],
env=None
)
stdio_transport = await self.exit_stack.enter_async_context(
stdio_client(server_params))
stdio, write = stdio_transport
self.session = await self.exit_stack.enter_async_context(
ClientSession(stdio, write))
await self.session.initialize()
async def process_query(self, query: str) -> str:
messages = [
{"role": "user", "content": query}
]
# 获取可用工具列表
response = await self.session.list_tools()
# 构建函数调用格式
available_tools = [{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema
}
} for tool in response.tools]
# 使用 Deepseek 模型通过 Ollama 生成回复
try:
response = self.client.chat.completions.create(
model="deepseek-chat", # 使用 Ollama 中的 Deepseek 模型
messages=messages,
tools=available_tools,
tool_choice="auto"
)
# 处理响应
content = response.choices[0]
message = content.message
# 检查是否需要工具调用
if hasattr(message, 'tool_calls') and message.tool_calls:
# 执行工具调用
tool_call = message.tool_calls[0]
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
# 调用 MCP 工具
result = await self.session.call_tool(tool_name, tool_args)
# 将结果添加到消息中
messages.append(message.model_dump())
messages.append({
"role": "tool",
"content": str(result.content[0].text),
"tool_call_id": tool_call.id,
})
# 让 Deepseek 继续处理结果
response = self.client.chat.completions.create(
model="deepseek-coder",
messages=messages,
)
return response.choices[0].message.content
return message.content
except Exception as e:
return f"发生错误: {str(e)}"
可以看到,使用 mcp 提供的 SDK 之后,将 function 的定义规范化了. 提出了 MCP Server 这样一个概念,专门用来提供可以让 MCP Client 调用的方法。无论是 Python、JavaScript 还是 Go ,都可以用于提供可供 Client 端调用的方法,只要遵循 MCP 协议就好。 使用 MCP 的整个流程可以总结如下

可以看到最核心的步骤就是
- 1. 从 MCP Server 获取可用的工具
- 2. 将可用的工具通过合理的方式传递给 LLM
- 3. 基于 LLM 的返回结果处理调用工具返回最终的结果
而 Cursor/Claude DeskTop/Trae 的这样 IDE 其实在做的就是以上这些事。当然这起其中的细节很多,比如如何配置 system prompt 确保 LLM 一定会调用合适的工具,如果用户本地安装了几十个 MCP Server, 怎么选择工具?如何尽可能的避免出现幻觉。这其中有很多 prompt engineer 的工作。
不同 LLM 厂商对于 function call 定义有差异这件事木已成舟,MCP 协议只能做约束,这些 IDE 通过对这些 function call 用法的兼容,让开发者聚焦于 MCP Server 的开发即可,只要按照协议实现,使用任意 LLM 都能确保正确的调用相应的方法。
了解了这个与原理,其实你也可以摆脱这些 IDE 自己做一个可以支持 MCP Server 的实现,但是似乎没有必要。
MCP 解决了什么问题
MCP 本身就是协议,因此如果更多人参与进来,开始遵循和支持 MCP,那么对整个 AI 生态的发展就是好事。就像互联网时代,大家都基于 HTTP 协议做应用,即便各自的业务千差万别,但是当需要合作的时候就会方便一些。
对于上面 function call 提到的问题,MCP 并没有完全解决。MCP 让开发者放心大胆的去开发 MCP Server,各类 MCP Host 可以非常轻松的配置 MCP Server, session.list_tools()
返回的可用工具越来越多,默认情况下会携带所有工具一股脑的传给 LLM,token 浪费的情况依然存在。尤其是现阶段 MCP Server 的质量良莠不齐,各类 MCP Server 站点提供了大量可用的 MCP Server,但是对这些 MCP Server 的质量似乎没有人做把控,就像移动互联网早期的应用市场,随便做个东西就能发布。
当然,这个问题无法靠 MCP 解决,理论上我们可以在用户 Query 和最终的 LLM 请求之间再添加一层专门用来选择合适的 available tools
,类似 RAG ,基于用户的问题选择合适的 tools 给 LLM。Cursor/Claude DeskTop/Trae
这类 MCP Host 应该早就这么干了,毕竟这是一个很容易想到的思路。
我们可以对比一下 LLM 使用 function call 和 MCP 与外接交互的差异
function call | MCP |
---|---|
![]() |
![]() |
可以清晰的看到,没有 MCP 的时候,LLM 与外部世界交互是直接进行交互的。而 MCP 出现之后,作为中间层将 LLM 和外界工具进行了隔离,这样就避免了随着 LLM 的发展,业务被某个LLM 深度绑定的囧境。
总结
MCP 本身并无法提升 LLM 的能力,他不会让 LLM 变得更聪明。但是他提供了一种让应用层开发者更友好的与 LLM 进行融合的方案。MCP 自然不是 function call 的替代者,同时 MCP 也不仅仅是一个约束 LLM 如何与调用外部函数的协议,他还有更多的内容值得我们去深入学习。