The Initialize Request
Learn what the client sends to start the MCP handshake.
The First Message
The initialize request is always the first message in an MCP session.
The client sends this to tell the server who it is and what it can do.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
},
"clientInfo": {
"name": "claude-desktop",
"version": "1.0.0"
}
}
}
Breaking Down the Request
Let's examine each field in the params object:
protocolVersion
The MCP protocol version the client wants to use. This is a date-based version
string following the format YYYY-MM-DD.
"protocolVersion": "2024-11-05"
MCP uses date-based versioning. The server should respond with a version it supports. If there's no compatible version, the server must reject the connection.
capabilities
The capabilities object tells the server what features the client supports.
This enables the server to adjust its behavior accordingly.
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
}
Common client capabilities include:
| Capability | Description |
|---|---|
roots |
Client can provide filesystem roots to the server |
roots.listChanged |
Client supports notifications when roots change |
sampling |
Client supports LLM sampling requests from server |
experimental |
Client supports experimental/non-standard features |
clientInfo
The clientInfo object identifies the client application. This is useful
for debugging and logging.
"clientInfo": {
"name": "claude-desktop",
"version": "1.0.0"
}
Parsing the Initialize Request
Here's how to parse and validate the initialize request in Python:
from dataclasses import dataclass, field
from typing import Any
@dataclass
class ClientInfo:
"""Information about the MCP client."""
name: str
version: str
@dataclass
class ClientCapabilities:
"""Capabilities supported by the client."""
roots: dict = field(default_factory=dict)
sampling: dict = field(default_factory=dict)
experimental: dict = field(default_factory=dict)
@dataclass
class InitializeParams:
"""Parameters for the initialize request."""
protocolVersion: str
capabilities: ClientCapabilities
clientInfo: ClientInfo
def parse_initialize_params(params: dict) -> InitializeParams:
"""Parse and validate initialize request parameters."""
# Extract required fields
protocol_version = params.get("protocolVersion")
if not protocol_version:
raise ValueError("Missing protocolVersion")
# Parse client info
client_info_data = params.get("clientInfo", {})
client_info = ClientInfo(
name=client_info_data.get("name", "unknown"),
version=client_info_data.get("version", "unknown")
)
# Parse capabilities
caps_data = params.get("capabilities", {})
capabilities = ClientCapabilities(
roots=caps_data.get("roots", {}),
sampling=caps_data.get("sampling", {}),
experimental=caps_data.get("experimental", {})
)
return InitializeParams(
protocolVersion=protocol_version,
capabilities=capabilities,
clientInfo=client_info
)
# Example usage
params = {
"protocolVersion": "2024-11-05",
"capabilities": {
"sampling": {}
},
"clientInfo": {
"name": "claude-desktop",
"version": "1.0.0"
}
}
init_params = parse_initialize_params(params)
print(f"Client: {init_params.clientInfo.name} v{init_params.clientInfo.version}")
print(f"Protocol: {init_params.protocolVersion}")
print(f"Has sampling: {bool(init_params.capabilities.sampling)}")
Real-World Examples
Here are examples of initialize requests from popular MCP clients:
Claude Desktop
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {}
},
"clientInfo": {
"name": "claude-desktop",
"version": "0.7.0"
}
}
}
Minimal Client
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "my-test-client",
"version": "1.0.0"
}
}
}
A client with empty capabilities ({}) is perfectly valid.
It simply means the client doesn't support any optional features.
The server should still work, just with reduced functionality.
Validation Rules
When your server receives an initialize request, validate:
- Method must be "initialize" — This is always the first request
- protocolVersion is required — Must be a valid version string
- clientInfo is recommended — Should have name and version
- capabilities can be empty — An empty object is valid
SUPPORTED_PROTOCOL_VERSIONS = ["2024-11-05", "2024-10-07"]
def validate_initialize_request(params: dict) -> tuple[bool, str]:
"""Validate initialize request parameters.
Returns (is_valid, error_message).
"""
# Check protocol version
version = params.get("protocolVersion")
if not version:
return False, "Missing protocolVersion"
if version not in SUPPORTED_PROTOCOL_VERSIONS:
return False, f"Unsupported protocol version: {version}"
# Validate clientInfo structure if present
client_info = params.get("clientInfo", {})
if client_info:
if not isinstance(client_info.get("name"), str):
return False, "clientInfo.name must be a string"
if not isinstance(client_info.get("version"), str):
return False, "clientInfo.version must be a string"
return True, ""
Key Takeaways
- The
initializerequest is always the first message - It contains protocol version, client capabilities, and client info
- Protocol version uses date-based format (YYYY-MM-DD)
- Capabilities tell the server what optional features the client supports
- The server must validate and respond appropriately