MCP 入门(二):从握手到执行,一文拆解架构设计

核心概念

三个角色,一套架构

MCP 采用客户端-服务器(client-server)架构。AI 应用作为主机(Host),负责建立与一个或多个 MCP 服务器的连接。具体怎么连?主机会为每个 MCP 服务器创建一个专属的客户端(Client),每个客户端与其对应的服务器之间维持一条独立的连接。

一句话概括:Host 管全局,Client 管连接,Server 管能力。

根据运行位置不同,MCP 服务器分为两种:

  • 本地服务器:使用 STDIO 传输,服务单个客户端
  • 远程服务器:使用 Streamable HTTP 传输,可同时服务多个客户端

MCP 架构的三个核心角色:

  • MCP 主机(Host):负责协调和管理一个或多个 MCP 客户端的 AI 应用
  • MCP 客户端(Client):维持与 MCP 服务器连接的组件,从服务器获取上下文信息,供主机使用
  • MCP 服务器(Server):为 MCP 客户端提供上下文的程序

举个例子:VSCode 就是一个 MCP 主机。当 VSCode 需要连接某个 MCP 服务器时,它会实例化一个 MCP 客户端对象来维持这条连接。如果再连另一个 MCP 服务器,VSCode 就会再创建一个新的客户端对象。一个主机,多个客户端,各管各的连接,互不干扰。

两层架构:数据层 + 传输层

MCP 在设计上分为两层:

  • 数据层(Data Layer):定义基于 JSON-RPC 的客户端-服务器通信协议,包含生命周期管理和核心元素(工具、资源、提示词、通知等)
  • 传输层(Transport Layer):定义客户端与服务器之间交换数据的通信机制和通道,包含连接建立、消息帧和授权机制

可以这样理解:数据层管"说什么",传输层管"怎么送达"。

数据层

数据层基于 JSON-RPC 2.0 实现消息交换协议,定义了消息的结构和语义。它包括:

  • 生命周期管理:处理连接初始化、能力协商和连接终止
  • 服务器功能:让服务器提供核心能力——供 AI 调用的工具、提供上下文数据的资源、用于构建交互的模板提示词
  • 客户端功能:让服务器能够请求客户端从主机的 LLM 中采样、向用户征询输入、将消息记录到客户端
  • 实用功能:支持实时通知和长时间运行操作的进度跟踪等辅助能力

传输层

传输层负责管理客户端与服务器之间的通信通道身份验证,处理连接建立消息帧结构以及安全通信

两种传输机制:

  • 标准传输(STDIO):通过标准输入输出流进行本地通信,零网络开销,性能最高
  • Streamable HTTP 传输:用于远程通信,兼容 HTTP 身份验证方法(bearer 令牌、API 密钥、自定义请求头等)。MCP 建议使用 OAuth 获取身份令牌

传输层的一个精妙之处在于:它将通信细节完全抽象出来。不管底层用的是 STDIO 还是 HTTP,上层的 JSON-RPC 2.0 消息格式完全一致。

数据层协议:MCP 最有趣的部分

MCP 的核心在于定义客户端和服务器之间的数据模式语义。其中,一组叫做"原语(Primitives)"的基础构建块是整个协议最有意思的部分——它们就是将上下文从服务器传送到客户端的具体方式。

MCP 使用 JSON-RPC 2.0 作为底层 RPC 协议。客户端和服务器互相发送请求并作出响应;如果不需要回复,也可以使用通知机制。

生命周期管理

MCP 是有状态协议,需要生命周期管理。它的目的很明确:在连接建立时,协商客户端和服务器双方各自支持什么功能。

原语(Primitives):MCP 的核心积木

**原语是 MCP 最重要的概念。**它们定义了客户端和服务器之间能相互提供的核心功能,明确了哪些上下文信息可以共享给 LLM,以及可以执行哪些操作。

服务器可以暴露三个核心原语:

  • 工具(Tools):AI 应用可调用的可执行函数(文件操作、API 调用、数据库查询等)
  • 资源(Resources):为 AI 应用提供上下文信息的数据源(文件内容、数据库记录、API 响应等)
  • 提示词(Prompts):可重用的模板,用于构建与 LLM 的交互(系统提示词、少样本示例等)

每个原语都关联了发现(*/list)、检索(*/get)以及部分场景下的执行(tools/call)方法。

工作流程是这样的:客户端先通过 */list 发现可用原语,再列出所有可用工具,最后按需执行调用。这个设计让工具列表可以动态更新,而不是写死的。

举个实际的例子:一个提供数据库上下文的 MCP 服务器,可以同时提供查询数据库的工具、包含数据库模式(Schema)的资源,以及包含使用示例的提示词。三种原语各司其职,配合使用。

除了服务器端和客户端的原语,MCP 还提供了横切性的实用原语,用来增强请求的执行方式:

  • 任务(Tasks,实验性):持久化执行封装器,为 MCP 请求提供延迟结果获取与状态追踪功能(适用于高开销计算、工作流自动化、批量处理、多步骤操作等场景)

通知:让连接"活"起来

MCP 支持实时通知,让服务器能主动告知客户端发生了变化。比如,当服务器的可用工具发生变动时,它可以通过通知将变更推送给客户端。

通知以 JSON-RPC 2.0 通知消息的形式发送,不需要等待响应。这意味着 MCP 连接不是"一问一答"的死板模式,而是具备实时推送能力的活连接。

例子:数据层实战

1. 初始化(生命周期管理)

MCP 的起点是通过能力协商握手来完成生命周期管理。客户端发送一个 initialize 请求,建立连接并协商双方支持的功能特性。

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "elicitation": {}
    },
    "clientInfo": {
      "name": "example-client",
      "version": "1.0.0"
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": {
        "listChanged": true
      },
      "resources": {}
    },
    "serverInfo": {
      "name": "example-server",
      "version": "1.0.0"
    }
  }
}

1.1. 握手到底在做什么?

初始化是 MCP 生命周期管理的关键环节,它承担三个核心职能:

  1. 协议版本协商protocolVersion 字段确保客户端和服务器使用兼容的协议版本。版本不匹配?直接终止连接,绝不含糊。
  2. 能力发现capabilities 对象让双方亮出各自的"底牌"——支持哪些原语(工具、资源、提示词),是否支持通知功能。提前摸清对方的能力边界,避免后续出现无效调用。
  3. 身份交换clientInfoserverInfo 对象提供身份和版本信息,方便调试和兼容性判断。

具体来看这个例子中的能力协商:

客户端能力
  • "elicitation": {}:客户端声明自己支持用户交互请求(可接收 elicitation/create 方法调用)
服务器能力
  • "tools": {"listChanged": true}:服务器支持工具原语,并且当工具列表发生变动时,会主动发送 tools/list_changed 通知
  • "resources": {}:服务器也支持资源原语(可以处理 resources/listresources/read 方法)

初始化成功后,客户端发送一个通知,表明已准备就绪:

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

1.2. AI 应用中是怎么跑起来的?

初始化阶段,AI 应用的 MCP 客户端管理器会与已配置的服务器逐一建立连接,并把各服务器的能力信息存下来,方便后续使用。应用据此判断哪些服务器能提供工具、哪些能提供资源,以及谁支持实时更新。

# 伪代码
# 启动服务器进程并建立通信:使用 stdio(标准输入输出)传输方式。
# 根据 server_config 运行服务器,返回读写两个流对象。
# async with 确保任务结束时自动关闭进程。
async with stdio_client(server_config) as (read, write):
    # 在传输层之上构建协议层:ClientSession 将原始读写流封装成
    # 符合 MCP 协议规范的会话,让我们能调用 initialize() 这样的
    # 高层级方法,而不用手动拼 JSON 字符串。
    async with ClientSession(read, write) as session:
        # 执行握手初始化:客户端向服务器"打招呼"——
        # "你好,你能做什么?"
        # 服务器返回版本号、身份信息以及它支持的功能列表。
        init_response = await session.initialize()
        # 按需注册能力:检查服务器是否支持工具。如果支持,
        # 就在应用中注册它为"具备工具能力"的服务器。
        # 之后 AI 就可以通过这个会话调用服务器提供的函数了。
        if init_response.capabilities.tools:
            app.register_mcp_server(session, supports_tools=True)
        # 标记就绪:一切就绪,通知应用该服务器可以正式接活了。
        app.set_server_ready(session)

2. 工具发现(原语)

连接建立之后,客户端可以通过发送 tools/list 请求来发现可用工具。这是 MCP 工具发现机制的核心——先摸清家底,再干活。

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "calculator_arithmetic",
        "title": "Calculator",
        "description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
        "inputSchema": {
          "type": "object",
          "properties": {
            "expression": {
              "type": "string",
              "description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
            }
          },
          "required": ["expression"]
        }
      },
      {
        "name": "weather_current",
        "title": "Weather Information",
        "description": "Get current weather information for any location worldwide",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "City name, address, or coordinates (latitude,longitude)"
            },
            "units": {
              "type": "string",
              "enum": ["metric", "imperial", "kelvin"],
              "description": "Temperature units to use in response",
              "default": "metric"
            }
          },
          "required": ["location"]
        }
      }
    ]
  }
}

2.1. 工具发现请求

tools/list 请求非常简洁,不需要任何参数。

2.2. 工具发现响应

响应中的 tools 数组提供了每个可用工具的完整元数据。这种基于数组的结构让服务器可以同时暴露多个工具,同时在不同功能之间保持清晰的边界。

每个工具对象包含几个关键字段:

  • name:工具在服务器命名空间中的唯一标识符。它是工具执行的"主键",命名应该清晰明确(比如用 calculator_arithmetic 而不是简单的 calculate
  • title:人类可读的显示名称,客户端可以用它向用户展示
  • description:工具用途的详细说明,包括它能做什么、适合什么场景
  • inputSchema:JSON Schema 格式的输入参数定义,提供类型验证,并清晰标注哪些参数必填、哪些可选

2.3. AI 应用中是怎么跑起来的?

AI 应用会从所有已连接的 MCP 服务器获取可用工具,汇总成一个统一的工具注册表供 LLM 使用。这样 LLM 就知道自己能调用哪些操作,并在对话中自动生成相应的工具调用指令。

# 伪代码:使用 MCP Python SDK 模式
available_tools = []
# 遍历所有已建立连接的会话
for session in app.mcp_server_sessions():
    # 获取工具列表(Discovery):返回该服务器的工具列表
    tools_response = await session.list_tools()
    # 工具聚合:将不同服务器的工具汇总到统一数组中
    available_tools.extend(tools_response.tools)
# 注册到对话上下文:将汇总后的工具列表交给对话管理系统
conversation.register_available_tools(available_tools)

3. 工具执行(原语)

客户端可以通过 tools/call 方法执行工具。这就是 MCP 原语在实际中的运作方式:先发现,再调用。

3.1. 工具执行请求

tools/call 请求采用结构化格式,确保客户端和服务器之间的类型安全和通信清晰。注意,这里使用的是工具发现响应中的完整名称 weather_current,而不是简化命名:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "weather_current",
    "arguments": {
      "location": "San Francisco",
      "units": "imperial"
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
      }
    ]
  }
}

3.2. 请求结构解析

请求中有几个关键部分:

  • name:必须与发现响应中的工具名精确匹配(weather_current),确保服务器能准确定位到要执行的工具
  • arguments:包含工具 inputSchema 中定义的输入参数:
    • location:“San Francisco”,必填参数
    • units:“imperial”,可选参数(默认 metric)
  • JSON-RPC 结构:使用标准 JSON-RPC 2.0 格式,通过唯一的 id 实现请求与响应的精确关联

3.3. 响应结构解析

响应体现了 MCP 灵活的内容系统:

  • content 数组:工具响应返回内容对象数组,支持多种响应格式(文本、图片、资源等)
  • 内容类型:每个内容对象都有一个 type 字段。"type": "text" 表示纯文本,但 MCP 还支持多种内容类型以适配不同场景
  • 结构化输出:响应提供可操作的信息,作为 AI 应用与 LLM 交互的上下文

这种执行模式让 AI 应用可以动态调用服务器功能,并接收结构化的响应结果,无缝整合到对话流中。

3.4. AI 应用中是怎么跑起来的?

当 LLM 在对话中决定使用某个工具时,AI 应用会拦截这个工具调用请求,将它路由到对应的 MCP 服务器执行,然后把结果作为对话流的一部分返回给 LLM。这样,LLM 就能访问实时数据并与外部世界交互了。

# 伪代码:AI 应用工具执行
# 触发执行:LLM 分析用户请求后,如果需要外部数据,
# 会生成一个结构化输出,指明要用的 tool_name 和 arguments。
async def handle_tool_call(conversation, tool_name, arguments):
    # 路由分发:查找"路由表"(工具聚合阶段建立的),
    # 找到拥有这个工具的 session
    session = app.find_mcp_session_for_tool(tool_name)
    # 远程调用:
    # 1. 客户端将工具名和参数打包成 MCP 协议格式
    # 2. 发送给对应的 MCP Server
    # 3. Server 执行实际操作(访问 API、读文件等)
    # 4. 等待 Server 返回执行结果
    result = await session.call_tool(tool_name, arguments)
    # 闭环反馈:将工具返回的数据追加到对话历史中,
    # 让 LLM 能基于这些信息继续推理
    conversation.add_tool_result(result.content)

4. 实时更新(通知)

MCP 通过实时通知机制,让服务器能主动告知客户端工具发生了变化,而无需客户端反复轮询。这是保持 MCP 连接同步和快速响应的关键特性。

4.1. 工具列表变动通知

当服务器的可用工具发生变动——新工具上线、现有工具被修改、或工具暂时不可用——服务器会主动推送通知:

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

4.2. MCP 通知的核心特点

  • 无需响应:通知中没有 id 字段,遵循 JSON-RPC 2.0 的通知语义——发了就完事,不用等回复
  • 基于能力声明:只有在初始化阶段声明了 "listChanged": true 的服务器才会发送这类通知(见第 1 步)
  • 事件驱动:服务器根据内部状态变化决定何时推送,让 MCP 连接具备动态性和实时性

4.3. 客户端如何响应?

收到通知后,客户端通常会立即请求更新后的工具列表。这就形成了一个"通知 -> 刷新"的闭环,确保客户端掌握的工具信息始终是最新的:

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/list"
}

4.4. 为什么通知机制如此重要?

  • 动态环境:工具可能随服务状态、外部依赖或用户权限的变化而增减
  • 高效通信:客户端无需轮询,有变化时自然会被通知
  • 状态一致:确保客户端对服务器可用工具的认知始终准确
  • 实时协作:让 AI 应用能够即时适应变化的上下文环境

通知模式不仅限于工具,它延伸到了所有 MCP 原语,实现客户端和服务器之间全方位的实时同步。

4.5. AI 应用中是怎么跑起来的?

当 AI 应用收到工具变更通知时,它会立刻刷新工具注册表并更新 LLM 的可用能力。如果当前有对话正在进行,还会实时注入一条系统消息告诉 LLM:“你的工具箱刚更新了。”

# 伪代码:AI 应用中的通知处理
# 监听回调:当收到 notifications/tools/list_changed 消息时,
# 自动触发这个异步函数
async def handle_tools_changed_notification(session):
    # 主动拉取:不猜测变化内容,直接请求最新的完整工具列表
    tools_response = await session.list_tools()
    # 更新注册表:将新工具列表同步到应用的内部存储中
    app.update_available_tools(session, tools_response.tools)
    # 实时上下文注入:如果对话正在进行,
    # 向 LLM 的上下文窗口插入一条系统消息,
    # 告知可用工具已更新
    if app.conversation.is_active():
        app.conversation.notify_llm_of_new_capabilities()
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐