Skip to content
Open
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
13 changes: 6 additions & 7 deletions docs/advanced/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ That's the whole triangle. Everything on this page is the middle bullet.

The SDK has no opinion about what a valid token looks like. You tell it, by implementing **`TokenVerifier`**:

```python title="server.py" hl_lines="12-14 19-24"
```python title="server.py" hl_lines="14-16 21-26"
--8<-- "docs_src/authorization/tutorial001.py"
```

Expand All @@ -27,7 +27,7 @@ The SDK has no opinion about what a valid token looks like. You tell it, by impl
`AuthSettings` is the public face of your resource server:

* `issuer_url`: the authorization server that issues your tokens.
* `resource_server_url`: the public URL of this MCP endpoint. It names *which* resource a token is for, and it's where the discovery document lives.
* `resource_server_url`: the public URL of this MCP endpoint. It names *which* resource a token is for, and it's where the discovery document lives. The SDK rejects any token whose `AccessToken.resource` does not match this URL, including one whose verifier left `resource` unset, so a token issued for a different resource (or for no resource) never reaches a tool. If your verifier validates the audience itself and cannot surface the claim, set `verifier_validates_audience=True`.
* `required_scopes`: every token must carry all of them.

!!! tip
Expand Down Expand Up @@ -61,14 +61,13 @@ You registered one tool. The second route is the SDK's.
This document is how a client that has never heard of your server finds its way in: it reads `authorization_servers` and goes there for a token. You wrote none of it.

!!! check
Call `/mcp` with no token (or with one your verifier returned `None` for) and the request is
stopped at the door:
Call `/mcp` with no token and the request is stopped at the door:

```text
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Authentication required", resource_metadata="http://127.0.0.1:8000/.well-known/oauth-protected-resource/mcp"
WWW-Authenticate: Bearer scope="notes:read", resource_metadata="http://127.0.0.1:8000/.well-known/oauth-protected-resource/mcp"

{"error": "invalid_token", "error_description": "Authentication required"}
{}
```

Nothing was parsed and no tool ran. And that `resource_metadata` pointer in `WWW-Authenticate` is
Expand All @@ -84,7 +83,7 @@ This document is how a client that has never heard of your server finds its way

Inside any handler, **`get_access_token()`** is the `AccessToken` your verifier returned for the current request:

```python title="server.py" hl_lines="4 32-35"
```python title="server.py" hl_lines="4 34-37"
--8<-- "docs_src/authorization/tutorial002.py"
```

Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/deprecated.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The table below names each deprecated feature, why it is going away, and the rep
| **Server-initiated sampling**: `ctx.session.create_message()`, the `sampling_callback=` you pass to `Client(...)` | SEP-2577 retires the capability. | Return `InputRequiredResult` and let the client retry the call (see **Multi-round-trip requests**). |
| **Protocol logging**: `ctx.log()`, `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`, `ctx.session.send_log_message()`, `client.set_logging_level()` | SEP-2577 retires the capability. Nothing in-protocol replaces it. | Ordinary `import logging` to stderr (see **Logging**). |
| **`ping`**: `client.send_ping()` | **Removed** from the protocol, not merely deprecated. There is no `ping` method in 2026-07-28. | Nothing. It only works against a `mode="legacy"` connection. |
| **Client->server progress**: `client.send_progress_notification()` | 2026-07-28 makes progress server->client only. | Nothing to send. Your *server* reports progress with `ctx.report_progress()` (see **Progress**). |
| **Client->server progress**: `client.send_progress_notification()` | 2026-07-28 makes progress server->client only. | Nothing to send. Your *server* reports progress with `ctx.report_progress()` (see **Progress**). The SDK separately deprecates the server-side explicit-token `ctx.session.send_progress_notification()` in favour of `report_progress` (same warning category, same replacement). |

Three things fall out of that table:

Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/oauth-clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ The first time `Client` sends a request, the server answers `401`. The provider

1. **Discovery.** It reads the `WWW-Authenticate` header, fetches the server's Protected Resource Metadata from `/.well-known/oauth-protected-resource`, learns which authorization server protects this resource, and fetches *that* server's metadata.
2. **Registration.** Nothing in storage? It registers you dynamically with your `OAuthClientMetadata` and stores the result.
3. **Authorization.** It generates the PKCE pair and a `state`, builds the authorization URL, awaits your `redirect_handler`, then awaits your `callback_handler` for the code.
3. **Authorization.** It checks that the discovered authorization-server metadata advertises `S256` PKCE support (and stops with `OAuthFlowError` if it does not), generates the PKCE pair and a `state`, builds the authorization URL, awaits your `redirect_handler`, then awaits your `callback_handler` for the code.
4. **Exchange.** It trades the code for an `OAuthToken`, stores it, and replays your original request with `Authorization: Bearer ...`.

After that it is quiet. Tokens come out of storage, an expired access token is refreshed with the refresh token, and only when none of that works does it run the flow again.
Expand Down
2 changes: 1 addition & 1 deletion docs/client/callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Pass all three callbacks and you get `['elicitation', 'sampling', 'roots']`. Pas
MCPError: Elicitation not supported
```

That is a protocol error (`-32600`, *invalid request*), not a tool error: there is nothing for
That is a protocol error (`-32602`, *invalid params*), not a tool error: there is nothing for
the model to read and retry. It's why `client_features` is worth having: a well-behaved server
checks before it asks.

Expand Down
4 changes: 2 additions & 2 deletions docs/client/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Everything else on this page is identical across all three. Headers, subprocesse
Four read-only properties, populated the moment you enter the block:

* `client.server_info`: the server's identity. `server_info.name` here is `"Bookshop"`, `server_info.version` is whatever the server reports.
* `client.server_capabilities`: what the server can do (`tools`, `resources`, `prompts`, `completions`, ...). A capability the server doesn't have is `None`.
* `client.server_capabilities`: what the server can do (`tools`, `resources`, `prompts`, `completions`, ...). A capability the server doesn't have is `None`. Pass `Client(..., strict_capabilities=True)` and the client uses this to refuse, client-side, a call whose capability the server didn't advertise: it raises `MCPError` with code `-32601` without sending anything. By default the request is sent and the server's answer comes back.
* `client.protocol_version`: the protocol version the two sides agreed on. Here it is `"2026-07-28"`.
* `client.instructions`: the server's `instructions=` string, or `None` if it didn't set one.

Expand Down Expand Up @@ -145,7 +145,7 @@ The resource verbs come in pairs: two ways to list, one way to read.

`read_resource` returns `contents`, a list of `TextResourceContents` or `BlobResourceContents`. Same idea as tool content: narrow with `isinstance`, then read `.text` (or `.blob`).

A client can also **subscribe** to a resource and be told when it changes: `subscribe_resource(uri)` and `unsubscribe_resource(uri)`, same shape as everything else here. `MCPServer` doesn't implement that half. It says so up front (`server_capabilities.resources.subscribe` is `False`) and answers the request with an `MCPError`: `-32601`, *Method not found*. A server that does support subscriptions is built on the low-level `Server` (**The low-level Server**).
A client can also **subscribe** to a resource and be told when it changes: `subscribe_resource(uri)` and `unsubscribe_resource(uri)`, same shape as everything else here. `MCPServer` doesn't implement that half. It says so up front (`server_capabilities.resources.subscribe` is `False`) and answers the request with an `MCPError`: `-32601`, *Method not found*. With `strict_capabilities=True` you get the same `-32601` without the round trip: the client sees `server_capabilities.resources.subscribe` is falsy and never sends the request. A server that does support subscriptions is built on the low-level `Server` (**The low-level Server**).

## Prompts

Expand Down
Loading