You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
Copy file name to clipboardExpand all lines: docs/advanced/low-level-server.md
+8-8Lines changed: 8 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ For everything else, stay on `MCPServer`.
12
12
13
13
## The same tool, by hand
14
14
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:
16
16
17
17
```python title="server.py" hl_lines="23 27 33"
18
18
--8<--"docs_src/lowlevel/tutorial001.py"
@@ -61,7 +61,7 @@ The same text the `@mcp.tool()` version produced. Two honest differences:
61
61
62
62
## Nothing is checked for you
63
63
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.
65
65
66
66
`Server` does not do that. Your `input_schema` is *advertised* to the client; it is never *applied* to `params.arguments`.
67
67
@@ -72,9 +72,9 @@ In **[Tools](../tutorial/tools.md)** you saw a bad argument get rejected before
72
72
MCPError: Internal server error
73
73
```
74
74
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)**.)
76
76
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)**.
78
78
79
79
## Two tools, one handler
80
80
@@ -106,7 +106,7 @@ Call it and the result carries both representations:
106
106
}
107
107
```
108
108
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)**.
110
110
111
111
## `_meta`: for the application, not the model
112
112
@@ -147,7 +147,7 @@ No `resources`, no `prompts`: there is nothing to back them. Pass `on_list_promp
147
147
148
148
* The lifespan is a `Callable[[Server[Catalog]], AbstractAsyncContextManager[Catalog]]`; `@asynccontextmanager` on an `async` generator gives you exactly that.
149
149
* 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)**.
151
151
152
152
Without a `lifespan=`, `ctx.lifespan_context` is an empty `dict`.
153
153
@@ -181,9 +181,9 @@ The handshake belongs to the runner. `server/discover`, `ping`, and every other
181
181
182
182
Each of these is one idea you now have the vocabulary for; each has its own chapter.
183
183
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)**).
185
185
*`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.
187
187
*`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.
Copy file name to clipboardExpand all lines: docs/client/caching.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -85,7 +85,7 @@ Cache keys also carry the **server's identity**: the URL string you dialed, with
85
85
### What the cache never does
86
86
87
87
***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.
89
89
***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.
90
90
***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).
91
91
***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.
Copy file name to clipboardExpand all lines: docs/client/callbacks.md
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,7 +15,7 @@ Here is a server whose tool can't finish on its own:
15
15
*`ctx.elicit(...)` sends an `elicitation/create` request **to the client** and waits.
16
16
* The tool doesn't return until somebody (a person in a form, or your code) supplies a `name`.
17
17
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.
19
19
20
20
## The elicitation callback
21
21
@@ -31,7 +31,7 @@ That is the server half, and the **[Elicitation](../tutorial/elicitation.md)** c
31
31
!!! tip
32
32
`params` is a union of the two elicitation modes. Here `params.mode` is `"form"`; a `"url"` request
33
33
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.
35
35
36
36
### Try it
37
37
@@ -59,11 +59,11 @@ One `tools/call` from you, one `elicitation/create` back from the server, answer
59
59
protocol path, and that path has no back-channel for server-to-client requests: `ctx.elicit`
60
60
fails before your callback ever runs. The transport doesn't decide that; the negotiated
61
61
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.
63
63
64
64
On a 2026-07-28 session the callback isn't dead, it's fed differently: when a tool returns an
65
65
`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)**.
67
67
68
68
## A callback is a capability
69
69
@@ -113,7 +113,7 @@ Pass all three callbacks and you get `['elicitation', 'sampling', 'roots']`. Pas
113
113
114
114
`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.
115
115
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)**.
117
117
118
118
You still need the callbacks to talk to servers that haven't moved. The signatures:
119
119
@@ -131,7 +131,7 @@ Pass them to `Client(...)` exactly like `elicitation_callback`.
131
131
132
132
Two more. Neither declares anything.
133
133
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.
135
135
136
136
`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.
0 commit comments