Skip to content

Commit 688b8a7

Browse files
committed
docs: move each page into the directory of its nav section
The nav regroup put pages from docs/tutorial/ under "Servers", pages from docs/advanced/ under "Clients", and so on -- the sidebar was right but the on-disk layout and every URL still described the old grouping. Move each of the 28 affected pages so its directory matches its section, under one rule: the directory follows the section and the filename stem never changes. docs/tutorial/ is gone. Every one of the 28 old URLs gets an entry in mkdocs-redirects' redirect_maps, so nothing 404s; under `strict: true` a stale redirect target is itself a build failure, and the rendered redirect stubs were spot-checked. Every relative inter-page link is recomputed for the pages' new locations, and the strict build (which fails on any broken link, nav path, or anchor) validates all of them. The other things that reference the moved paths move in lockstep: - The tests/docs_src/ module docstrings, two example READMEs, and RELEASE.md all name docs pages by path. - tests/test_examples.py's find_examples() directory list is rewritten for the new layout, and gains the two pages that are now at the docs root (protocol-versions.md, deprecated.md) and would otherwise silently lose the inline-code-block lint coverage. None of docs_src/ moves: the `--8<--` snippet includes are repo-root- relative, so the 120 tested example files and their tests are untouched. The "Inside your handler" section index also drops an over-broad claim while it is being moved into: Elicitation and Multi-round-trip requests are not Context verbs (Resolve is an annotated parameter and MRTR is a return value; only the legacy `ctx.elicit` path touches Context), so the Context bullet now names only the progress and change-notification verbs it actually carries.
1 parent 63ba33d commit 688b8a7

71 files changed

Lines changed: 201 additions & 169 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

RELEASE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ the publish job — `skip-existing` makes it skip whatever already landed. The
3535

3636
1. Update the pre-release version examples in `README.md` and the docs
3737
(grep the outgoing version — the pins live in the README Installation
38-
section, `docs/index.md`, and `docs/installation.md`) so the tagged
38+
section, `docs/index.md`, and `docs/get-started/installation.md`) so the tagged
3939
commit — and therefore the README PyPI publishes — names the version
4040
being released. When entering a new phase (alpha → beta → rc), update
4141
the banner wording too.

docs/advanced/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ instead:
2222
* **Multi-round-trip requests** and **Subscriptions** are under
2323
**[Inside your handler](../handlers/index.md)** — both are things a handler *does*.
2424
* **URI templates** is under **[Servers](../servers/index.md)**, next to Resources.
25-
* **[Protocol versions](../client/protocol-versions.md)** and
26-
**[Deprecated features](deprecated.md)** each have their own top-level page.
25+
* **[Protocol versions](../protocol-versions.md)** and
26+
**[Deprecated features](../deprecated.md)** each have their own top-level page.
2727

2828
If you're not sure whether you need this section, you don't.

docs/advanced/low-level-server.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ For everything else, stay on `MCPServer`.
1212

1313
## The same tool, by hand
1414

15-
This is `search_books` from **[Tools](../tutorial/tools.md)** (the nine-line `@mcp.tool()` file) with the sugar removed:
15+
This is `search_books` from **[Tools](../servers/tools.md)** (the nine-line `@mcp.tool()` file) with the sugar removed:
1616

1717
```python title="server.py" hl_lines="23 27 33"
1818
--8<-- "docs_src/lowlevel/tutorial001.py"
@@ -61,7 +61,7 @@ The same text the `@mcp.tool()` version produced. Two honest differences:
6161

6262
## Nothing is checked for you
6363

64-
In **[Tools](../tutorial/tools.md)** you saw a bad argument get rejected before your function ran. That was `MCPServer` validating the call against the schema it generated.
64+
In **[Tools](../servers/tools.md)** you saw a bad argument get rejected before your function ran. That was `MCPServer` validating the call against the schema it generated.
6565

6666
`Server` does not do that. Your `input_schema` is *advertised* to the client; it is never *applied* to `params.arguments`.
6767

@@ -72,9 +72,9 @@ In **[Tools](../tutorial/tools.md)** you saw a bad argument get rejected before
7272
MCPError: Internal server error
7373
```
7474

75-
A JSON-RPC error, code `-32603`, with a deliberately generic message: the SDK won't leak your traceback to a remote caller. The model never finds out what it did wrong, so it can't retry. (In a test, `raise_exceptions=True` surfaces the real exception instead; see **[Testing](../tutorial/testing.md)**.)
75+
A JSON-RPC error, code `-32603`, with a deliberately generic message: the SDK won't leak your traceback to a remote caller. The model never finds out what it did wrong, so it can't retry. (In a test, `raise_exceptions=True` surfaces the real exception instead; see **[Testing](../get-started/testing.md)**.)
7676

77-
That generalises. An exception raised from a low-level handler is **always** a protocol error, never an `is_error=True` tool result. If you want the model to read the failure and recover, validate `params.arguments` yourself and return `CallToolResult(content=[TextContent(...)], is_error=True)`. The two kinds of failure are the subject of **[Handling errors](../tutorial/handling-errors.md)**.
77+
That generalises. An exception raised from a low-level handler is **always** a protocol error, never an `is_error=True` tool result. If you want the model to read the failure and recover, validate `params.arguments` yourself and return `CallToolResult(content=[TextContent(...)], is_error=True)`. The two kinds of failure are the subject of **[Handling errors](../servers/handling-errors.md)**.
7878

7979
## Two tools, one handler
8080

@@ -106,7 +106,7 @@ Call it and the result carries both representations:
106106
}
107107
```
108108

109-
The server never compares the two fields. This SDK's `Client` does: return `structured_content` that doesn't satisfy the `output_schema` you declared and `call_tool` raises a `RuntimeError` that starts with `Invalid structured content returned by tool search_books` and goes on to quote the `jsonschema` failure. Promising a schema is cheap; keeping it is on you. The whole ladder of return types and schemas is in **[Structured Output](../tutorial/structured-output.md)**.
109+
The server never compares the two fields. This SDK's `Client` does: return `structured_content` that doesn't satisfy the `output_schema` you declared and `call_tool` raises a `RuntimeError` that starts with `Invalid structured content returned by tool search_books` and goes on to quote the `jsonschema` failure. Promising a schema is cheap; keeping it is on you. The whole ladder of return types and schemas is in **[Structured Output](../servers/structured-output.md)**.
110110

111111
## `_meta`: for the application, not the model
112112

@@ -147,7 +147,7 @@ No `resources`, no `prompts`: there is nothing to back them. Pass `on_list_promp
147147

148148
* The lifespan is a `Callable[[Server[Catalog]], AbstractAsyncContextManager[Catalog]]`; `@asynccontextmanager` on an `async` generator gives you exactly that.
149149
* Whatever it `yield`s becomes `ctx.lifespan_context`, and because the handlers are annotated `ServerRequestContext[Catalog]`, `.search(...)` autocompletes and type-checks.
150-
* It is entered once when the server starts and exited once when it stops. Startup, teardown, and `MCPServer`'s version of the same idea are in **[Lifespan](../tutorial/lifespan.md)**.
150+
* It is entered once when the server starts and exited once when it stops. Startup, teardown, and `MCPServer`'s version of the same idea are in **[Lifespan](../handlers/lifespan.md)**.
151151

152152
Without a `lifespan=`, `ctx.lifespan_context` is an empty `dict`.
153153

@@ -181,9 +181,9 @@ The handshake belongs to the runner. `server/discover`, `ping`, and every other
181181

182182
Each of these is one idea you now have the vocabulary for; each has its own chapter.
183183

184-
* `on_call_tool`, `on_get_prompt`, and `on_read_resource` may return an `InputRequiredResult` instead of their normal result to pause the call and ask the client for input; see **[Multi-round-trip requests](multi-round-trip.md)**. True to this tier, nothing is installed for you: where `MCPServer` seals `requestState` by default, here the `request_state` you set crosses the wire exactly as written until you opt in with `server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[...]), default_audience=server.name))`: one line (both names import from `mcp.server.request_state`) for the identical sealing and verification `MCPServer` performs (**[Protecting `requestState`](multi-round-trip.md#protecting-requeststate)**).
184+
* `on_call_tool`, `on_get_prompt`, and `on_read_resource` may return an `InputRequiredResult` instead of their normal result to pause the call and ask the client for input; see **[Multi-round-trip requests](../handlers/multi-round-trip.md)**. True to this tier, nothing is installed for you: where `MCPServer` seals `requestState` by default, here the `request_state` you set crosses the wire exactly as written until you opt in with `server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[...]), default_audience=server.name))`: one line (both names import from `mcp.server.request_state`) for the identical sealing and verification `MCPServer` performs (**[Protecting `requestState`](../handlers/multi-round-trip.md#protecting-requeststate)**).
185185
* `on_list_resources`, `on_read_resource`, `on_list_prompts`, `on_get_prompt`, `on_completion` are the same `(ctx, params) -> result` shape for the other primitives.
186-
* `on_subscriptions_listen` serves the 2026-07-28 `subscriptions/listen` stream. Pass a `ListenHandler` built over a `SubscriptionBus` and publish events to the bus from your other handlers; see **[Subscriptions](subscriptions.md)** for the full composition.
186+
* `on_subscriptions_listen` serves the 2026-07-28 `subscriptions/listen` stream. Pass a `ListenHandler` built over a `SubscriptionBus` and publish events to the bus from your other handlers; see **[Subscriptions](../handlers/subscriptions.md)** for the full composition.
187187
* `server.streamable_http_app()` returns the same Starlette app `MCPServer`'s does; deploy it the way **[Running your server](../run/index.md)** deploys any other ASGI app. There is no `server.run(transport=...)` down here: `server.run(read_stream, write_stream, server.create_initialization_options())` drives one connection over a pair of streams, and that one line is the whole story.
188188

189189
## Recap

docs/advanced/middleware.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ In increasing order of how much you should hesitate:
8484
The SDK ships exactly one middleware, and it is already on your server's list: the one that
8585
emits an OpenTelemetry span for every message. You don't append it, and most of the time you
8686
don't think about it. It is a no-op until you install an exporter, and it has its own page:
87-
**[OpenTelemetry](opentelemetry.md)**.
87+
**[OpenTelemetry](../run/opentelemetry.md)**.
8888

8989
!!! info
9090
If you have written ASGI middleware, you already know this shape. Starlette's
@@ -101,8 +101,8 @@ don't think about it. It is a no-op until you install an exporter, and it has it
101101
* `ctx.request_id is None` is how you tell a notification from a request.
102102
* Raise instead of calling `call_next` to refuse one message; the connection survives.
103103
* The SDK's own OpenTelemetry tracing is a middleware too, already on the list. See
104-
**[OpenTelemetry](opentelemetry.md)**.
104+
**[OpenTelemetry](../run/opentelemetry.md)**.
105105
* The whole surface is provisional. Observe with it; don't build on it.
106106

107-
That is everything that wraps a request. **[Authorization](authorization.md)** is what decides whether the request
107+
That is everything that wraps a request. **[Authorization](../run/authorization.md)** is what decides whether the request
108108
gets to run at all.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Cache keys also carry the **server's identity**: the URL string you dialed, with
8585
### What the cache never does
8686

8787
* **Session-tier calls bypass it.** `client.session.list_tools()` and friends always make the round trip; the cache lives on the `Client` verbs.
88-
* **`server/discover` stays out of it.** The discover result is delivered once, at connect, and never enters the response cache, even when it carries a `ttlMs`. If you persist one yourself to skip the reconnect probe ([`prior_discover`](../client/protocol-versions.md#reconnecting-with-prior_discover)), its freshness is your bookkeeping: `DiscoverResult` carries `ttl_ms` and `cache_scope`, already parsed, for exactly that purpose.
88+
* **`server/discover` stays out of it.** The discover result is delivered once, at connect, and never enters the response cache, even when it carries a `ttlMs`. If you persist one yourself to skip the reconnect probe ([`prior_discover`](../protocol-versions.md#reconnecting-with-prior_discover)), its freshness is your bookkeeping: `DiscoverResult` carries `ttl_ms` and `cache_scope`, already parsed, for exactly that purpose.
8989
* **Continuation pages are never cached.** Only cursor-less calls participate. A continuation page rejected for an expired cursor does *evict* the cached listing, because the listing changed under it.
9090
* **Multi-round-trip reads are never cached.** A `read_resource` seeded with `input_responses`/`request_state`, or one that resolves through input rounds, never enters the cache (a spec MUST).
9191
* **Notification eviction needs notifications.** Eviction is only as good as the transport's delivery, and the modern in-process path (`Client(server)` with the default `mode="auto"`) does not deliver standalone notifications today.

docs/client/callbacks.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Here is a server whose tool can't finish on its own:
1515
* `ctx.elicit(...)` sends an `elicitation/create` request **to the client** and waits.
1616
* The tool doesn't return until somebody (a person in a form, or your code) supplies a `name`.
1717

18-
That is the server half, and the **[Elicitation](../tutorial/elicitation.md)** chapter owns it. This chapter is the other end of the wire.
18+
That is the server half, and the **[Elicitation](../handlers/elicitation.md)** chapter owns it. This chapter is the other end of the wire.
1919

2020
## The elicitation callback
2121

@@ -31,7 +31,7 @@ That is the server half, and the **[Elicitation](../tutorial/elicitation.md)** c
3131
!!! tip
3232
`params` is a union of the two elicitation modes. Here `params.mode` is `"form"`; a `"url"` request
3333
carries `params.url` instead of a schema. One callback handles both; branch on `params.mode`.
34-
**[Elicitation](../tutorial/elicitation.md)** shows the full pattern.
34+
**[Elicitation](../handlers/elicitation.md)** shows the full pattern.
3535

3636
### Try it
3737

@@ -59,11 +59,11 @@ One `tools/call` from you, one `elicitation/create` back from the server, answer
5959
protocol path, and that path has no back-channel for server-to-client requests: `ctx.elicit`
6060
fails before your callback ever runs. The transport doesn't decide that; the negotiated
6161
protocol does, in-memory and over a URL alike. Pin `mode="legacy"` whenever your client has
62-
to answer one; every test behind this page does. **[Protocol versions](protocol-versions.md)** has the whole story.
62+
to answer one; every test behind this page does. **[Protocol versions](../protocol-versions.md)** has the whole story.
6363

6464
On a 2026-07-28 session the callback isn't dead, it's fed differently: when a tool returns an
6565
`InputRequiredResult` carrying an `ElicitRequest`, `Client` dispatches that entry to the same
66-
`elicitation_callback` and retries the call for you. That flow is **[Multi-round-trip requests](../advanced/multi-round-trip.md)**.
66+
`elicitation_callback` and retries the call for you. That flow is **[Multi-round-trip requests](../handlers/multi-round-trip.md)**.
6767

6868
## A callback is a capability
6969

@@ -113,7 +113,7 @@ Pass all three callbacks and you get `['elicitation', 'sampling', 'roots']`. Pas
113113

114114
`sampling_callback` answers `sampling/createMessage`: the server asking *your* model to complete something. `list_roots_callback` answers `roots/list`: the server asking which directories it may work in.
115115

116-
Both work. Both follow the rule above. And both serve RPCs the **2026-07-28 spec removes**: a modern server doesn't call back into your client mid-request, it hands the request back to you as part of the tool result (**[Multi-round-trip requests](../advanced/multi-round-trip.md)**). The callbacks themselves are not dead. When an `InputRequiredResult` carries a `CreateMessageRequest` or a `ListRootsRequest`, `Client`'s auto-loop dispatches it to the same `sampling_callback` or `list_roots_callback` you registered here. The whole list is in **[Deprecated features](../advanced/deprecated.md)**.
116+
Both work. Both follow the rule above. And both serve RPCs the **2026-07-28 spec removes**: a modern server doesn't call back into your client mid-request, it hands the request back to you as part of the tool result (**[Multi-round-trip requests](../handlers/multi-round-trip.md)**). The callbacks themselves are not dead. When an `InputRequiredResult` carries a `CreateMessageRequest` or a `ListRootsRequest`, `Client`'s auto-loop dispatches it to the same `sampling_callback` or `list_roots_callback` you registered here. The whole list is in **[Deprecated features](../deprecated.md)**.
117117

118118
You still need the callbacks to talk to servers that haven't moved. The signatures:
119119

@@ -131,7 +131,7 @@ Pass them to `Client(...)` exactly like `elicitation_callback`.
131131

132132
Two more. Neither declares anything.
133133

134-
`logging_callback` receives every `notifications/message` a server sends, as `LoggingMessageNotificationParams` (`level`, `logger`, `data`). Protocol logging is itself deprecated by the 2026-07-28 spec (**[Logging](../tutorial/logging.md)** has what to do instead), so this callback exists for the servers that still emit it.
134+
`logging_callback` receives every `notifications/message` a server sends, as `LoggingMessageNotificationParams` (`level`, `logger`, `data`). Protocol logging is itself deprecated by the 2026-07-28 spec (**[Logging](../handlers/logging.md)** has what to do instead), so this callback exists for the servers that still emit it.
135135

136136
`message_handler` is the catch-all: every server notification reaches it (as well as its specific callback), and on a stream-backed transport so does every transport-level `Exception`. The one pattern worth knowing is `if isinstance(message, Exception): raise message`, so a broken connection fails loudly instead of vanishing.
137137

0 commit comments

Comments
 (0)