跳转至

MCP

什么是MCP?

MCP 起源于 2024 年 11 月 25 日 Anthropic 发布的文章:Introducing the Model Context Protocol。

MCP (Model Context Protocol,模型上下文协议)定义了应用程序和 AI 模型之间交换上下文信息的方式。这使得开发者能够**以一致的方式将各种数据源、工具和功能连接到 AI 模型**(一个中间协议层),就像 USB-C 让不同设备能够通过相同的接口连接一样。MCP 的目标是创建一个通用标准,使 AI 应用程序的开发和集成变得更加简单和统一。

可以看出,MCP 就是以更标准的方式让 LLM Chat 使用不同工具,更简单的可视化如下图所示,这样你应该更容易理解“中间协议层”的概念了。Anthropic 旨在实现 LLM Tool Call 的标准。

如何使用MCP?

对于用户来说,我们并不关心 MCP 是如何实现的,通常我们只考虑如何更简单的用上这一特性。

具体的使用方式参考官方文档:For Claude Desktop Users。这里不再赘述,配置成功后可以在 Claude 中测试:Can you write a poem and save it to my desktop? Claude 会请求你的权限后在本地新建一个文件。

并且官方也提供了非常多现成的 MCP Servers,你只需要选择你希望接入的工具,然后接入即可。

比如官方介绍的 filesystem 工具,它允许 Claude 读取和写入文件,就像在本地文件系统中一样。

MCP架构

这里首先引用官方给出的架构图。

MCP 由三个核心组件构成:Host、Client 和 Server。让我们通过一个实际场景来理解这些组件如何协同工作:

假设你正在使用 Claude Desktop (Host) 询问:"我桌面上有哪些文档?"

  1. Host:Claude Desktop 作为 Host,负责接收你的提问并与 Claude 模型交互。
  2. Client:当 Claude 模型决定需要访问你的文件系统时,Host 中内置的 MCP Client 会被激活。这个 Client 负责与适当的 MCP Server 建立连接。
  3. Server:在这个例子中,文件系统 MCP Server 会被调用。它负责执行实际的文件扫描操作,访问你的桌面目录,并返回找到的文档列表。

整个流程是这样的:你的问题 → Claude Desktop(Host) → Claude 模型 → 需要文件信息 → MCP Client 连接 → 文件系统 MCP Server → 执行操作 → 返回结果 → Claude 生成回答 → 显示在 Claude Desktop 上。

这种架构设计使得 Claude 可以在不同场景下灵活调用各种工具和数据源,而开发者只需专注于开发对应的 MCP Server,无需关心 Host 和 Client 的实现细节。

模型如何决定使用的工具?

Claude(模型)是在什么时候确定使用哪些工具的呢?好在 Anthropic 为我们提供了详细的解释:

当用户提出一个问题时:

  1. 客户端(Claude Desktop / Cursor)将你的问题发送给 Claude。
  2. Claude 分析可用的工具,并决定使用哪一个(或多个)。
  3. 客户端通过 MCP Server 执行所选的工具。
  4. 工具的执行结果被送回给 Claude。
  5. Claude 结合执行结果构造最终的 prompt 并生成自然语言的回应。
  6. 回应最终展示给用户!

MCP Server 是由 Claude 主动选择并调用的。有意思的是 Claude 具体是如何确定该使用哪些工具呢?以及是否会使用一些不存在的工具呢(幻觉)?

工具执行与结果反馈机制

其实工具的执行就比较简单和直接了。承接上一步,我们把 system prompt(指令与工具调用描述)和用户消息一起发送给模型,然后接收模型的回复。当模型分析用户请求后,它会决定是否需要调用工具:

  • 无需工具时:模型直接生成自然语言回复。
  • 需要工具时:模型输出结构化 JSON 格式的工具调用请求。

如果回复中包含结构化 JSON 格式的工具调用请求,则客户端会根据这个 json 代码执行对应的工具。具体的实现逻辑都在 process_llm_response 中,代码,逻辑非常简单。

如果模型执行了 tool call,则工具执行的结果 result 会和 system prompt 和用户消息一起**重新发送**给模型,请求模型生成最终回复。

如果 tool call 的 json 代码存在问题或者模型产生了幻觉怎么办呢?通过阅读代码 发现,我们会 skip 掉无效的调用请求。

执行相关的代码与注释如下:

Python
 ... # 省略无关的代码
 async def start(self):
     ... # 上面已经介绍过了,模型如何选择工具

     while True:
         # 假设这里已经处理了用户消息输入.
         messages.append({"role": "user", "content": user_input})

         # 获取 LLM 的输出
         llm_response = self.llm_client.get_response(messages)

         # 处理 LLM 的输出(如果有 tool call 则执行对应的工具)
         result = await self.process_llm_response(llm_response)

         # 如果 result 与 llm_response 不同,说明执行了 tool call (有额外信息了)
         # 则将 tool call 的结果重新发送给 LLM 进行处理。
         if result != llm_response:
             messages.append({"role": "assistant", "content": llm_response})
             messages.append({"role": "system", "content": result})

             final_response = self.llm_client.get_response(messages)
             logging.info("\nFinal response: %s", final_response)
             messages.append(
                 {"role": "assistant", "content": final_response}
             )
         # 否则代表没有执行 tool call,则直接将 LLM 的输出返回给用户。
         else:
             messages.append({"role": "assistant", "content": llm_response})    

结合这部分原理分析:

  • 工具文档至关重要 - 模型通过工具描述文本来理解和选择工具,因此精心编写工具的名称、docstring 和参数说明至关重要。
  • 由于 MCP 的选择是基于 prompt 的,所以任何模型其实都适配 MCP,只要你能提供对应的工具描述。但是当你使用非 Claude 模型时,MCP 使用的效果和体验难以保证(没有做专门的训练)。