Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,115 @@ _Full example: [examples/snippets/clients/oauth_client.py](https://github.com/mo

For a complete working example, see [`examples/clients/simple-auth-client/`](examples/clients/simple-auth-client/).

## Roots

### Listing Roots

Clients can provide a `list_roots_callback` so that servers can discover the client's workspace roots (directories, project folders, etc.):

```python
from mcp import ClientSession, types
from mcp.shared.context import RequestContext


async def handle_list_roots(
context: RequestContext[ClientSession, None],
) -> types.ListRootsResult:
"""Return the client's workspace roots."""
return types.ListRootsResult(
roots=[
types.Root(uri="file:///home/user/project", name="My Project"),
types.Root(uri="file:///home/user/data", name="Data Folder"),
]
)


# Pass the callback when creating the session
session = ClientSession(
read_stream,
write_stream,
list_roots_callback=handle_list_roots,
)
```

When a `list_roots_callback` is provided, the client automatically declares the `roots` capability (with `listChanged=True`) during initialization.

### Roots Change Notifications

When the client's workspace roots change (e.g., a folder is added or removed), notify the server:

```python
# After roots change, notify the server
await session.send_roots_list_changed()
```

## SSE Transport (Legacy)

For servers that use the older SSE transport, use `sse_client()` from `mcp.client.sse`:

```python
import asyncio

from mcp import ClientSession
from mcp.client.sse import sse_client


async def main():
async with sse_client("http://localhost:8000/sse") as (read_stream, write_stream):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()

tools = await session.list_tools()
print(f"Available tools: {[t.name for t in tools.tools]}")


asyncio.run(main())
```

The `sse_client()` function accepts optional `headers`, `timeout`, `sse_read_timeout`, and `auth` parameters. The SSE transport is considered legacy; prefer [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) for new servers.

## Ping

Send a ping to verify the server is responsive:

```python
# After session.initialize()
result = await session.send_ping()
# Returns EmptyResult on success; raises on timeout
```

## Logging

### Receiving Log Messages

Pass a `logging_callback` to receive log messages from the server:

```python
from mcp import ClientSession, types


async def handle_log(params: types.LoggingMessageNotificationParams) -> None:
"""Handle log messages from the server."""
print(f"[{params.level}] {params.data}")


session = ClientSession(
read_stream,
write_stream,
logging_callback=handle_log,
)
```

### Setting the Server Log Level

Request that the server change its minimum log level:

```python
await session.set_logging_level("debug")
```

The `level` parameter is a `LoggingLevel` string: `"debug"`, `"info"`, `"notice"`, `"warning"`, `"error"`, `"critical"`, `"alert"`, or `"emergency"`.

## Parsing Tool Results

When calling tools through MCP, the `CallToolResult` object contains the tool's response in a structured format. Understanding how to parse this result is essential for properly handling tool outputs.
Expand Down
110 changes: 110 additions & 0 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,116 @@ MCP servers declare capabilities during initialization:
| `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:

```python
# 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`:

```python
import mcp.types as types

# Send a cancellation notification
await session.send_notification(
types.ClientNotification(
types.CancelledNotification(
params=types.CancelledNotificationParams(
requestId="request-id-to-cancel",
reason="User navigated away",
)
)
)
)
```

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:

```python
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`:

```python
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](https://json-schema.org/draft/2020-12) for tool input schemas, output schemas, and elicitation schemas. When using Pydantic models, schemas are generated automatically via `model_json_schema()`:

```python
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",
# }
```

For FastMCP tools, input schemas are derived automatically from function signatures. For structured output, the output schema is derived from the return type annotation.

## Pagination

For pagination details, see:
Expand Down
Loading