揭开 MCP 的神秘面纱:标准化 AI 上下文管理的未来(上)

引言

最近MCP大火,本文尝试揭开它神秘的面纱。文章较长,分为上下两篇。

架构

MCP协议遵循客户端-主机-服务器架构,其中一个主机应用运行多个客户端实例,每个客户端实例维护了和服务器建立的独立的连接。

image-20250402171530670

  • Host: 希望通过MCP访问数据的程序,比如一个聊天应用程序。
  • Client: 与服务器保持1:1连接(会话)的客户端,Host通过这个Client连接不同的Server提供的功能。
  • Server:通过MCP公开特定功能的轻量级程序,比如上图的Server可以访问文档(Wiki)、谷歌搜索和代码执行等。

通过这种架构可以把一些敏感信息,如访问内部系统的密钥放到Server端,避免泄露的风险。

Host

Host进程充当容易和协调器:

  • 创建和管理多个客户端实例
  • 控制客户端连接权限和生命周期
  • 执行安全策略和同意要求
  • 处理用户授权
  • 协调LLM的集成和采样
  • 管理跨客户端的上下文聚合

Client

每个客户端由主机创建并维护一个独立的服务器连接:

  • 为每个服务器建立一个有状态会话
  • 处理协议协商和能力交换
  • 双向路由协议消息
  • 管理订阅和通知
  • 维护服务器之间的安全边界

Server

服务器提供专门的上下文和功能:

  • 通过MCP原语暴露资源、工具和提示
  • 独立运作并专注于特定职责
  • 通过客户端接口请求采样
  • 可以是本地进程,也可以是远程服务

协议层

协议层负责消息封装、请求与响应的关联,以及高级通信模式的处理。

class Session(BaseSession[RequestT, NotificationT, ResultT]):
    async def send_request(
        self,
        request: RequestT,
        result_type: type[Result]
    ) -> Result:
        """
        发送请求并等待响应。如果响应包含错误抛出McpError
        """
        

    async def send_notification(
        self,
        notification: NotificationT
    ) -> None:
        """发送不需要响应的单向通知"""


    async def _received_request(
        self,
        responder: RequestResponder[ReceiveRequestT, ResultT]
    ) -> None:
        """处理来自对方的请求"""


    async def _received_notification(
        self,
        notification: ReceiveNotificationT
    ) -> None:
        """处理来自对方的通知"""

传输层

传输层处理客户端和服务器之间的实际通信,现在定义了两种传输机制:

  1. Stdio
    • 使用标准输入/输出进行通信
    • 适用于本地进程
  2. 带SSE的HTTP(后续版本升级为Streamable HTTP)
    • 使用 Server-Sent Events (SSE) 进行服务器向客户端的消息传输
    • 使用 HTTP POST 进行客户端向服务器的消息传输

所有传输方式均使用 JSON-RPC 2.0 进行消息交换。

带SSE的HTTP

SSE(Server-Sent Events)是一种基于 HTTP 的单向通信机制,允许服务端持续不断地向客户端推送数据。它通过保持一个 HTTP 长连接,将多个事件以“流”的方式发送给客户端,适合用于实时消息推送、进度更新、通知等场景。客户端连接后,会一直监听服务端传来的消息,直到连接关闭或中断。

Client Server 发起 HTTP GET 请求 (/events) 返回 200 + Content-Type: text/event-stream 连接保持不关闭 event: message\ndata: {"msg":"你好"} 触发 message 事件处理逻辑 loop [持续推送] 中断连接(如刷新页面) 连接关闭 alt [客户端断开] [服务端断开] 客户端可自动重连(默认 3 秒) Client Server

SSE的特点是:

  • 单向: 服务器 → 客户端
  • 基于HTTP
    • 通用性
    • 调试更简单
  • 传输格式为文本(UTF-8)
  • 内置自动重连

现在大多数大模型(如 OpenAI、Claude、Gemini、Mistral 等)在支持流式响应(streaming response)时,通常是通过 SSE(Server-Sent Events)协议来实现的。

一旦启用 stream,返回的 HTTP 响应就变成了 Content-Type: text/event-stream,也就是 SSE 格式:

返回数据大概像这样:

data: {"id": "...", "choices": [{"delta": {"content": "你"}}]}

data: {"id": "...", "choices": [{"delta": {"content": "好"}}]}

data: {"id": "...", "choices": [{"delta": {"content": ","}}]}

data: {"id": "...", "choices": [{"delta": {"content": "我"}}]}

data: {"id": "...", "choices": [{"delta": {"content": "是"}}]}

...

data: [DONE]

再来看一下带SSE的HTTP,在该模式中,服务端作为独立进程运行。服务端必须提供两个端点:

  1. SSE端点(/sse):用于客户端建立连接并接收服务端消息;
  2. HTTP POST端点(/messages/):用于客户端发送消息给服务端;

当客户端连接后,服务端必须发送一个 endpoint 事件,其中包含客户端后续用来发送消息的 URI。之后所有客户端消息必须通过 HTTP POST 请求发送到该端点。

服务端的消息通过 SSE 的 message 事件发送,其内容以 JSON 编码并包含在事件数据中。

Client Server 打开 SSE 连接 发送 endpoint 事件 发送 HTTP POST 消息 发送 SSE 消息事件 loop [消息交换] 关闭 SSE 连接 Client Server

这就是为什么在底层API实现中需要暴露这两个端点:

  starlette_app = Starlette(
          debug=True,
          routes=[
              Route("/sse", endpoint=handle_sse),
              Mount("/messages/", app=sse.handle_post_message),
          ],
      )

术语 & 流程

服务器功能

服务器通过 MCP 提供语言模型上下文的基本构建块。这些原语使客户端、服务器和语言模型之间能够进行丰富的交互:

  • Prompts(提示):预定义的模板或指令,用于引导语言模型的交互
  • Resources(资源):提供给模型的结构化数据或内容,以增加上下文信息
  • Tools(工具):可执行的函数,使模型能够执行操作或获取信息

每种原语可按以下控制层级进行归类:

原语控制方式描述示例
Prompts用户控制由用户选择触发的交互式模板斜杠命令、菜单选项
Resources应用控制由客户端管理并附加的上下文数据文件内容、Git 历史
Tools模型控制暴露给 LLM 以执行操作的函数API POST 请求、文件写入

我们这里重点关注工具能力。

工具调用

MCP允许服务器暴露工具,以供LLM使用。工具使LLM能与外部系统交互,例如查询数据库、调用API等。每个工具有唯一的名称、包含描述其模式的元数据。

工具定义包含:

  • name : 工具的唯一标识符
  • description:工具功能描述
  • inputSchema:JSON Schema,定义预期参数
  • annotations:可选,描述工具行为

支持工具的服务器必须声明工具能力:

{
  "capabilities": {
    "tools": {
      "listChanged": true
    }
  }
}

listChanged告诉服务器是否会在可用工具列表更改时发送通知。

获取工具列表

客户端发送tools/list请求来发现可用工具:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {
    "cursor": "optional-cursor-value"
  }
}

响应:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "获取指定位置的当前天气信息",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "城市名称或邮政编码"
            }
          },
          "required": ["location"]
        }
      }
    ],
    "nextCursor": "next-page-cursor"
  }
}

调用工具

客户端发送tools/call请求来调用工具:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "location": "深圳"
    }
  }
}

响应:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "深圳当前天气:\n温度:18°C\n天气情况:晴转多云"
      }
    ],
    "isError": false
  }
}

工具列表变更通知

若服务器声明了listChanged能力,则当可用工具列表更改时,服务器应发送通知:

{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed"
}

完整流程:

LLM Client Server 发现工具 tools/list 工具列表 选择工具 选择使用的工具 调用工具 tools/call 工具调用结果 处理结果 工具列表更新 tools/list_changed tools/list 更新后的工具列表 LLM Client Server

客户端功能

客户端可以实现额外功能来丰富连接的MCP服务器。

Roots

MCP为客户端提供了一种向服务器公开文件系统"根"的标准化方法,定义服务器可以访问客户端哪些目录和文件。

Sampling

MCP为服务器提供的一种标准化方法,可以让服务器通过客户端从LLM中请求采样(完成或生成)。使得服务器也能够利用AI功能。

能力协商

MCP采用基于能力的协商系统,客户端和服务器在初始化时明确声明其支持的功能。

  • 服务器声明其支持的能力,如资源订阅、工具支持和提示模板等。
  • 客户端声明其支持的能力,如采样支持和通知处理。
  • 双方在整个会话过程中必须遵守已声明的能力。
  • 可以通过协议扩展来协商额外的功能。
Host Client Server 初始化客户端 以能力信息初始化会话 响应支持的能力 具有协商功能的活动会话 用户或模型发起的操作 请求(工具/资源) 响应 更新 UI 或响应模型 loop [客户端请求] 请求(采样) 转发至 AI AI 响应 响应 loop [服务器请求] 资源更新 状态变更 loop [通知] 终止会话 结束会话 Host Client Server

每种能力都解锁特定的协议功能,例如:

  • 服务器功能需要在服务器能力声明中公开
  • 发送资源订阅通知要求服务器声明支持订阅
  • 工具调用要求服务器声明工具能力
  • 采样需要客户端在能力声明中明确支持

这种能力协商机制确保客户端和服务器能明确理解各自的支持功能,同时保持协议的可扩展性。

生命周期

MCP为客户端与服务器之间的连接定义了一个严谨的生命周期:

  1. 初始化:能力协商与版本协议确认
  2. 运行:正常的协议通信
  3. 关闭:连接的优雅关闭
Client Server 初始化阶段 发送初始化请求 返回初始化响应 发送已初始化通知 运行阶段 正常的协议交互 关闭阶段 断开连接 连接关闭 Client Server

初始化

初始化阶段是客户端与服务器的第一步交互。客户端与服务器需要:

  • 确定协议版本的兼容性
  • 交换并协商能力
  • 共享实现细节

客户端必须通过发送initialize请求启动该阶段,其中包含: 支持的协议版本、客户端能力、客户端实现信息。

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {}
    },
    "clientInfo": {
      "name": "ExampleClient",
      "version": "1.0.0"
    }
  }
}

服务器必须响应其自身的能力和信息:’

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "logging": {},
      "prompts": {
        "listChanged": true
      },
      "resources": {
        "subscribe": true,
        "listChanged": true
      },
      "tools": {
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "ExampleServer",
      "version": "1.0.0"
    }
  }
}

成功初始化后,客户端必须发送initialized通知,表明其已准备好进行正常操作:

{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

版本协商

initialize请求中,客户端必须发送其支持的协议版本,通常选择支持的最新版本。

如果服务器支持请求的协议版本,则必须以相同版本响应;否则服务器必须以其支持的其他协议版本响应。

如果客户端不支持服务器响应的协议版本,则应断开连接。

能力协商

客户端和服务器的能力决定了会话期间可用的可选协议功能:

分类能力描述
Clientroots提供文件系统root的能力,即定义服务器可以拥有哪些目录和文件的访问权限。
Clientsampling支持LLM采样请求,使服务器能通过客户端请求LLM采样(补全或生成),让服务器能利用客户端的AI功能。
Clientexperimental支持非标准实验性功能
Serverprompts提供提示词模板
Serverresources提供可读的资源
Servertools暴露可调用的工具
Serverlogging发送结构化的日志消息
Serverexperimental支持非标准实验性功能

运行

在运行阶段,客户端和服务器根据协商的能力进行消息交换。

双方应该:

  • 遵守协商的协议版本
  • 仅使用已协商成功的能力

关闭

在关闭阶段,一方会优雅地终止协议连接,通过底层传输机制指示连接终止。

常见问题

MCP和Function Calling的区别是什么

模型上下文协议和函数调用都是扩展LLM功能的方法,尽管它们的目标类似,但在架构、范围和实现方式上存在显著差异。

函数调用

  • 目的: 允许LLM根据用户提示调用预定义的函数或API,从而获取数据或执行特定操作。
  • 机制: 开发者定义一组特定参数的函数,供LLM使用。当处理提示词时,LLM判断是否需要调用函数,并生成必要的参数以执行该函数。
  • 局限:
    • 供应商特定实现: 不同的LLM提供商可能有不同的函数调用实现方式,导致不一致和集成挑战。
    • 紧密耦合: 函数通常和LLM应用程序紧密集成,降低了适应或扩展的灵活性。

模型上下文协议

  • 目的: 旨在标准化AI应用与外部工具、数据源和系统之间的交互,为此类集成提供统一的协议。
  • 机制: MCP采用客户端-主机-服务器架构:
    • 服务器:通过标准化API公开工具、资源和提示。
    • 客户端:驻留在AI应用程序中,管理与MCP服务器的连接,促进LLM与外部系统之间的通信。
    • 主机:用户交互的应用程序,包含MCP客户端以连接各种服务器。
  • 优势:
    • 组件解耦: 通过将工具与LLM代理分离,MCP允许更模块化和灵活的集成。
    • 标准化: 提供连接AI模型到各种数据源和工具的一致一致,减少定制的需要。
    • 可扩展性:便于集成多个工具,而不会导致进程复杂性成倍增长。
    • 安全:调用工具的API_KEY可以放到服务端,避免客户端泄露的风险。

参考

  1. https://modelcontextprotocol.io/
  2. https://spec.modelcontextprotocol.io/specification/2025-03-26/
  3. https://github.com/sidharthrajaram/mcp-sse
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愤怒的可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值