全网最细,一文带你弄懂 MCP 的核心原理!

本文视频教程:www.bilibili.com/video/BV1LQ...

  • MCP 是如何做到统一工具调用方式的?
  • MCP 客户端和 MCP Server 到底是怎么交互的?
  • 为什么有的 MCP 客户端支持所有模型,有的确不行?

大家好,欢迎来到 code秘密花园,我是花园老师(ConardLi)。

为了方便大家更深度的理解 MCP ,在今天这一期,我们用几个例子来一起学习 MCP 的核心原理。

在上一期 《MCP+数据库:一种提高结构化数据检索效果的新方式》,我们一起学习了 MCP 的基础知识,包括

  • MCP 基础知识,和 Function Call 的关系
  • 了解 MCP Clinet、MCP Server 等核心概念
  • 在 Cherry Studio 中简单尝试了 MCP
  • 在 Cline 中基于 mongo-mcp-server 实现了结构化数据的精准检索

还没学习的同学建议先阅读完上一期再回来看这一期,今天我们的学习大纲如下:

下面我们先回顾一下 MCP 的基础知识:

一、MCP 基础回顾

MCP(Model Context Protocol,模型上下文协议)是由 Anthropic 公司(也就是开发 Claude 模型的公司)推出的一个开放标准协议,就像是一个 "通用插头" 或者 "USB 接口",制定了统一的规范,不管是连接数据库、第三方 API,还是本地文件等各种外部资源,目的就是为了解决 AI 模型与外部数据源、工具交互的难题。

MCP 大概的工作方式:MCP Host,比如 Claude Desktop、Cursor 这些工具,在内部实现了 MCP Client,然后MCP Client 通过标准的 MCP 协议和 MCP Server 进行交互,由各种三方开发者提供的 MCP Server 负责实现各种和三方资源交互的逻辑,比如访问数据库、浏览器、本地文件,最终再通过 标准的 MCP 协议返回给 MCP Client,最终在 MCP Host 上展示。

开发者按照 MCP 协议进行开发,无需为每个模型与不同资源的对接重复编写适配代码,可以大大节省开发工作量,另外已经开发出的 MCP Server,因为协议是通用的,能够直接开放出来给大家使用,这也大幅减少了开发者的重复劳动。

首先一步,我们先从 MCP Server 的配置讲起,弄懂 MCP Server 的配置为什么是这样设计的。

二、MCP Server 的配置为何长这样?

我们使用 Cherry Studio 中的 MCP 服务配置来举例,最前面的名称和描述比较好理解,都是用于展示的。

2.1 通信协议

下面我们发现有两个类型,STDIOSSE

MCP 协议中的 STDIOSSE 其实就是是两种不同的(MCP ServerMCP Client)通信模式:

  • STDIO(标准输入输出):像「面对面对话」:客户端和服务器通过本地进程的标准输入和标准输出直接通信。例如:本地开发时,你的代码脚本通过命令行启动,直接与 MCP 服务器交换数据,无需网络连接。
  • SSE(服务器推送事件):像「电话热线」:客户端通过 HTTP 协议连接到远程服务器,服务器可以主动推送数据(如实时消息)。例如:AI 助手通过网页请求调用远程天气 API,服务器持续推送最新的天气信息。

简单理解,STDIO 调用方式是将一个 MCP Server 下载到你的本地,直接调用这个工具,而 SSE 则是通过 HTTP 服务调用托管在远程服务器上的 MCP Server

这就是一个 SSE MCP Server 的配置示例,非常简单,我们直接使用网络协议和工具进行通信:

json 复制代码
{
  "mcpServers": {
    "browser-use-mcp-server": {
      "url": "http://localhost:8000/sse"
    }
  }
}

而我们之前用到的 FileSystem、Mongodb 都属于典型的 STDIO 调用:

json 复制代码
{
  "mcpServers": {
    "mongodb": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "~/Downloads"
      ]
    }
  }
}

STDIO 为程序的输入输出提供了一种统一的标准方式,无论是什么类型的程序,只要遵循 STDIO 协议,就可以方便地进行数据的输入和输出,并且可以很容易地与其他程序或系统组件进行交互。

在命令行中,使用 STDIO 通信是非常常见的,比如 Linux 系统中的 cat 命令就是一个极为简单且能体现 STDIO 通信的例子。cat 命令通常用于连接文件并打印到标准输出。当你不指定任何文件时,它会从标准输入读取内容,并将其输出到标准输出。

打开终端,输入 cat 命令,然后按下回车键。此时,cat 命令开始等待你从标准输入(通常是键盘)输入内容。你可以输入任意文本,每输入一行并按下回车键,cat 会立即将这行内容输出到标准输出。

在这个例子中,你输入了 你好,这里是 code秘密花园!我是花园老师cat 命令将这些内容从标准输入读取后,马上输出到标准输出。

我们还能借助一些操作符,比如重定向操作符 > 将标准输出重定向到文件。下面的例子会把你输入的内容保存到 test.txt 文件里:

上述命令执行后,test.txt 文件里会包含你输入的两行内容:

在这个过程中,cat 命令从标准输入读取内容,接着将输出重定向到文件,这就是一个最简单的 STDIO 通信案例。

2.2 命令和参数

下面再来说说配置里的命令,对应 JSON 配置中的 command 参数,在没有填写时,这里默认推荐的是 uvx 和 npx:

在之前 mongodb 的例子中, command 使用的就是 npx ,之前我们提到了,只要安装了 Node.js 环境,就可以使用,那为什么这里不写 node,而要用 npx 命令呢?

npxNode.js 生态系统中的一个命令行工具,它本质上是 npm的一个扩展。npx 的名字可以理解为"运行一个包"(npm execute package)的缩写。

简单来说,npx 的主要功能就是帮助我们快速运行一些通过 npm 安装的工具,而不需要我们手动去下载安装这些工具到全局环境。

json 复制代码
{
  "mcpServers": {
    "mongodb": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "~/Downloads"
      ]
    }
  }
}

在这个配置中,args 里第一个参数 "-y" 其实就等同于 --yes,其作用是在执行命令时自动同意所有提示信息,可以避免交互式的确认步骤。

而第二个参数其实就是这个 npm 包的名字,比如 @modelcontextprotocol/server-filesystem 这个包:

args 里的第三个参数其实就是传递给这个包的一个必备参数:允许读取的本机路径。

这样理解其实就能说得通了,这几个信息,再加上大模型分析用户输入后得出的参数,通过 STDIO 协议传递给这个包,其实就可以构成一条在终端直接可以运行的一条命令:

bash 复制代码
npx -y @modelcontextprotocol/server-filesystem ~/Downloads  <<< '{"method":"tools/call","params":{"name":"list_directory","arguments":{"path":"~/Downloads"}},"jsonrpc":"2.0","id":1}'

我们测试执行一下:

我们把输入内容格式化一下:

<<<(Here 字符串操作符)的主要用途是将一个字符串作为标准输入(STDIN)传递给前面的命令。

在这个命令中,我们通过 <<< 操作符,把后面的字符串传递给前面的命令(也就是 @modelcontextprotocol/server-filesystem 这个包)当作输入数据。

后面的字符串中包含了需要调用的函数(list_directory),以及传递给这个函数的参数(~/Downloads),

前面的部分都是我们在 MCP Server 中配置的内容,属于固定的部分。而大模型做的,就是根据用户当前的输入,以及当前支持的 MCP 工具列表,判断出要不要调用这个工具,如果要调用,生成结构化的工具参数,最后 MCP Client 通过 STDIO 协议将这个结构化的参数再传递给 MCP Server

在上面的命令中,还有一个选项是 uvx,和 npx 类似,它也可以直接让你临时执行某个工具包,而无需全局安装。不过 uvx:是 uv 工具链的一部分,主要聚焦于 Python 生态系统,它会创建一个临时的隔离 Python 环境来运行 Python 工具包。

2.3 传输格式

我们再来看看刚刚的命令执行的结果:

也是一段格式化好的内容,里面返回了当前目录下的所有文件,而模型在接收到这段格式化的输出后,会将其变成口语化的内容再返回到 MCP 客户端。

这个输入、输出的参数格式,遵循的是 JSON-RPC 2.0 传输格式,它有一套自己的规则,规定了请求和响应的格式、如何处理错误等,官方文档的中的描述:

2.4 Windows 下的配置

在实际使用中,大家可能会发现,下面的配置在 Windows 下可能不会生效

json 复制代码
{
  "mcpServers": {
    "mongodb": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "~/Downloads"
      ]
    }
  }
}

需要改成下面的方式:

json 复制代码
{
  "mcpServers": {
    "mongodb": {
      "command": "cmd",
      "args": [
        "/c",
        "npx",
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "~/Downloads"
      ]
    }
  }
}

这是因为在不同操作系统下 默认命令解释器(Shell)的工作机制不同

macOS 的终端(如 bash/zsh)是 Unix shell,支持直接执行可执行文件(如 npx)。

Windows 的默认命令解释器是 cmd.exe,而非直接执行程序。即使 npx 已安装(如通过全局 npm install -g npx),也需要通过 cmd.exe 来调用,因为:

  • Windows 不直接识别 Unix 风格的命令路径(如 npx 本质是 Node.js 脚本,需通过 node 执行)。
  • cmd.exe 需要明确的指令格式(如 /c 参数用于执行命令后退出)。

args 中的 /ccmd.exe 的参数,表示"执行后续命令后关闭窗口"。完整的执行流程是:

bash 复制代码
cmd.exe /c npx -y @modelcontextprotocol/server-filesystem "~/Downloads"

这里 cmd.exe 先解析 /c,再将 npx ... 作为子命令执行,确保 Windows 能正确调用 Node.js 脚本(npx)。

通过分析 MCP Server 的配置,我们了解了 MCP ClinetMCP Server 的通信协议、执行的命令和参数,以及两者数据传输的标准格式,这样 MCP ClinetMCP Server 的交互方式我们基本上就弄清楚了。

那执行 MCP Server 我们一定要通过 npxuvx 运行一个包吗?

当然不是,实际上任何能够在命令行执行代码的方式,都是可以的,比如我们可以直接把一个 MCP Server 的仓库拉取到本地,然后通过 node 或 python 命令直接执行里面的代码文件,这样就可以实现完全断网运行(前提是 MCP Server 中不会调用远程 API)。

json 复制代码
{
  "mcpServers": {
    "mongodb": {
      "command": "node",
      "args": [
        "/path/mcp-server.js"
      ]
    }
  }
}

下面,我们在本地实现一个最简单的例子,来教大家如何构建一个 MCP Server。

三、MCP Server 是如何开发和调试的?

3.1 基于 AI 辅助编写 MCP Server

在官方文档(modelcontextprotocol.io/quickstart/... MCP Server 的开发方式,以及官方提供的各个语言的 SDK 的示例代码:

写的挺清晰的,如果你懂代码,仔细看一遍很快就能上手,但在 AI 时代,从零自己去写肯定不可能了,官方其实也建议通过 AI 来帮我们实现 MCP,所以在文档中单独还提供了一个 Building MCP with LLMs 的章节。

大概思路如下:

::: block-1 在开始之前,收集必要的文档,以帮助 AI 了解 MCP:

  • 访问 modelcontextprotocol.io/llms-full.t... 并复制完整的文档文本。
  • 导航到 MCP 的 TypeScript 软件开发工具包(SDK)或 Python 软件开发工具包的代码仓库。
  • 复制(README)和其他相关文档。
  • 将这些文档粘贴到你与克劳德的对话中。

提供了相关文档之后,向 AI 清晰地描述你想要构建的服务器类型:

  • 你的服务器将公开哪些资源。
  • 它将提供哪些工具。
  • 它应该给出的任何提示。
  • 它需要与哪些外部系统进行交互。

比如这是一个例子:构建一个 MCP 服务器,该服务器:

  • 连接到我公司的 PostgreSQL 数据库。
  • 将表模式作为资源公开。
  • 提供用于运行只读 SQL 查询的工具。
  • 包含针对常见数据分析任务的提示。 :::

在实际测试中我发现,上面提到的 modelcontextprotocol.io/llms-full.t... 这个文档就是整个文档站的内容,里面包含了很多构建 MCP Server 不需要的内容,反而会给模型造成干扰,大家可以直接参考我的提示词:

markdown 复制代码
## 需求

基于提供的 MCP 相关资料,帮我构建一个 MCP Server,需求如下:

- 提供一个获取当前时间的工具
- 接收时区作为参数(可选)
- 编写清晰的注释和说明
- 要求功能简洁、只包含关键功能
- 使用 TypeScript 编写

请参考下面四个资料:

## [参考资料 1] MCP 基础介绍

- 粘贴 https://modelcontextprotocol.io/introduction 里的内容。

## [参考资料 2] MCP 核心架构

- 粘贴 https://modelcontextprotocol.io/docs/concepts/architecture 里的内容。

## [参考资料 3] MCP Server 开发指引

- 粘贴 https://modelcontextprotocol.io/quickstart/server 里的内容。

## [参考资料 4] MCP Typescript SDK 文档

- 粘贴 https://github.com/modelcontextprotocol/typescript-sdk/blob/main/README.md 里的内容。

下面是一个 AI 帮我生成好的 MCP Server 的关键代码:

js 复制代码
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "TimeServer", // 服务器名称
  version: "1.0.0", // 服务器版本
});


server.tool(
  "getCurrentTime", // 工具名称,
  "根据时区(可选)获取当前时间", // 工具描述
  {
    timezone: z
      .string()
      .optional()
      .describe(
        "时区,例如 'Asia/Shanghai', 'America/New_York' 等(如不提供,则使用系统默认时区)"
      ),
  },
  async ({ timezone }) => {
     // 具体工具实现,这里省略
  }
);

/**
 * 启动服务器,连接到标准输入/输出传输
 */
async function startServer() {
  try {
    console.log("正在启动 MCP 时间服务器...");
    // 创建标准输入/输出传输
    const transport = new StdioServerTransport();
    // 连接服务器到传输
    await server.connect(transport);
    console.log("MCP 时间服务器已启动,等待请求...");
  } catch (error) {
    console.error("启动服务器时出错:", error);
    process.exit(1);
  }
}

startServer();

其实代码非常简单,我们可以拆分为三部分来理解:

第一步:使用官方提供的 @modelcontextprotocol/sdk/server/mcp.js 包,创建一个 McpServer 实例:

js 复制代码
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

/**
 * 创建 MCP 服务器实例
 */
const server = new McpServer({
  name: "TimeServer", // 服务器名称
  version: "1.0.0", // 服务器版本
});

第二步:使用 server.tool 定义提供的工具方法,包括工具方法的名称、工具方法的描述、工具方法的参数、工具方法的具体实现逻辑。

另外使用了 "zod" 这个包定义了方法参数的类型以及描述。

js 复制代码
import { z } from "zod";

server.tool(
  "getCurrentTime", // 工具名称,
  "根据时区(可选)获取当前时间", // 工具描述
  {
    timezone: z
      .string()
      .optional()
      .describe(
        "时区,例如 'Asia/Shanghai', 'America/New_York' 等(如不提供,则使用系统默认时区)"
      ),
  },
  async ({ timezone }) => {
     // 具体工具实现,这里省略
  }
);

第三步:启动 Server,并且使用 SDK 中导出的 StdioServerTransport 来定义工具使用 STDIO 通信协议,等待外部的标准输入,并且把工具的执行结果转化为标准输出反馈到外部。

js 复制代码
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

/**
 * 启动服务器,连接到标准输入/输出传输
 */
async function startServer() {
  try {
    console.log("正在启动 MCP 时间服务器...");
    // 创建标准输入/输出传输
    const transport = new StdioServerTransport();
    // 连接服务器到传输
    await server.connect(transport);
    console.log("MCP 时间服务器已启动,等待请求...");
  } catch (error) {
    console.error("启动服务器时出错:", error);
    process.exit(1);
  }
}

startServer();

就是这么简单,我们只需要按照这个模板来添加更多的工具实现就可以了,另外 Resources、Prompt 的编写方式其实都是类似的,大家只需要按照这套提示词模板,定义好自己的需求,AI(Claude 的准确性最高)基本上都能比较完整的实现。

3.2 使用 inspector 调试 MCP Server

开发完成后,我们可以直接使用官方提供的 MCP Server 调试工具(@modelcontextprotocol/inspector)来进行调试。

我们也可以直接通过 npx 运行 @modelcontextprotocol/inspector

bash 复制代码
npx @modelcontextprotocol/inspector <command> <arg1> <arg2>

比如我们可以调试我们刚刚开发好的工具,这里直接使用 node 运行我们本地构建好的代码:

bash 复制代码
npx @modelcontextprotocol/inspector node dist/index.js

启动成功后,它会在我们的本地监听 6274 端口,我们点击 Connect

连接成功后,我们点击 List Tools

然后可以看到当前 MCP Server 定义的所有工具,我们这里定义了一个获取当前时间的工具,我们点击这个工具,可以对它进行调试:

我们可以在下方所有交互产生的请求和响应的具体数据:

基于这样的思路,我们可以使用 @modelcontextprotocol/inspector 调试任意的 MCP Server, 在你想要使用,但是还不知道怎么使用一个 MCP Server 时,都可以使用它进行调试:

bash 复制代码
npx @modelcontextprotocol/inspector npx -y @modelcontextprotocol/server-filesystem  ~/Downloads

3.3 在 Cline 中测试 MCP Server

下面我们在 Cline 中配置测试一下我们刚刚开发好的 MCP Server,这里我们直接使用 node 执行:

测试结果:

四、MCP Client 是如何和 Server 通信的?

看到这,可能很多同学还会有疑问,MCP Clinet 是怎么知道 MCP Server 都提供了哪些工具列表的?模型又怎么从这些工具中选择出合适的工具?在后续的问答中两者又是如何配合的呢?

下面我们用抓包工具来分别对 Cherry StudioCline 两个工具进行抓包,使用我们刚刚开发好 MCP Server,分析整个交互过程。

4.1 配置抓包工具

我们使用 Charles 这个工具来抓包。

Cherry Studio 中,仅有一部分模型支持 MCP ,这里我们选择阿里云百炼的 qwen-max,实测在 Cherry Studio 中工具调用是非常稳定的:

然后我们在 Charles 中过滤一下阿里云百炼的请求,就可以过滤出后续的请求包了:

Cline 中,所有模型都支持 MCP,这里我们选择的是 Openrouter 下免费的 DeepseekV3 。这里需要注意的是, Charles 并不能直接抓取到 VsCode 的请求,我们需要在 VsCode 下配置代理:

点击 VsCode 左上角的 "文件" 菜单,选择 "首选项 > 设置",搜索 http.proxy,然后在其中填写 http://127.0.0.1:8888,则可以将 vsCode 后续的请求都代理到 Charles 下:

然后在 Charles 中过滤一下 Openrouter 的请求,就可以过滤后续的请求包了:

还有一点需要注意,这些软件一般是都采用的是流式输出,所以在响应报文里不会展示完整的 JSON 内容,

我们可以借助 AI 帮我们还原原始的响应报文"

4.2 Cherry Studio 抓包分析

当我们在 Cherry Studio 中开启我们刚刚开发的 Time MCP Server,然后询问 "纽约时间" :

我们发现软件实际调用了两次 LLM API

下面我们直接来看我已经处理好的请求报文(省略了一些无关内容,只保留关键信息):

  • 第一次请求: 客户端 将从 MCP Server 获取到的所有的工具列表,通过 tools 参数传递给了模型,相当于告诉模型,我现在有这些工具,你可以根据用户的输入(messages.content) 判断是否使用工具,以及使用哪个工具:
  • 第一次响应:模型根据用户输入的内容,以及当前支持的工具列表,判断需要使用 getCurrentTime 工具(这里使用 id 进行映射),并且拼接好了工具调用所需要的标准参数格式:{"timezone": "America/New_York"}
  • 第二次请求:客户端将上次模型返回的工具调用参数,以及在本地执行工具调用后的结果都合并到 messages 里,传递给模型,让模型基于这些信息生成最终回答:
  • 第二次响应:模型根据输入的信息产出最终回答给用户的内容:

4.3 Cline 抓包分析

我们在 Cline 中配置好 Time MCP Server ,并且询问同样的问题:

然后我们发现 Charles 中同样抓到两条请求:

同样的,我们把请求报文处理好,并保存到本地查看:

  • 第一次请求:我们发现请求中并没有像 Cherry Studio 一样,包含 tools 字段,而是包含了一段超长的系统提示词(统计了一下,居然长达 41695 个字符,可见 Cline 真的挺费 Token 的。。。):

然后,我对这段系统提示词进行了处理了分析,提取出了关键的部分,我们来具体分析一下:

提示词第一部分:身份设定:

提示词第二部分:工具使用相关:

  • 使用原则:说明工具需用户批准后执行,每条消息只能用一个工具,依据上次工具使用结果逐步完成任务。
  • 格式规范:给出工具使用的类似 XML 标签格式示例,如 <read_file><path>src/main.js</path></read_file>
  • 具体工具介绍:以 use_mcp_tool 为例,详细说明其描述、参数(必填的 server_name、tool_name、arguments)和用法,并给出使用示例。
  • 使用指南:包括在 <Think> 标签中评估信息、选择合适工具、迭代使用工具、按格式使用工具、依据用户回复结果决策、等待用户确认等内容。

提示词第三部分:MCP Server 相关:

  • 协议作用:介绍模型上下文协议(MCP)允许系统与本地运行的 MCP 服务器通信,扩展能力。
  • 已连接服务器操作:说明连接服务器后可通过 use_mcp_tool 使用工具、access_mcp_resource 访问资源。
  • 可用工具:说明工具的描述、具体参数信息:

提示词第四部分:一些约束信息:

提示词第五部分:目标工作流程:

  • 任务分析与目标设定:分析任务设定可实现目标并排序。
  • 目标处理与工具使用:依次处理目标,每次用一个工具,调用工具前分析并确定参数。
  • 任务完成与结果展示:完成任务用 attempt_completion 工具展示结果,可提供 CLI 命令,根据反馈改进,避免无意义对话。

这就是 Cline 系统提示词的关键部分,当前这里省略了一些和 MCP 不相关的信息,比如 Cline 本身提供的一些读取文件、编写代码的工具等等。

  • 第一次响应:这里和 Cherry Studio 也不一样,并没有使用 tools_call 字段,而是直接使用 assistant 字段返回需要调用的工具信息:

我们格式化一下,发现和系统提示词里要求的工具调用格式是相同的:

  • 第二次请求:客户端将上次模型返回的工具调用参数,以及在本地执行工具调用后的结果都合并到 messages 里,传递给模型,让模型基于这些信息生成最终回答:
  • 第二次响应:模型根据输入的信息产出最终回答给用户的内容,

这里依然按照系统提示词中约定的标准格式返回:

4.4 MCP 的核心流程总结

看到这里,大家应该比较明确了,Cherry Studio 实际上是通过将 MCP Server 中提供的工具、响应结果,转换为 Function Call 的标准格式来和模型进行交互。

ClineMCP Server 中提供的工具、响应结果转换未一套自己约定的输入、输出的标准数据格式,通过系统提示词来声明这种约定,再和模型进行交互。

这也解释了,为什么在 Cherry Studio 中只有一部分模型支持 MCP,前提是选择的模型需要支持 Function Call 的调用,并且在客户端进行了特殊适配;而 Cline 则使用的是系统提示词,所以所有模型都支持。

  1. 初始化与工具列表获取

    用户首先对MCP客户端进行初始化操作,随后MCP客户端向MCP服务器发送请求以获取可用的工具列表,MCP服务器将工具列表返回给客户端。

  2. 用户输入与提示词构建

    用户在客户端完成初始化后,向MCP客户端输入具体请求。客户端将此前获取的工具列表与用户输入内容相结合,共同组成用于询问LLM的提示词。

  3. 工具传递方式选择

    MCP客户端通过两种方式之一将提示词传递给LLM:

    • 方式1:使用Function Call(函数调用)直接携带工具列表信息;
    • 方式2:在系统提示词(System Prompt)中包含工具列表。
  4. LLM判断与响应

    LLM接收到提示词后,返回判断结果:

    • 无需工具:LLM直接将处理结果通过MCP客户端回复给用户;
    • 需要工具:LLM先向客户端返回所需工具的参数格式要求。
  5. 工具命令生成与执行

    若需要工具,MCP 客户端根据 LLM 提供的参数格式,以及 MCP Server 配置的命令模板进行拼接,生成完整的可执行命令,并在本地环境(Local_Env)中执行该命令。

  6. 结果处理与输出

    本地环境执行命令后,将结果返回给 MCP 客户端。客户端将执行结果提交给 LLM,由 LLM 对技术化的执行结果进行处理,最终以人性化的语言形式输出给用户。

五、使用 mcp-client-nodejs 展示 MCP 交互流程

为了方便大家更好的学习和 MCP ,我使用 Node.js 开发了一个基础版的 MCP Clinet(基于 Function Call 实现),项目地址:github.com/ConardLi/mc...

MCP Clinet 的开发还是稍微有点门槛的,所以不在这期教程里演示具体的代码细节,主要用此工具来帮助大家更深入的理解 MCP ClinetMCP Server 的整个交互流程,至于开发者们如果感兴趣可以直接基于我的项目进行二次开发。

5.1 基于 LLM 构建 MCP Client

同样的,这个客户端的核心逻辑也是基于 AI 编写的,大家可以直接使用我这个提示词:

markdown 复制代码
## 需求
我想开发一个 Node.js 版的 MCP Clinet ,下面有一些参考材料,包括 MCP 基础介绍、MCP 核心架构介绍、MCP Clinet 开发教程,请你根据这些参考材料帮我开发一个 MCP Client,注意增加完善的中文注释,以及在开发完成后编写一个完善的中文 Readme 说明文档。

## MCP 基础介绍
粘贴 https://modelcontextprotocol.io/introduction 的内容。

## MCP 核心架构介绍
粘贴 https://modelcontextprotocol.io/docs/concepts/architecture 的内容。

## MCP Client 开发教程
粘贴 https://modelcontextprotocol.io/quickstart/client 的内容。

## 注意点
- 使用 openai 的 SDK 替换 @anthropic-ai/sdk ,这样可以支持更多的模型,兼容性更好
- 你不要指定依赖的版本,如果遇到安装依赖让我自己来安装,你只负责编写代码

5.2 mcp-client-nodejs 项目介绍

核心特性

  • 支持连接任何符合 MCP 标准的服务器
  • 支持兼容 OpenAI API 格式的 LLM 能力
  • 自动发现和使用服务器提供的工具
  • 完善的日志记录系统,包括 API 请求和工具调用
  • 交互式命令行界面
  • 支持工具调用和结果处理

安装和配置

  1. 克隆仓库
bash 复制代码
git clone https://github.com/yourusername/mcp-client.git
cd mcp-client
  1. 安装依赖
bash 复制代码
npm install
  1. 配置环境变量

复制示例环境变量文件并设置你的 LLM API 密钥:

bash 复制代码
cp .env.example .env

然后编辑 .env 文件,填入你的 LLM API 密钥、模型提供商 API 地址、以及模型名称:

ini 复制代码
OPENAI_API_KEY=your_api_key_here
MODEL_NAME=xxx
BASE_URL=xxx
  1. 编译项目
bash 复制代码
npm run build

使用方法

要启动 MCP 客户端,你可以使用以下几种方式:

  1. 直接指定服务器脚本路径
bash 复制代码
node build/index.js <服务器脚本路径>

其中 <服务器脚本路径> 是指向 MCP 服务器脚本的路径,可以是 JavaScript (.js) 或 Python (.py) 文件。

  1. 使用配置文件
bash 复制代码
node build/index.js <服务器标识符> <配置文件路径>

其中 <服务器标识符> 是配置文件中定义的服务器名称,<配置文件路径> 是包含服务器定义的 JSON 文件的路径。

json 复制代码
{
  "mcpServers": {
    "time": {
      "command": "node",
      "args": [
        "/Users/xxx/Desktop/github/mcp/dist/index.js"
      ],
      "description": "自定义 Node.js MCP服务器"
    },
    "mongodb": {
      "command": "npx",
      "args": [
        "mcp-mongo-server",
        "mongodb://localhost:27017/studentManagement?authSource=admin"
      ]
    }
  },
  "defaultServer": "mongodb",
  "system": "自定义系统提示词"
}
  1. 使用 npm 包(npx)

你可以直接通过 npx 运行这个包,无需本地克隆和构建:

bash 复制代码
# 直接连接脚本
$ npx mcp-client-nodejs /path/to/mcp-server.js

# 通过配置文件连接
$ npx mcp-client-nodejs mongodb ./mcp-servers.json

注意:需要在当前运行目录的 .env 配置模型相关信息

5.3 分析 MCP 详细交互流程

MCP Client 包含一个全面的日志系统,详细记录所有关键操作和通信。日志文件保存在 logs/ 目录中,以 JSON 格式存储,方便查询和分析。

  • LLM 的请求和响应 - 记录与 LLM API 的所有通信
  • 工具调用和结果 - 记录所有工具调用参数和返回结果
  • 错误信息 - 记录系统运行期间的任何错误

日志文件连统命名为 [index] [log_type] YYYY-MM-DD HH:MM:SS.json,包含序号、日志类型和时间戳,方便按时间顺序查看整个会话。

下面我们使用 mcp-mongo-server 来演示整个流程,首先在项目目录新建 mcp-servers.json,并填写下面的配置:

json 复制代码
{
  "mcpServers": {
    "mongodb": {
      "command": "npx",
      "args": [
        "mcp-mongo-server",
        "mongodb://localhost:27017/studentManagement?authSource=admin"
      ]
    }
  },
  "system": "使用中文回复。\n\n当用户提问中涉及学生、教师、成绩、班级、课程等实体时,需要使用 MongoDB MCP 进行数据查询和操作,表结构说明如下:xxx"
}

然后执行 node build/index.js mongodb ./mcp-servers.json,客户端成功启动:

然后我们输入一个问题:张老师教哪门课? ,因为教师信息、课程信息分别存储在两个表:

所以这个任务 AI 也需要分两步完成,第一步先去检索教室表中姓张的老师,我们输入继续:

下面检索课程表,得出最终信息:

然后我们发现,整个过程一共产生了 13 调日志:

我们逐个来看一下:

  • [0] [GET Tools]:在客户端初始化时,拉取当前配置的 MCP Server 下提供的所有工具列表:
  • [1] [LLM Request] :向大模型发送 用户问题,并将所有工具列表(包含函数+参数的具体定义)通过 tools 字段传递过去:
  • [2] [LLM Response] :模型根据用户输入 + 支持的参数列表判断需要使用的工具,并通过 tool_calls 字段返回:
  • [3] [Tool Call]:客户端根据模型返回的函数+参数+MCP 服务器的配置,拼接成一条可执行的命令,执行第一次工具调用:从 teachers 表中检索姓张的老师的信息:
  • [4] [Tool Call Response]:工具返回结果:姓张的老师的 ID:
  • [5] [LLM Request]:客户端将工具调用的结果反馈给模型:
  • [6] [LLM Response] :模型判断本次任务没有完成,需要继续执行工具调用(根据教师 ID 去课程表查询课程信息):
  • [7] [LLM Request] :因为一次交互已经完成,但并未完成任务,所以客户端主动询问用户是否继续,用户选择继续,所以再次发送请求:
  • [8] [LLM Response] :和 6 返回的内容一样(实际上这一步有点重复),如果用户选择继续可以直接发起第二次工具调用

  • [9] [Tool Call] :客户端执行工具调用,查询课程信息:

  • [10] [Tool Call Response]:工具返回班级信息:
  • [11] [LLM Request] :客户端将之前工具调用的所有信息再次返回给模型:
  • [12] [LLM Response] :模型最终给出人性化输出:

5.4 最终流程总结

以上的过程,非常清晰的展示了整个 MCP 的交互流程,下面我们最后再总结一下:

一、初始化阶段

  1. 客户端启动与工具列表获取
    • 用户首先启动 MCPClient,完成初始化操作。
    • MCPClientMCPServer 发送 GET /tools/list 请求,获取可用工具的元数据。
    • MCPServer 返回包含工具名称、功能描述、参数要求等信息的 工具列表JSON,供客户端后续构建提示词使用。

二、交互阶段

1. 用户输入与提示词构建

  • 用户通过 MCPClient 输入自然语言请求(如"查询服务器状态""生成文件报告"等)。
  • MCPClient 将用户请求与初始化阶段获取的 工具列表 结合,生成包含任务目标和工具能力的提示词(Prompt),传递给 LLMService(大语言模型服务层)。

2. 工具描述传递方式(二选一)

  • 方式1(Function Call)
    LLMService 通过 LLM_API 调用大语言模型时,在请求中直接携带 工具schema(结构化工具定义,如参数格式、调用格式),告知模型可用工具的调用方式。
  • 方式2(系统提示词嵌入)
    LLMService 将工具列表以自然语言描述形式嵌入 系统提示词(System Prompt),让模型在理解用户需求时知晓可用工具的功能边界。

3. 模型决策与响应解析

  • LLM_API 返回包含 tool_decision (工具调用决策)的响应:
    • 若判定 无需工具(如简单文本回复),响应直接包含最终答案;
    • 若判定 需要工具(如需要执行本地命令、调用外部接口),响应中包含所需工具的参数要求(如工具名称、入参格式)。
  • LLMService 解析决策结果,将信息传递给 MCPClient

4. 工具调用分支(需要工具时)

  • 获取命令模板MCPClient 根据模型指定的工具名称,在初始化时保存的工具配置中取出对应的 命令模板(如Shell命令格式、API调用参数模板)。
  • 生成与执行命令MCPClient 将用户输入参数与命令模板结合,通过 ToolService (工具执行服务)生成完整可执行命令,并提交给 本地系统 执行。
  • 结果处理本地系统 返回原始执行结果(如命令输出文本、API返回数据),ToolService 将其转换为 结构化结果 (如JSON格式),反馈给 MCPClient
  • 二次调用模型生成最终回复MCPClient 将结构化结果与用户原始问题一并提交给 LLMService ,通过 LLM_API 调用模型,将技术化的执行结果转化为自然语言描述(如将"服务器CPU使用率80%"转化为"当前服务器CPU负载较高,建议检查进程")。

5. 直接回复分支(无需工具时)

  • 若模型判定无需工具,MCPClient 直接将模型响应显示给用户(如简单的文本问答、信息总结)。

三、最终输出

无论是否经过工具调用,MCPClient 最终将处理后的 自然语言结果 呈现给用户,完成整个交互流程。

六、最后

关注《code秘密花园》从此学习 AI 不迷路,code秘密花园 AI 教程完整的学习资料汇总在这个飞书文档:rncg5jvpme.feishu.cn/wiki/U9rYwR...

相关推荐
_一条咸鱼_10 分钟前
AI 大模型的 MCP 原理
人工智能·深度学习·面试
noravinsc11 分钟前
html页面打开后中文乱码
前端·html
_一条咸鱼_17 分钟前
AI 大模型 Function Calling 原理
人工智能·深度学习·面试
胚芽鞘68139 分钟前
vue + element-plus自定义表单验证(修改密码业务)
javascript·vue.js·elementui
小满zs44 分钟前
React-router v7 第四章(路由传参)
前端·react.js
寰宇视讯1 小时前
金山科技在第91届中国国际医疗器械博览会CMEF 首发新品 展现智慧装备+AI
大数据·人工智能·科技
訾博ZiBo1 小时前
AI日报 - 2025年04月17日
人工智能
小陈同学呦1 小时前
聊聊双列瀑布流
前端·javascript·面试
耿雨飞1 小时前
二、The Power of LLM Function Calling
人工智能·大模型
金能电力1 小时前
金能电力领跑京东工业安全工器具赛道 2025年首季度数据诠释“头部效应”
人工智能·安全·金能电力安全工器具