---
title: "MCP 生命周期：从 initialize 到 initialized 再到正常通信"
wiki: mcp
category: "协议规范"
slug: mcp-lifecycle
url: https://learnagent.wiki/mcp/cards/mcp-lifecycle
tags: ["MCP", "生命周期", "initialize", "capabilities", "roots"]
last_updated: 2026-04-11
reading_time: 8
---

> 很多人第一次看 MCP，会把它想成“客户端和服务端一连上就开始互调方法”。但官方规范在 Lifecycle 章节里定义得非常严格：**MCP 连接不是随便开聊，而是先走一段有明确顺序的生命周期。**

# MCP 生命周期：从 initialize 到 initialized 再到正常通信

## 基础概念

很多人第一次看 MCP，会把它想成“客户端和服务端一连上就开始互调方法”。但官方规范在 Lifecycle 章节里定义得非常严格：**MCP 连接不是随便开聊，而是先走一段有明确顺序的生命周期。**

官方规范把整个过程拆成三段：

1. **Initialization**
2. **Operation**
3. **Shutdown**

这里最关键的不是名字，而是一个硬约束：**初始化阶段必须先完成，后面的正常通信才成立。**

### 核心要素

| 要素 | 作用 |
|------|------|
| **`initialize` 请求** | 客户端先发起版本与能力协商 |
| **能力交换** | 双方声明自己支持哪些可选能力，比如 tools / resources / prompts / roots |
| **`notifications/initialized`** | 初始化成功后，客户端必须再发一个已初始化通知，表示可以进入正常通信 |
| **正常操作阶段** | 这时才开始 `tools/list`、`resources/read`、`prompts/get` 等日常交互 |
| **关闭阶段** | 会话结束，连接终止，回到未初始化状态 |

### 一次标准握手长什么样

```mermaid
sequenceDiagram
    participant Client
    participant Server

    Client->>Server: initialize
    Server-->>Client: initialize result
    Client->>Server: notifications/initialized
    Note over Client,Server: 进入正常通信阶段
    Client->>Server: tools/list / resources/list / prompts/list ...
```

### 为什么要多一个 `initialized`

很多协议只做一次握手，MCP 为什么还要补一个通知？原因很实在：**光拿到 initialize 的响应，不代表客户端已经准备好进入正常工作流。**

规范要求 `initialized` 明确告诉 Server：

- 我已经接受版本协商结果
- 我也知道双方支持哪些能力了
- 现在可以开始普通交互

这相当于把“握手完成”和“开始正式工作”分成了两个明确时刻。

## 基础用法

### 第一步：客户端必须先发 `initialize`

规范最新页明确说：

- Initialization phase **MUST** 是 client/server 的第一段交互
- 由客户端发 `initialize`
- 里面要带：
  - `protocolVersion`
  - `capabilities`
  - `clientInfo`

最小结构大致像这样：

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "roots": {
        "listChanged": true
      }
    },
    "clientInfo": {
      "name": "ExampleClient",
      "version": "1.0.0"
    }
  }
}
```

### 第二步：服务端返回自己的能力和信息

服务端响应里至少会返回：

- 协议版本
- 服务端 capabilities
- `serverInfo`

规范页里的典型结构是：

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

这一步的核心不是“告诉你服务端叫什么”，而是：**双方在这里正式确认这一条连接上到底有哪些能力可用。**

### 第三步：客户端必须发送 `notifications/initialized`

规范在 latest lifecycle 页里写得非常硬：

> After successful initialization, the client MUST send an `initialized` notification to indicate it is ready to begin normal operations.

也就是：

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

只有这条通知之后，才算真正进入正常通信。

### 第四步：再进入正常操作阶段

这时候才轮到：

- `tools/list`
- `tools/call`
- `resources/list`
- `resources/read`
- `prompts/list`
- `prompts/get`
- `roots/list`

这些日常交互。

### `listChanged` 到底有什么用

MCP 生命周期里一个很实用的设计，是很多能力都可以在 initialize 阶段顺带声明：

- 我支不支持 `listChanged`
- 我会不会发列表变更通知

比如在 Roots 里，官方文档写得很明确：

- 客户端支持 roots 时，必须在 `capabilities.roots` 声明
- `listChanged` 表示后续 roots 变化时，客户端会不会发 `notifications/roots/list_changed`

这说明生命周期不只是“开场寒暄”，而是在一开始就把后续会话行为约定清楚。

### 规范里还有一个很容易漏的点

Lifecycle 页面还明确要求：

> The `initialize` request MUST NOT be part of a JSON-RPC batch.

也就是初始化请求不能混进一批别的请求里。原因也很直观：**初始化完成前，其他请求本来就还不该被处理。**

## 同类工具对比

如果把 MCP 生命周期和普通“直接调接口”习惯放一起看，差异会很明显：

| 维度 | MCP 生命周期 | 直接 REST 调接口式心智 |
|------|--------------|------------------------|
| 第一步 | 先能力与版本协商 | 通常直接请求业务接口 |
| 会话状态 | 明确有初始化前 / 初始化后之分 | 很多 REST 场景没有显式会话状态 |
| 能力边界 | 双方一开始就声明支持哪些能力 | 常常靠文档或约定记忆 |
| 动态变化通知 | 可通过 `listChanged` 等能力约定 | 通常需要额外机制 |
| 适合复杂协议吗 | 很适合 | 不一定 |

核心区别：

- **MCP**：更像“先谈清楚这条连接能做什么，再开始办事”。
- **普通接口思维**：更像“你知道接口就直接调”。

## 常见误区

| 误区 | 准确理解 |
|------|----------|
| 以为连上 transport 后就能直接 `tools/call` | 不行。必须先走 `initialize` 和 `notifications/initialized` |
| 以为 `initialize` 响应回来后就自动算完成握手 | 还不完整。客户端还必须发 `notifications/initialized` |
| 以为 capabilities 只是展示信息 | 不只是。它决定后续会话里哪些功能和通知模式是合法的 |
| 以为 `initialize` 可以和别的请求一起 batch 发 | 规范明确说不行 |
| 以为 `listChanged` 是可有可无的小细节 | 对动态工具列表、资源列表、roots 变化这类场景，它很关键 |
| 以为 roots 是正常操作阶段才“顺便看看” | 实际上 roots 能力本身就在 initialize 阶段声明 |

## 优劣势分析

| 优势 | 劣势 |
|------|------|
| **边界清楚**：初始化前后状态明确，不容易“半连不连” | **实现更严格**：不能图省事跳过步骤 |
| **能力协商完整**：一开始就知道双方支持什么 | **初学者更容易被握手流程绕晕**：比“直接调接口”多一层协议心智 |
| **为动态会话打基础**：`listChanged`、roots、sampling 等能力都能先约定 | **调试时需要看更多消息**：出错时不能只看业务请求，要看 initialize 阶段 |
| **版本兼容性更稳**：先谈协议版本，再决定如何通信 | **客户端实现差异会暴露**：有的客户端对某些 capability 支持不完整，问题会在初始化阶段体现出来 |
| **对复杂客户端 / 服务器关系更友好**：适合多能力、多通知的长连接会话 | **心智不如“发请求拿响应”直觉**：需要开发者真正理解会话状态机 |

## 思考题

<details>
<summary>初级：为什么规范要求 `initialize` 必须是第一段交互，而不是允许先试着调个 `tools/list` 看看？</summary>

**参考答案：**

因为在初始化之前，服务端和客户端连“彼此支持什么版本、有哪些可选能力”都还没谈清楚。

如果这时就直接发 `tools/list`，等于默认假设：

- 协议版本已经兼容
- 服务端支持 tools
- 当前会话已经进入正常操作阶段

这些假设在协议层都还没有被正式确认。Lifecycle 的意义，就是先把这些前提建立起来。

</details>

<details>
<summary>中级：为什么 `notifications/initialized` 不是多余的一条消息？</summary>

**参考答案：**

因为 `initialize` 响应只说明“服务端已经给出能力和版本结果”，不代表客户端已经准备好按这个结果进入正常通信。

`notifications/initialized` 的作用，就是让客户端再明确说一次：

- 我收到并接受这次协商了
- 我现在正式切到 operational 状态

这能避免很多“握手还没完全完成，就开始收业务请求”的模糊状态。

</details>

<details>
<summary>进阶：为什么 `listChanged` 这类 capability 会在生命周期阶段声明，而不是等后面真变了再说？</summary>

**参考答案：**

因为通知行为本身就是会话契约的一部分。

如果一开始没说清楚“我后面可能会发列表变更通知”，那另一方就不知道该不该监听、该不该维护本地缓存、该不该在 UI 上做动态刷新。

所以这些能力必须在 initialize 阶段声明。它们不是“某个具体功能细节”，而是整个会话行为模式的一部分。

</details>

## 参考资料

1. MCP 规范最新版 Lifecycle：<https://modelcontextprotocol.io/specification/latest/basic/lifecycle>（查询日期 2026-04-11）
2. MCP Roots 文档：<https://modelcontextprotocol.io/docs/concepts/roots>（查询日期 2026-04-11）
3. MCP Roots 规范页：<https://modelcontextprotocol.io/specification/draft/client/roots>（查询日期 2026-04-11）

---
*Source: https://learnagent.wiki/mcp/cards/mcp-lifecycle*
*Markdown mirror of https://learnagent.wiki, served as text/markdown for LLM ingestion.*