Initialization Flow
Implement the MCP handshake from the client side.
The Initialization Sequence
Before you can use any MCP functionality, the client must complete a handshake with the server. This establishes protocol version and capabilities.
Client Server
│ │
│──── initialize request ────────────────►│
│ (client capabilities) │
│ │
│◄─── initialize response ────────────────│
│ (server capabilities) │
│ │
│──── notifications/initialized ─────────►│
│ (handshake complete) │
│ │
│ Ready for operation! │
│ │
Step 1: Send Initialize Request
The client starts by sending an initialize request with
its protocol version and capabilities:
def initialize(self) -> dict:
"""
Perform the MCP initialization handshake.
Returns:
Server's initialization response with capabilities.
"""
# Send initialize request
response = self.send_request("initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {
# Client capabilities - what we support
"roots": {
"listChanged": True # We can handle root change notifications
},
"sampling": {} # We support sampling (LLM requests from server)
},
"clientInfo": {
"name": "my-mcp-client",
"version": "1.0.0"
}
})
return response
Understanding the Initialize Response
The server responds with its own capabilities:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
}
},
"serverInfo": {
"name": "my-awesome-server",
"version": "1.0.0"
}
}
}
The capabilities tell you what features are available. If tools
is present, the server has tools. If resources.subscribe is true,
you can subscribe to resource updates.
Step 2: Parse and Store Capabilities
After receiving the response, parse and store the server's capabilities for later use:
def initialize(self) -> dict:
"""Perform the MCP initialization handshake."""
response = self.send_request("initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {"listChanged": True},
"sampling": {}
},
"clientInfo": {
"name": "my-mcp-client",
"version": "1.0.0"
}
})
# Check for errors
if "error" in response:
raise RuntimeError(f"Initialize failed: {response['error']}")
result = response["result"]
# Store server info
self.server_info = result.get("serverInfo", {})
self.server_capabilities = result.get("capabilities", {})
self.protocol_version = result.get("protocolVersion")
print(f"Connected to: {self.server_info.get('name', 'Unknown')}")
print(f"Protocol version: {self.protocol_version}")
return response
Step 3: Send Initialized Notification
After processing the response, the client must send an
notifications/initialized message to complete the handshake:
def initialize(self) -> dict:
"""Perform the MCP initialization handshake."""
# Step 1: Send initialize request
response = self.send_request("initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {"listChanged": True},
"sampling": {}
},
"clientInfo": {
"name": "my-mcp-client",
"version": "1.0.0"
}
})
if "error" in response:
raise RuntimeError(f"Initialize failed: {response['error']}")
# Step 2: Store server capabilities
result = response["result"]
self.server_info = result.get("serverInfo", {})
self.server_capabilities = result.get("capabilities", {})
self.protocol_version = result.get("protocolVersion")
# Step 3: Send initialized notification
self.send_notification("notifications/initialized")
# Mark as ready
self.initialized = True
return response
The notifications/initialized message is required by the
MCP specification. Some servers may not function correctly if you skip it.
Checking Capabilities
Add helper methods to check what the server supports.
Edit: stdio_client.py
Add these methods to the StdioClient class:
# Add these instance variables in __init__:
# self.server_info = {}
# self.server_capabilities = {}
# self.protocol_version = None
# self.initialized = False
def initialize(self) -> dict:
"""Perform the MCP initialization handshake."""
# Step 1: Send initialize request
response = self.send_request("initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {"listChanged": True},
"sampling": {}
},
"clientInfo": {
"name": "my-mcp-client",
"version": "1.0.0"
}
})
if "error" in response:
raise RuntimeError(f"Initialize failed: {response['error']}")
# Step 2: Store server capabilities
result = response["result"]
self.server_info = result.get("serverInfo", {})
self.server_capabilities = result.get("capabilities", {})
self.protocol_version = result.get("protocolVersion")
# Step 3: Send initialized notification
self.send_notification("notifications/initialized")
# Mark as ready
self.initialized = True
return response
def has_tools(self) -> bool:
"""Check if the server provides tools."""
return "tools" in self.server_capabilities
def has_resources(self) -> bool:
"""Check if the server provides resources."""
return "resources" in self.server_capabilities
def has_prompts(self) -> bool:
"""Check if the server provides prompts."""
return "prompts" in self.server_capabilities
def supports_resource_subscriptions(self) -> bool:
"""Check if the server supports resource subscriptions."""
resources = self.server_capabilities.get("resources", {})
return resources.get("subscribe", False)
Complete Initialization Example
Here's how initialization looks in practice:
with StdioClient(["python", "my_server.py"]) as client:
# Perform initialization
client.initialize()
# Check capabilities
print(f"Has tools: {client.has_tools()}")
print(f"Has resources: {client.has_resources()}")
print(f"Has prompts: {client.has_prompts()}")
# Now we can call other methods
if client.has_tools():
tools_response = client.send_request("tools/list")
print(f"Available tools: {tools_response}")
Always check capabilities before calling methods. If you call
tools/list on a server that doesn't support tools,
you'll get an error.