Lesson 4 - Step 3 of 6

Server Response

Learn how to build the initialize response with serverInfo, capabilities, and negotiated version.

The Initialize Response

After receiving the initialize request, the server must respond with its own information: the negotiated protocol version, server capabilities, and server identity.

json
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "protocolVersion": "2024-11-05",
        "capabilities": {
            "tools": {},
            "resources": {},
            "prompts": {}
        },
        "serverInfo": {
            "name": "my-mcp-server",
            "version": "1.0.0"
        }
    }
}

Response Fields

The result object contains three main fields:

protocolVersion

The protocol version the server has chosen to use. This must be a version that both client and server support.

json
"protocolVersion": "2024-11-05"
Version Negotiation

The server should respond with the highest version it supports that is also supported by the client. If no compatible version exists, the server should return an error instead of a result.

serverInfo

Information about your server. This helps clients identify what server they're connected to and is useful for debugging.

json
"serverInfo": {
    "name": "weather-mcp-server",
    "version": "2.1.0"
}

capabilities

The capabilities your server provides. This tells the client what features are available.

json
"capabilities": {
    "tools": {},
    "resources": {
        "subscribe": true,
        "listChanged": true
    },
    "prompts": {
        "listChanged": true
    },
    "logging": {}
}

Server Capabilities Explained

Here are the main capabilities a server can advertise:

Capability Description
tools Server provides callable tools
resources Server provides readable resources
prompts Server provides prompt templates
logging Server can send log messages to client
experimental Non-standard experimental features

Building the Response in Python

Here's how to construct the initialize response:

python
from dataclasses import dataclass, field, asdict
from typing import Any, cast

# Server configuration
SERVER_NAME = "my-mcp-server"
SERVER_VERSION = "1.0.0"
SUPPORTED_VERSIONS = ["2024-11-05", "2024-10-07"]

@dataclass
class ServerInfo:
    """Information about this MCP server."""
    name: str
    version: str

@dataclass
class ServerCapabilities:
    """Capabilities provided by this server."""
    tools: dict = field(default_factory=dict)
    resources: dict = field(default_factory=dict)
    prompts: dict = field(default_factory=dict)
    logging: dict = field(default_factory=dict)

@dataclass
class InitializeResult:
    """Result of the initialize request."""
    protocolVersion: str
    capabilities: ServerCapabilities
    serverInfo: ServerInfo

def build_initialize_response(
    request_id: int | str,
    client_protocol_version: str
) -> dict:
    """Build the initialize response.

    Args:
        request_id: The ID from the initialize request
        client_protocol_version: Version requested by client

    Returns:
        Complete JSON-RPC response dict
    """
    # Negotiate protocol version
    negotiated_version = negotiate_version(client_protocol_version)

    # Build the result
    result = InitializeResult(
        protocolVersion=negotiated_version,
        capabilities=ServerCapabilities(
            tools={},           # We support tools
            resources={},       # We support resources
            prompts={}          # We support prompts
        ),
        serverInfo=ServerInfo(
            name=SERVER_NAME,
            version=SERVER_VERSION
        )
    )

    # Convert to JSON-RPC response
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "result": asdict(result)
    }

def negotiate_version(client_version: str) -> str:
    """Negotiate the protocol version.

    Returns the highest mutually supported version.
    Raises ValueError if no compatible version.
    """
    if client_version in SUPPORTED_VERSIONS:
        return client_version

    # Try to find any compatible version
    # In practice, you might have more complex logic here
    raise ValueError(f"Unsupported protocol version: {client_version}")

# Example usage
response = build_initialize_response(
    request_id=1,
    client_protocol_version="2024-11-05"
)
print(response)

Example Responses

Full-Featured Server

json
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "protocolVersion": "2024-11-05",
        "capabilities": {
            "tools": {},
            "resources": {
                "subscribe": true,
                "listChanged": true
            },
            "prompts": {
                "listChanged": true
            },
            "logging": {}
        },
        "serverInfo": {
            "name": "full-featured-server",
            "version": "3.0.0"
        }
    }
}

Tools-Only Server

json
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "protocolVersion": "2024-11-05",
        "capabilities": {
            "tools": {}
        },
        "serverInfo": {
            "name": "calculator-server",
            "version": "1.0.0"
        }
    }
}
Only Advertise What You Support

Only include capabilities your server actually implements. If you don't have any resources, don't include "resources": {}. The client uses this to know what requests are valid.

Error Response

If the server cannot complete initialization (e.g., incompatible version), return an error response instead:

json
{
    "jsonrpc": "2.0",
    "id": 1,
    "error": {
        "code": -32602,
        "message": "Unsupported protocol version",
        "data": {
            "requested": "2023-01-01",
            "supported": ["2024-11-05", "2024-10-07"]
        }
    }
}

Python Error Handling

python
def handle_initialize(request: dict) -> dict:
    """Handle the initialize request.

    Returns either a success response or error response.
    """
    request_id = cast(int | str, request.get("id"))
    params = request.get("params", {})

    try:
        # Validate and parse parameters
        client_version = params.get("protocolVersion")
        if not client_version:
            raise ValueError("Missing protocolVersion")

        # Check version compatibility
        if client_version not in SUPPORTED_VERSIONS:
            return {
                "jsonrpc": "2.0",
                "id": request_id,
                "error": {
                    "code": -32602,
                    "message": "Unsupported protocol version",
                    "data": {
                        "requested": client_version,
                        "supported": SUPPORTED_VERSIONS
                    }
                }
            }

        # Build success response
        return build_initialize_response(request_id, client_version)

    except Exception as e:
        return {
            "jsonrpc": "2.0",
            "id": request_id,
            "error": {
                "code": -32603,
                "message": f"Internal error: {str(e)}"
            }
        }

Key Takeaways

  • The server responds with negotiated protocol version, capabilities, and server info
  • Only advertise capabilities your server actually implements
  • Version negotiation should find the highest mutually supported version
  • Return an error if no compatible version exists
  • The response ID must match the request ID