三分钟带你全面理解MCP

575 阅读15分钟

首发于公众号 code进化论,欢迎关注。

理论知识

随着大语言模型(LLM, Large Language Model)如 GPT、Claude、DeepSeek 等在自然语言处理任务中取得显著突破,越来越多的系统开始尝试将其作为核心能力集成进各类智能应用中。然而,在实际应用过程中,我们逐渐发现,大语言模型在设计理念和实现方式上存在一些结构性缺陷,这些缺陷直接限制了其在高可靠性、高实时性场景中的表现,也成为推动“增强型语言系统”兴起的重要动因。

缺陷

缺陷一:知识封闭,难以实施更新

大语言模型的训练方式决定了其知识边界是静态的,模型训练往往使用某一时间点前收集的大规模语料,在此基础上通过长时间离线训练获得语言生成能力。一旦训练完成,其内部参数便不再具备学习能力,也无法通过外部交互进行知识更新。就像是一个知识丰富但是被困在屋子里的人,只能依靠自己已有的知识回答问题。

举个例子:GPT-3 是 2020 年训练的,即使你问它 2023 年 NBA 总冠军是谁,它也答不出来——它不知道!

缺陷二:无法调用外部工具

大模型本质上是一个语言生成器,而不是任务执行器,简单来说它就是一个聊天机器人,连最简单的天气查询都做不到,例如当用户说:

“请帮我查一下明天下午北京的天气。”

普通的大语言模型并不会真的去查天气数据,而是可能这样回答:

“明天下午北京可能会是晴天,气温在20度左右。”

这听起来像是个回答,但它完全是模型根据过去训练数据中出现过的天气描述“猜”的,并不来自于任何真实的天气数据源。

而要构建一个简单的天气预报 agent 就需要大模型能够调用类似 OpenWeather 这样的天气查询工具。

方案

RAG的提出:解决知识滞后

检索增强生成(RAG,Retrieval-Augmented Generation) 是一种结合信息检索和生成模型的技术框架。它通过将大语言模型(LLM)的生成能力与外部知识库的检索能力结合,使 AI 应用在回答问题或生成内容时更准确。

简单来理解 RAG 系统所做的事情是帮助大模型临时性地获得他所不具备的外部知识,允许它在回答问题之前先找答案。

Function Calling的提出:赋予模型行动力

解决知识问题只是第一步,如何让大模型具备调用外部工具的能力,才是其向智能体(Agent)演进的关键。为此,Function Calling(函数调用)机制应运而生。

Function Calling 是 Open AI 于 2023 年 6 月份首次提出的方案,可查看原文链接

由于底层的技术限制,大模型本身是无法和外部工具进行交互的,因此 Function Calling 的思路就是创建一个外部函数作为中介:

这样大模型就能间接的去调用外部工具,下面是 OpenAi 官方给出的一个示例,展示了整个 Function Calling 的执行流程:

  • 当用户输入 query 后,agent 首先会将已有的工具函数及其描述&query送给大模型。

    from openai import OpenAI
    import json
    
    client = OpenAI()
    
    tools = [{
        "type": "function",
        "name": "get_weather",
        "description": "Get current temperature for provided coordinates in celsius.",
        "parameters": {
            "type": "object",
            "properties": {
                "latitude": {"type": "number"},
                "longitude": {"type": "number"}
            },
            "required": ["latitude", "longitude"],
            "additionalProperties": False
        },
        "strict": True
    }]
    
    input_messages = [{"role": "user", "content": "What's the weather like in Paris today?"}]
    
    response = client.responses.create(
        model="gpt-4o",
        input=input_messages,
        tools=tools,
    )
    
  • 大模型会根据用户 query 的意图分析调用哪一个工具,并将工具函数名及其需要的参数输出。

    [{
        "type": "function_call",
        "id": "fc_12345xyz",
        "call_id": "call_12345xyz",
        "name": "get_weather",
        "arguments": "{\"latitude\":48.8566,\"longitude\":2.3522}"
    }]
    
  • agent 拿到工具函数及其参数后,会执行代码调用函数,拿到工具函数返回的参数。

  • agent 再将之前的信息和工具函数的返回值送给大模型,让大模型进行总结。

  • 大模型返回总结后的结果输出给 agent,最终展示给用户。

Function Calling 这个能力确实是挺好的,给了大模型赋予了更多的能力,目前像 coze 的插件系统、千帆 Appbuilder 里的组件系统都是基于 Function Calling 的思路来封装的。

但是它有一个比较大的缺点,就是实现成本太高了,主要体现在几方面。

  • 编写外部函数的成本高

    一个简单的外部函数往往就要上百行代码,同时为了让大模型能够认识这些外部函数,需要开发者为这些外部函数编写 JSON Schema 格式的功能说明。

  • 微调大模型以支持 Function calling 能力

    通过微调大模型让他能够认识工具函数,并构造出函数所需要的参数。

  • 协议碎片化(重点

    OPEN AI 最开始提出这项技术的时候,并没有想让它成为一项标准,所以虽然后续很多模型也支持了 Function Call 的调用,但是各自实现的方式都不太一样。

    这也就意味着,如果我们要发开一个 Function Call 工具,需要对不同的模型进行适配,比如参数格式、触发逻辑、返回结构等等,这个成本是非常高的。

    所以对与开发者来说,大部分情况还需要借助像 Dify、coze、Appbuilder 等平台来开发 Ai Agent。

  • 设计一套提示词模板

    为了提高 Function Calling 的准确率,可能还需要开发者去设计一套提示词模板,例如千帆 Appbuilder 提供的角色指令功能。

详细的函数定义说明可查看 OpenAI 文档

什么是MCP?

MCP(Model Context Protocol,模型上下文协议)是一种由 Anthropic 公司推出的一个开放标准协议,旨在标准化大型语言模型(LLM)与外部数据源和工具的集成,其核心目标是解决“MxN”问题,即将 M 个不同的 LLM 与 N 个不同的工具集成的组合复杂性。通过提供一个通用的开放标准,进而取代了 Function Calling 碎片化的定制集成。

MCP整体架构

MCP组成部分

MCP Host

Host 指的是内置了 MCP Client 的应用程序,例如 Claude Desktop、Cursor,内部包含了对大模型的调用,调用 MCP Client 连接 MCP Server 等。在 MCP 的官方文档中已经列举出了目前支持 MCP 协议的 MCP Host:

MCP Client

Client 位于 Host 内部,负责通过标准的 MCP 协议与 MCP Server 建立连接和交互。

MCP Server

server 是一个轻量级应用程序,负责实现各种和三方资源交互的逻辑,比如访问数据库、浏览器、本地文件,然后通过标准化模型上下文协议公开其功能,client 连接到 server 之后能够访问其公开的功能。MCP Server 主要提供三种能力:

  • 资源(Resources)类似文件的数据,可以被客户端读取(如 API 响应、文件内容)。
  • 工具(Tools):LLM 可以调用的函数(需要用户授权)。
  • 提示词(Prompts):预先编写的模板,帮助用户完成特定任务。

这三个组成部分的关系如下图所示:

有了 MCP 协议之后,开发者只需要为每个工具按照 MCP 协议开发一个 MCP Server,不同的大模型只需要通过 MCP Client 和 MCP Server 建立连接就能使用 Server 中提供的功能,进而间接的调用外部工具,这可以大大减少开发者的工作量。

目前大部分第三方工具都提供了对应的 MCP Server,例如百度地图、高德地图、Github等,想查找更多 MCP Server 可以到下面几个网站:

使用案例

现在我们成热打铁,使用一个案例来带大家快速上手 MCP,这里我们选用 VsCode + Cline 来演示。

在这之前需要先保证电脑上已经安装 nodejs环境,因为下面演示的 MCP Server 需要在 node 环境运行。

在 vscode 中安装 Clien

模型配置

这里要配置两部分:

  • 模型提供商选择 OpenRouter,因为它提供了免费的 deepseek v3
  • 选择 deepseek-chat:free 模型

安装MCP Server

在 cline 的 MCP Server 商场中选择 github 对应的 MCP Server 并进行安装。

最终 cline 会提示如果要访问你的 github 仓库需要输入你的 API Key

这里我们只需要按照提示点击链接去生成一个 API Key,并给这个 API Key 赋予相应的操作权限,比如仓库内容的访问权限、仓库的创建、删除权限等:

查看是否连接

在MCP Server 页面选择已安装的 MCP Server,如果显示绿的按钮,就表示已经连接,并可以查看 MCP Server 提供的工具。

调用MCP Server

接下来就可以直接调用 MCP Server 中的工具来操作 github 仓库,比如在输入框中输入查询 query:

我的github名字是xxx,我有哪些仓库

最终大模型会返回 github 下所有的仓库。

MCP流程解析

看到这里其实大家基本对 MCP 有了一个初步的了解,但是大多数人对整个 MCP 运行的流程肯定还存在疑问,比如

  • MCP Clinet 是怎么知道 MCP Server 都提供了哪些工具列表的。
  • 大模型怎么从这些工具中选择出合适的工具。
  • MCP 是如何解决 Function Calling 的弊端。

接下来就会带大家使用上面的案例来一步步解析 MCP 完整的流程,在这个过程中会使用抓包工具 Charles 对 Cline 进行抓包,抓包的信息主要分为两部分:

  • MCP Host 和大模型之间的通信。

  • MCP Client 和 MCP Server 之间的通信。

    由于上面的使用案例使用的是 stdio 的通信方式,我们会在后面的章节通过手动开发一个使用 SSE 作为通信方式的 MCP Server,这样就能进行抓包分析。

配置抓包工具

由于 Charles 并不能直接抓取到 VsCode 的请求,我们需要在 VsCode 下配置请求代理,在 Vscode 的配置页搜索 http.proxy,然后在其中填写 http://127.0.0.1:8888,这样可以将 Vscode 的请求都代理到 Charles 下:

这样后续 openRouter 的请求就能在 charles 看到:

这里要注意的是请求的返回结果都是流式的(sse请求),所以返回的信息都是一段一段的,我们可以直接把这些消息全部复制出来保存到一个 txt 文件,让 chatgpt 帮忙合并一下:

请求分析

Charles 的抓包结果看,Cline 调用了两次 LLM API,下面对这两次请求进行分析:

  • 第一次请求

    第一次请求的请求体包含两部分内容,第一部分是一段很长的系统提示词,告诉大模型他的角色信息、可用工具等,第二部分是用户的 query 信息。

    系统提示词主要包含五部分:

    • 角色指定

    • 工具使用介绍

    • MCP Server 信息介绍

    • 约束信息

    • 目标工作流程

    因此只要大模型能读懂系统提示词就能读懂 MCP Server 提供的工具及其使用方式,这也就解决了 Function Calling 中存在的 n * m 的问题。

  • 第一次请求的响应

    这里的返回和系统提示此中要求的是一致的,在 中展示大模型对用户需求的分析及满足客户需求要调用的工具。在 <use_mcp_tool> 标签中展示了调用的工具及其传入的参数。

  • 第二次请求

    第二次请求的请求体内容在第一次请求的基础上加了两条:

    • 第一次请求的返回结果,即大模型思考和需要调用的工具信息。
    • 调用工具后的返回结果

    将这些信息传给大模型进行最终的答案总结。

  • 第二次请求响应

    大模型根据输入的信息产出最终回答给用户的内容,下面是合并 sse 消息之后的结果。

流程总结

MCP Server原理

mcp server 本质上就是一个服务,通过标准化模型上下文协议公开特定功能,MCP Client 与 MCP Server 建立连接之后可以通过特定的方式进行通信,在这个过程涉及了两个问题:

  • 如何以及何时启动 MCP Server。
  • MCP Client 如何和 MCP Server 进行通信。

通信方式

MCP Server 支持两种通信方式:STDIOSSE

  • STDIO(标准输入输出

    MCP Client 将 MCP Server 作为一个子进程启动,运行在本地环境,它们通过标准输入/输出流进行通信,消息格式为 JSON-RPC ,整体流程如下:

    例如 Github-MCP-Server、Filesystem-MCP-Server 都是 STDIO 类型的。如果想将 STDIO 的通信方式转成 SSE,可以使用 supergateway 进行转换**。**

  • SSE(服务器推送事件

    MCP Server 会部署为一个远程服务,MCP Client 通过 HTTP 协议连接远程服务器,服务器可以主动推送数据。

启动 MCP Server

不同通信方式的 MCP Server 启动方式会有所不同,使用 STDIO 的 MCP Server 需要在本地环境启动一个单独的进行运行,而使用 SSE 的 MCP Server 是一个已部署的远程服务,接入简单,下面通过分析两者的配置文件来介绍。

  • STDIO(标准输入输出

    这里以 Github-MCP-Server 为例:

    {
      "mcpServers": {
        "github.com/modelcontextprotocol/servers/tree/main/src/github": {
          "command": "npx",
          "args": ["-y", "@modelcontextprotocol/server-github"],
          "env": {
            "GITHUB_PERSONAL_ACCESS_TOKEN": "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"
          },
          "disabled": false,
          "autoApprove": []
        }
      }
    }
    
    • command

      指定如何启动该 MCP Server,这里使用 npxnpx 是 Node.js 生态系统中的一个命令行工具,它的主要功能就是帮助我们快速运行一些通过 npm 安装的工具,而不需要我们手动去下载安装这些工具到全局环境。

      除了npx 我们也可以通过 nodepython 等工具来启动 MCP Server,这个具体要看 MCP Server 的实现。

    • args

      传递给 npx 命令的参数,"-y" 其实就等同于 --yes,其作用是在执行命令时自动同意所有提示信息,可以避免交互式的确认步骤,第二个参数其实就是这个 npm 包的名字。

    • env

      定义了环境变量,用于传递配置或授权信息给 MCP Server。

  • SSE(服务器推送事件

    {
      "mcpServers": {
        "baidu-maps": {
          "url": "https://mcp.map.baidu.com/sse?ak=您的AK"
        }
      }
    }
    

    基于 SSE 通信的 MCP Server 是一个远程的服务,所以只需要知道这个远程服务的 url 就能连接 MCP Server,MCP Server 所需要的鉴权参数通过 SearchParams 传入。

如何开发一个MCP Server

官方文档中可以找到 MCP Server 的开发方式,并且官方提供了各个语言的 SDK 的示例代码:

整个开发流程写的非常清晰,跟着官方的流程就能快速开发一个 MCP Server,但是官方文档中只给出了 STDIO 类型的 MCP Server 开发示例,如果想学习 SSE 类型的 MCP Server 开发流程可以到 SDK 的 github 仓库中找到对应的示例:

创建MCP Server实例

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

import { McpServerfrom "@modelcontextprotocol/sdk/server/mcp.js";

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

定义工具

import {z} from "zod";

/** 增加一个计算两数之和的工具 */
server.tool(
    "calculate_sum",
    "计算两个数的和",
    {
        a: z.number().describe('一个数字'),
        b: z.number().describe('一个数字')
    },
    async ({a, b}) => ({
        content: [{type: "text", text: String(a + b)}]
    })
);

/** 增加一个计算两数之积的工具 */
server.tool(
    "calculate_multiply",
    "计算两个数的乘积",
    {
        a: z.number().describe('一个数字'),
        b: z.number().describe('一个数字')
    },
    async ({a, b}) => ({
        content: [{type: "text", text: String(a * b)}]
    })
);

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

启动 Server

import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";

const startServer = async () => {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error("MCP Server running on stdio");
}
  
startServer().catch((error) => {
    console.error("Fatal error in startServer():", error);
    process.exit(1);
});

启动 Server 时需要传入使用的通信工具,这里使用的 StdioServerTransport 通信协议,启动后等待外部的标准输入,并且把工具的执行结果转化为标准输出反馈到外部。

Cline接入 MCP Server

在 Cline 的 MCP Server 配置中加入刚刚创建的 MCP Server,这里需要注意的是由于我们创建的 MCP Server 是一个 STDIO 的 Server,所以需要在配置中配置他的启动命令,这里使用的是 node ,因为我们的产出就是一个本地的 js 文件,同时将产物的地址作为参数传给 node 命令。这样就能运行这个 js 文件启动服务。

参考资料

【1】Function Calling: openai.com/index/funct… 【2】标准输入输出:en.wikipedia.org/wiki/Standa… 【3】JSON-RPC:en.wikipedia.org/wiki/JSON-R… 【4】supergateway: github.com/coding-alt/… 【5】服务器推送事件: developer.mozilla.org/en-US/docs/… 【6】mcp官方文档: modelcontextprotocol.io/quickstart/…