Lesson 4 - Step 4 of 6

Capability Negotiation

Understand what capabilities mean and how to use the tools, resources, and prompts flags.

What Are Capabilities?

Capabilities are feature flags that tell each party what the other can do. They enable graceful feature discovery without breaking compatibility.

diagram
┌─────────────────────────────────────────────────────────────┐
│                   CAPABILITY EXCHANGE                        │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Client Capabilities          Server Capabilities            │
│  ────────────────────         ────────────────────           │
│  • roots                      • tools                        │
│  • sampling                   • resources                    │
│  • experimental               • prompts                      │
│                               • logging                      │
│                               • experimental                 │
│                                                              │
│  "I can provide roots         "I offer tools and             │
│   and handle sampling"         resources"                    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Server Capability Flags

Let's examine each server capability in detail:

tools

The tools capability indicates your server provides callable functions. When present, clients can use tools/list and tools/call.

json
"capabilities": {
    "tools": {
        "listChanged": true  // Optional: server will notify on tool changes
    }
}
Sub-capability Description
listChanged Server sends notifications/tools/list_changed when tools change

resources

The resources capability indicates your server provides readable data. When present, clients can use resources/list and resources/read.

json
"capabilities": {
    "resources": {
        "subscribe": true,    // Clients can subscribe to resource updates
        "listChanged": true   // Server notifies when resource list changes
    }
}
Sub-capability Description
subscribe Clients can subscribe to resource changes via resources/subscribe
listChanged Server sends notifications/resources/list_changed

prompts

The prompts capability indicates your server provides prompt templates. When present, clients can use prompts/list and prompts/get.

json
"capabilities": {
    "prompts": {
        "listChanged": true  // Server notifies when prompt list changes
    }
}

logging

The logging capability allows the server to send log messages to the client. Useful for debugging and monitoring.

json
"capabilities": {
    "logging": {}  // Server can send notifications/message
}

Client Capability Flags

Clients also advertise their capabilities:

roots

Indicates the client can provide filesystem roots to the server. This is useful for servers that need to access files.

json
"capabilities": {
    "roots": {
        "listChanged": true  // Client notifies when roots change
    }
}

sampling

Indicates the client supports LLM sampling requests from the server. This allows servers to request AI completions.

json
"capabilities": {
    "sampling": {}  // Client supports sampling/createMessage
}

Capability-Based Behavior

Your server should adapt its behavior based on capabilities:

python
from dataclasses import dataclass, field

@dataclass
class ClientCapabilities:
    """Capabilities the client supports."""
    roots: dict = field(default_factory=dict)
    sampling: dict = field(default_factory=dict)
    experimental: dict = field(default_factory=dict)

    @property
    def has_roots(self) -> bool:
        """Check if client can provide filesystem roots."""
        return bool(self.roots)

    @property
    def has_sampling(self) -> bool:
        """Check if client supports LLM sampling."""
        return bool(self.sampling)

    @property
    def supports_roots_list_changed(self) -> bool:
        """Check if client notifies on root changes."""
        return self.roots.get("listChanged", False)

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

    @property
    def has_tools(self) -> bool:
        return bool(self.tools)

    @property
    def has_resources(self) -> bool:
        return bool(self.resources)

    @property
    def has_prompts(self) -> bool:
        return bool(self.prompts)

class MCPServer:
    def __init__(self):
        self.client_capabilities: ClientCapabilities | None = None
        self.server_capabilities = ServerCapabilities(
            tools={},
            resources={"subscribe": True}
        )

    def handle_initialize(self, params: dict) -> dict:
        """Store client capabilities during initialization."""
        caps = params.get("capabilities", {})
        self.client_capabilities = ClientCapabilities(
            roots=caps.get("roots", {}),
            sampling=caps.get("sampling", {}),
            experimental=caps.get("experimental", {})
        )

        # Log what the client can do
        print(f"Client has roots: {self.client_capabilities.has_roots}")
        print(f"Client has sampling: {self.client_capabilities.has_sampling}")

        # Return our capabilities
        return {
            "protocolVersion": "2024-11-05",
            "capabilities": {
                "tools": self.server_capabilities.tools,
                "resources": self.server_capabilities.resources,
            },
            "serverInfo": {
                "name": "capability-aware-server",
                "version": "1.0.0"
            }
        }

    def can_request_sampling(self) -> bool:
        """Check if we can request LLM sampling from the client."""
        return (
            self.client_capabilities is not None and
            self.client_capabilities.has_sampling
        )

Capability-Gated Methods

Clients should only call methods for advertised capabilities:

text
Server Capability        Enabled Methods
─────────────────────    ───────────────────────────────────
tools                    tools/list, tools/call
resources                resources/list, resources/read,
                         resources/subscribe, resources/unsubscribe
prompts                  prompts/list, prompts/get
logging                  (server can send notifications/message)
Reject Invalid Requests

If a client calls tools/list but your server didn't advertise the tools capability, you should return a "Method not found" error (-32601).

python
def handle_method(self, method: str, params: dict) -> dict:
    """Route method calls with capability checking."""

    # Check tools capability
    if method.startswith("tools/"):
        if not self.server_capabilities.has_tools:
            return {
                "error": {
                    "code": -32601,
                    "message": f"Method not found: {method} (tools not supported)"
                }
            }

    # Check resources capability
    if method.startswith("resources/"):
        if not self.server_capabilities.has_resources:
            return {
                "error": {
                    "code": -32601,
                    "message": f"Method not found: {method} (resources not supported)"
                }
            }

    # Check prompts capability
    if method.startswith("prompts/"):
        if not self.server_capabilities.has_prompts:
            return {
                "error": {
                    "code": -32601,
                    "message": f"Method not found: {method} (prompts not supported)"
                }
            }

    # Route to actual handlers
    handlers = {
        "tools/list": self.handle_tools_list,
        "tools/call": self.handle_tools_call,
        "resources/list": self.handle_resources_list,
        "resources/read": self.handle_resources_read,
        # ... etc
    }

    handler = handlers.get(method)
    if handler:
        return handler(params)

    return {"error": {"code": -32601, "message": f"Unknown method: {method}"}}

Common Capability Combinations

Here are typical capability configurations for different server types:

Tool-only Server (Calculator, API wrapper)

json
"capabilities": {
    "tools": {}
}

File System Server

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

Full-Featured Server

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

For your first MCP server, start with just "tools": {}. You can add more capabilities as you implement additional features. It's better to under-promise than to advertise features you haven't built.

Key Takeaways

  • Capabilities are feature flags that enable graceful feature discovery
  • Servers advertise: tools, resources, prompts, logging
  • Clients advertise: roots, sampling
  • Only advertise capabilities you actually implement
  • Reject method calls for capabilities you don't support
  • Use sub-capabilities like listChanged and subscribe for advanced features