MCP Lifecycle Overview
Understand the three phases of an MCP connection: Initialization, Operation, and Shutdown.
What You'll Build
In this lesson, you'll build mcp_core.py — a working MCP server
that properly implements the initialization handshake. By the end, your server will:
- Track connection state (disconnected, initializing, ready)
- Handle the
initializerequest with version negotiation - Respond with server capabilities
- Process the
notifications/initializednotification - Reject requests before initialization is complete
Each step builds on the previous one, and you'll test your server with curl commands.
The Three Phases of MCP
Every MCP connection follows a strict lifecycle with three distinct phases. Understanding this lifecycle is essential before building a working MCP server.
┌─────────────────────────────────────────────────────────────────┐
│ MCP CONNECTION LIFECYCLE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ PHASE 1: INITIALIZATION │ │
│ │ ─────────────────────────────────────────────────────── │ │
│ │ Client ──► initialize request (capabilities, version) │ │
│ │ Server ◄── initialize response (capabilities, version) │ │
│ │ Client ──► notifications/initialized │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ PHASE 2: OPERATION │ │
│ │ ─────────────────────────────────────────────────────── │ │
│ │ Client ──► tools/list, tools/call │ │
│ │ Client ──► resources/list, resources/read │ │
│ │ Client ──► prompts/list, prompts/get │ │
│ │ ... (normal MCP operations) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ PHASE 3: SHUTDOWN │ │
│ │ ─────────────────────────────────────────────────────── │ │
│ │ Client closes connection or server terminates │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Phase 1: Initialization (The Handshake)
The initialization phase is the handshake between client and server. No other operations can happen until this completes successfully.
| Step | Direction | Message |
|---|---|---|
| 1 | Client → Server | initialize request |
| 2 | Server → Client | initialize response |
| 3 | Client → Server | notifications/initialized |
A server MUST reject any requests (except initialize) until the
handshake completes. Attempting to call tools/list before
initialization will result in an error.
Phase 2: Operation
Once initialized, the client and server can exchange messages freely. This is where the real work happens:
- Tool discovery — Client queries available tools
- Tool execution — Client invokes tools with arguments
- Resource access — Client reads available resources
- Prompt retrieval — Client fetches prompt templates
// Example: Client lists available tools
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
// Server responds with tool definitions
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a location",
"inputSchema": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
]
}
}
Phase 3: Shutdown
The shutdown phase occurs when either party terminates the connection:
- Client closes the connection gracefully
- Server process terminates
- Network connection drops
- Timeout occurs
For STDIO transport, shutdown typically means the server process exits. For HTTP/SSE transport, the client simply stops sending requests.
Why the Handshake Matters
The initialization handshake serves several critical purposes:
1. VERSION NEGOTIATION
Client: "I support protocol version 2024-11-05"
Server: "I also support 2024-11-05, let's use that"
2. CAPABILITY DISCOVERY
Client: "I can do sampling"
Server: "I provide tools and resources"
3. IDENTITY EXCHANGE
Client: "I am Claude Desktop v1.0.0"
Server: "I am weather-server v2.1.0"
The handshake lets both parties know exactly what to expect from each other. A client won't try to call tools on a server that doesn't support them, and a server knows what features the client can handle.
State Machine View
Your MCP server should track its connection state:
from enum import Enum
class ConnectionState(Enum):
"""MCP connection states."""
DISCONNECTED = "disconnected" # No connection
INITIALIZING = "initializing" # Received initialize, not yet confirmed
READY = "ready" # Handshake complete, operational
SHUTDOWN = "shutdown" # Closing down
class MCPServer:
def __init__(self):
self.state = ConnectionState.DISCONNECTED
def handle_initialize(self, params):
"""Handle initialize request."""
if self.state != ConnectionState.DISCONNECTED:
raise Exception("Already initialized")
self.state = ConnectionState.INITIALIZING
# ... process initialize
return initialize_response
def handle_initialized_notification(self):
"""Handle initialized notification."""
if self.state != ConnectionState.INITIALIZING:
raise Exception("Invalid state for initialized")
self.state = ConnectionState.READY
def handle_request(self, method, params):
"""Handle normal requests."""
if self.state != ConnectionState.READY:
raise Exception("Server not initialized")
# ... handle the request
What's Next?
In the following steps, we'll examine each part of the handshake in detail:
- Step 2: The initialize request from the client
- Step 3: The server's response with capabilities
- Step 4: Understanding capability negotiation
- Step 5: The initialized notification
- Step 6: Building the complete handshake in Python