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.
┌─────────────────────────────────────────────────────────────┐
│ 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.
"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.
"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.
"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.
"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.
"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.
"capabilities": {
"sampling": {} // Client supports sampling/createMessage
}
Capability-Based Behavior
Your server should adapt its behavior based on capabilities:
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:
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)
If a client calls tools/list but your server didn't advertise
the tools capability, you should return a "Method not found"
error (-32601).
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)
"capabilities": {
"tools": {}
}
File System Server
"capabilities": {
"tools": {},
"resources": {
"subscribe": true,
"listChanged": true
}
}
Full-Featured Server
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
},
"logging": {}
}
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
listChangedandsubscribefor advanced features