Protocol Features
This page covers cross-cutting MCP protocol features.
MCP Primitives
The MCP protocol defines three core primitives that servers can implement:
Primitive
Control
Description
Example Use
| Prompts |
User-controlled |
Interactive templates invoked by user choice |
Slash commands, menu options |
| Resources |
Application-controlled |
Contextual data managed by the client application |
File contents, API responses |
| Tools |
Model-controlled |
Functions exposed to the LLM to take actions |
API calls, data updates |
Server Capabilities
MCP servers declare capabilities during initialization:
Capability
Feature Flag
Description
| prompts |
listChanged |
Prompt template management |
| resources |
subscribe listChanged |
Resource exposure and updates |
| tools |
listChanged |
Tool discovery and execution |
| logging |
- |
Server logging configuration |
| completions |
- |
Argument completion suggestions |
Ping
Both clients and servers can send ping requests to check that the other side is responsive:
# From a client
result = await session.send_ping()
# From a server (via ServerSession)
result = await server_session.send_ping()
Both return an EmptyResult on success. If the remote side does not respond within the session timeout, an exception is raised.
Cancellation
Either side can cancel a previously-issued request by sending a CancelledNotification:
import mcp.types as types
from mcp import ClientSession
async def cancel_request(session: ClientSession) -> None:
"""Send a cancellation notification for a previously-issued request."""
await session.send_notification(
types.ClientNotification(
types.CancelledNotification(
params=types.CancelledNotificationParams(
requestId="request-id-to-cancel",
reason="User navigated away",
)
)
)
)
Full example: examples/snippets/clients/cancellation.py
The CancelledNotificationParams fields:
- requestId (optional): The ID of the request to cancel. Required for non-task cancellations.
- reason (optional): A human-readable string describing why the request was cancelled.
Capability Negotiation
During initialization, the client and server exchange capability declarations. The Python SDK automatically declares capabilities based on which callbacks and handlers are registered:
Client capabilities (auto-declared when callbacks are provided):
- sampling -- declared when sampling_callback is passed to ClientSession
- roots -- declared when list_roots_callback is passed to ClientSession
- elicitation -- declared when elicitation_callback is passed to ClientSession
Server capabilities (auto-declared when handlers are registered):
- prompts -- declared when a list_prompts handler is registered
- resources -- declared when a list_resources handler is registered
- tools -- declared when a list_tools handler is registered
- logging -- declared when a set_logging_level handler is registered
- completions -- declared when a completion handler is registered
After initialization, clients can inspect server capabilities:
capabilities = session.get_server_capabilities()
if capabilities and capabilities.tools:
tools = await session.list_tools()
Protocol Version Negotiation
The SDK defines LATEST_PROTOCOL_VERSION and SUPPORTED_PROTOCOL_VERSIONS in mcp.shared.version:
from mcp.shared.version import LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS
# LATEST_PROTOCOL_VERSION is the version the SDK advertises during initialization
# SUPPORTED_PROTOCOL_VERSIONS lists all versions the SDK can work with
During initialization, the client sends LATEST_PROTOCOL_VERSION. If the server responds with a version not in SUPPORTED_PROTOCOL_VERSIONS, the client raises a RuntimeError. This ensures both sides agree on a compatible protocol version before exchanging messages.
JSON Schema (2020-12)
MCP uses JSON Schema 2020-12 for tool input schemas, output schemas, and elicitation schemas. When using Pydantic models, schemas are generated automatically via model_json_schema():
from pydantic import BaseModel, Field
class SearchParams(BaseModel):
query: str = Field(description="Search query string")
max_results: int = Field(default=10, description="Maximum results to return")
# Pydantic generates a JSON Schema 2020-12 compatible schema:
schema = SearchParams.model_json_schema()
# {
# "properties": {
# "query": {"description": "Search query string", "type": "string"},
# "max_results": {
# "default": 10,
# "description": "Maximum results to return",
# "type": "integer",
# },
# },
# "required": ["query"],
# "title": "SearchParams",
# "type": "object",
# }
Full example: examples/snippets/servers/json_schema_example.py
For FastMCP tools, input schemas are derived automatically from function signatures. For structured output, the output schema is derived from the return type annotation.
For pagination details, see: