第九课
MCP 从零实现教程(九)
第九课:MCP 生命周期(Lifecycle)——Client 第一次连接到底发生了什么?
目标:理解一个 MCP Server 从启动到正式工作的完整过程。
📚 本课目标
学完本课,你将能够回答:
- 为什么不能一连接就调用 Tool?
initialize到底是干什么的?- 什么是 Capability(能力协商)?
- 为什么官方要设计一个初始化阶段?
这一课没有太多复杂代码,但它是理解 MCP 协议 的关键。
📖 第一部分:回顾我们的 Mini MCP
前几课,我们一直这样:
Client
│
▼
tools/list
│
▼
Server
或者:
Client
│
▼
tools/call
│
▼
Server
是不是感觉很自然?
但是这里其实少了一步。
📖 第二部分:现实世界不能这样工作
假设:
Cursor 第一次连接你的 MCP Server。
它什么都不知道。
不知道:
- 你支持哪个 MCP 版本?
- 你有没有 Tool?
- 有没有 Resource?
- 有没有 Prompt?
- 有没有 Logging?
所以:
第一件事情不是:
tools/list
而是:
你好,我们先认识一下。
这就是:
initialize
官方规定:initialize 必须是 Client 与 Server 的第一次交互,双方在这里完成协议版本和能力协商。
📖 第三部分:真正的生命周期
真正 MCP:
Client
│
▼
initialize
│
▼
initialize Response
│
▼
initialized
│
▼
tools/list
│
▼
tools/call
│
▼
resources/list
│
▼
......
注意:
这里有三个阶段。
很多教程都会漏掉:
initialized
📖 第四部分:第一步——Client 发送 initialize
第一次连接:
发送:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"clientInfo": {
"name": "MiniClient",
"version": "1.0"
},
"capabilities": {}
}
}
是不是像登录一样?
这里告诉 Server:
我是:
MiniClient
我支持:
2025-06-18
我支持这些能力:
...
📖 第五部分:Server 回复
Server:
返回:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"serverInfo": {
"name": "MiniServer",
"version": "1.0"
},
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
}
}
}
这里:
Server 告诉 Client:
我是:
MiniServer
我支持:
Tool
Resource
Prompt
📖 第六部分:什么叫 Capability(能力)?
很多人第一次看到:
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
}
觉得:
就是配置。
其实不是。
它真正表示:
Server 能做什么。
例如:
如果:
{
"tools": {}
}
说明:
支持 Tool
如果没有:
{
"resources": {}
}
说明:
不能读取 Resource
所以:
Capability:
其实就是:
Feature List(功能列表)
💻 第七部分:我们自己实现 initialize()
开始写。
def initialize(request):
return {
"jsonrpc": "2.0",
"id": request["id"],
"result": {
"protocolVersion": "1.0",
"serverInfo": {
"name": "MiniMCP",
"version": "0.1"
},
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
}
}
}
是不是非常简单?
💻 第八部分:Dispatcher 增加 initialize
以前:
Dispatcher:
if method == "tools/list":
今天:
增加:
def dispatch(request):
method = request["method"]
if method == "initialize":
return initialize(request)
elif method == "tools/list":
return list_tools()
elif method == "tools/call":
return call_tool(request)
这样:
Server:
已经支持:
初始化。
📖 第九部分:为什么还有 initialized?
很多教程都会讲到这里。
其实:
还差一步。
Client:
收到:
initialize Response
以后:
必须发送:
{
"jsonrpc":"2.0",
"method":"notifications/initialized"
}
注意:
没有:
id
因为:
它不是 Request。
它只是:
通知。
意思就是:
我已经准备好了。
开始正式工作。
官方规范要求:Server 在收到 notifications/initialized 之前,不应开始发送普通请求;Client 也应在初始化完成后再进入正常通信阶段。
💻 第十部分:增加 initialized
我们自己实现。
server_state = {
"initialized": False
}
增加:
def initialized():
server_state["initialized"] = True
print("客户端初始化完成")
Dispatcher:
增加:
elif method == "notifications/initialized":
initialized()
return None
是不是:
非常简单?
📖 第十一部分:整个生命周期终于完整了
现在:
真正流程:
Client
│
▼
initialize
│
▼
Server 返回能力
│
▼
notifications/initialized
│
▼
Server 标记:
initialized=True
│
▼
tools/list
│
▼
tools/call
│
▼
正常工作
看到这里,
是不是终于理解:
为什么:
官方把:
initialize
单独设计成一个协议?
因为:
这是:
整个连接建立的开始。
🧪 本课练习
请自己新增:
server_state = {
"initialized": False
}
要求:
第一次:
发送:
{
"method":"initialize"
}
输出:
Server 信息。
然后:
发送:
{
"method":"notifications/initialized"
}
输出:
Client Ready
最后:
发送:
{
"method":"tools/list"
}
能够正常工作。
你还可以进一步改造 Dispatcher:如果 server_state["initialized"] 为 False,除了 initialize 之外的请求全部拒绝,这样就更接近真实 MCP 的行为。
🎯 本课总结
今天,我们终于完成了 MCP 的完整生命周期。
一个真实的 MCP Server 启动以后,不是马上执行 Tool。
而是:
启动
↓
initialize(握手)
↓
能力协商(Capabilities)
↓
initialized(准备完成)
↓
正式开始工作
↓
tools/list
↓
tools/call
↓
......
↓
关闭连接
这也是为什么 MCP 看起来比普通 REST API 多了一层流程:它不仅要调用接口,还要先建立一个协议会话,确认双方都支持哪些能力、使用哪个协议版本,再进入正常通信。
🚀 第十课预告
从第十课开始,我们不再学习单个协议点,而是开始重构整个项目。
我们会把前九课零散的代码整理成一个真正的项目结构:
mini_mcp/
│
├── server.py
├── dispatcher.py
├── registry.py
├── tools/
│ ├── math.py
│ ├── weather.py
│ └── file.py
├── resources/
├── prompts/
└── client.py
从这一课开始,你写的将不再是”教程代码”,而是一个可以持续扩展、不断接近官方 MCP Server 的真实项目。

浙公网安备 33010602011771号