Skip to content

Commit cab970c

Browse files
committed
docs: reword every page to stand on its own
The pages were written for a linear read-through, so many refer to the reader's history on ANOTHER page -- "In Tools you returned a str and the result came back twice", "the input schema you met in Tools", "the same one you use in Testing", "So far every request has gone one way", "you already know". Most people arrive at reference docs from a search engine and read one page; for them those sentences are false and read as steps in a walkthrough they are not on. An audit of all 42 pages found 24 such sentences on 16 pages (26 pages were already clean). Each is rewritten to carry the SAME facts and, almost always, the SAME cross-link, with only the false claim about the reader's history removed: In [Tools] you returned a str and the result came back twice ... -> A tool that returns a plain str produces the result twice ... the TextContent you met in [Tools] -> the TextContent a plain str result becomes ([Tools]) You saw this in [Tools] with Field(le=50). -> [Tools] shows the same rejection with a Field(le=50) constraint. Cross-REFERENCES are deliberately untouched: routing the reader elsewhere for MORE ("the full addressing syntax is on [URI templates]") is what good reference docs do. Only a sentence that DEPENDS on another page to make sense is the bug. Every rewritten factual claim was re-verified against the SDK source, not against the docs. Also: - The word "chapter" becomes "page" everywhere (27 sites): a book has chapters, a reference has pages. - The landing page's "Where to go next" gains the two audience routes it was missing: someone building a CLIENT, and someone adding MCP to an app they already run. The README routes both; the docs did not. - migration.md's Tasks note is corrected. It said Tasks "are expected to return as a separate MCP extension in a future release"; the 2026-07-28 revision reintroduces them as SEP-2663 (io.modelcontextprotocol/tasks), redesigned around polling. This SDK does not implement the extension yet, and the note now says so.
1 parent 688b8a7 commit cab970c

23 files changed

Lines changed: 50 additions & 48 deletions

docs/advanced/low-level-server.md

Lines changed: 4 additions & 4 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](../servers/tools.md)** (the nine-line `@mcp.tool()` file) with the sugar removed:
15+
This is the `search_books` tool that **[Tools](../servers/tools.md)** writes in nine lines of `@mcp.tool()`, with the sugar removed:
1616

1717
```python title="server.py" hl_lines="23 27 33"
1818
--8<-- "docs_src/lowlevel/tutorial001.py"
@@ -56,12 +56,12 @@ asyncio.run(main())
5656

5757
The same text the `@mcp.tool()` version produced. Two honest differences:
5858

59-
* `result.structured_content` is `None`. The high-level server wrapped your `-> str` into `{"result": ...}`; here nobody builds what you didn't build.
59+
* `result.structured_content` is `None`. The high-level server wraps a `-> str` into `{"result": ...}` for you; here nobody builds what you didn't build.
6060
* `list_tools` returns the schema **you** typed, character for character. The high-level version had `"title": "Query"` on every property and a `"title": "search_booksArguments"` at the root: Pydantic artifacts. Down here, if it's on the wire, you put it there.
6161

6262
## Nothing is checked for you
6363

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.
64+
`MCPServer` rejects a bad argument before your function ever runs, validating the call against the schema it generated (**[Tools](../servers/tools.md)**).
6565

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

@@ -179,7 +179,7 @@ The handshake belongs to the runner. `server/discover`, `ping`, and every other
179179

180180
## The other handlers
181181

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

184184
* `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.

docs/advanced/pagination.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Every `list_*` method on `Client` (`list_tools`, `list_resources`, `list_resourc
4848

4949
Run its `main()` and it prints `100 resources`: ten pages of ten, stitched together by a loop that never knew there were ten pages.
5050

51-
This is the same loop **[The Client](../client/index.md)** chapter showed you, and it costs nothing against a server that doesn't page: `next_cursor` is `None` on the first response and the loop runs once.
51+
This is the same loop **[The Client](../client/index.md)** shows for every `list_*` verb, and it costs nothing against a server that doesn't page: `next_cursor` is `None` on the first response and the loop runs once.
5252

5353
## The three rules
5454

docs/client/callbacks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Client callbacks
22

3-
So far every request has gone one way: client to server.
3+
Nearly every request in MCP goes one way: client to server.
44

55
A server can also ask the **client** for things: to put a question to the user, to sample the user's model, to list the user's workspace folders. You answer those requests by passing **callbacks** to `Client(...)`.
66

@@ -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](../handlers/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)** page owns it. This page is the other end of the wire.
1919

2020
## The elicitation callback
2121

docs/client/identity-assertion.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Identity assertion
22

3-
Every provider in **[OAuth clients](oauth-clients.md)** starts by asking the MCP server a question: *which authorization server do you trust?* It follows the answer wherever it points, and then either a person signs in or a pre-shared secret stands in for one.
3+
An ordinary OAuth provider (**[OAuth clients](oauth-clients.md)**) starts by asking the MCP server a question: *which authorization server do you trust?* It follows the answer wherever it points, and then either a person signs in or a pre-shared secret stands in for one.
44

55
An enterprise wants neither decided per server. It already runs an identity provider (Okta, Microsoft Entra ID, your own); the user already signed in to it this morning; and it is the one place the security team wants to decide who may reach what. [SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990), the **Enterprise-Managed Authorization** extension, moves the decision there. The IdP signs a short-lived JWT, an **Identity Assertion JWT Authorization Grant**, the **ID-JAG**: a statement that *this user*, through *this client*, may reach *this MCP server*. The client trades it for an ordinary access token. No browser, no consent screen, no dynamic registration.
66

7-
This chapter is both ends of that trade. The MCP server itself never changes: it is still the resource server from **[Authorization](../run/authorization.md)**, checking whatever token shows up.
7+
This page is both ends of that trade. The MCP server itself never changes: it is still the resource server from **[Authorization](../run/authorization.md)**, checking whatever token shows up.
88

99
## Two token requests
1010

11-
Two different authorities are in play, and naming them apart is most of understanding this page. The **enterprise IdP** is your organization's identity provider: it knows who the employee is, it is where policy lives, and it issues the ID-JAG. The SDK never talks to it. The **MCP authorization server** is the same party it was in **[Authorization](../run/authorization.md)**: the issuer named in the MCP server's metadata, the thing that mints the tokens that MCP server accepts. In the flows you already know, those two roles are usually one box. Here they are two, and the whole grant is the second agreeing to trust the first.
11+
Two different authorities are in play, and naming them apart is most of understanding this page. The **enterprise IdP** is your organization's identity provider: it knows who the employee is, it is where policy lives, and it issues the ID-JAG. The SDK never talks to it. The **MCP authorization server** is the same party it was in **[Authorization](../run/authorization.md)**: the issuer named in the MCP server's metadata, the thing that mints the tokens that MCP server accepts. In an ordinary OAuth flow, those two roles are usually one box. Here they are two, and the whole grant is the second agreeing to trust the first.
1212

1313
The client makes one token request to each.
1414

@@ -27,7 +27,7 @@ Everything below is the second request: the client that sends it and the authori
2727

2828
Read it from the bottom.
2929

30-
* `main()` is the `main()` from **[OAuth clients](oauth-clients.md)**, line for line. That is the point: once the provider exists, nothing downstream knows which grant produced the token.
30+
* `main()` is the standard OAuth-client `main()` (**[OAuth clients](oauth-clients.md)**), unchanged line for line. That is the point: once the provider exists, nothing downstream knows which grant produced the token.
3131
* The provider takes what the other providers cannot discover: a `client_id` and `client_secret` somebody **pre-registered** with the authorization server, that authorization server's `issuer`, and `assertion_provider`, an async callback that returns a fresh ID-JAG on demand.
3232
* `storage` is the same `TokenStorage` protocol. Only the two token methods are ever called; there is no dynamic registration here, so there is no `client_info` to remember.
3333

docs/client/index.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ It is one object with one lifecycle: construct it, enter `async with`, call meth
1212

1313
The server at the top is only there so you have something to connect to. The client is the five highlighted lines.
1414

15-
* `Client(mcp)` is given the **server object itself**. That is the in-memory transport: no subprocess, no port, no HTTP. It is how every example in this chapter, and every test you write, connects.
15+
* `Client(mcp)` is given the **server object itself**. That is the in-memory transport: no subprocess, no port, no HTTP. It is how every example on this page, and every test you write, connects.
1616
* `async with` is the **lifecycle**. Entering it connects and negotiates; leaving it disconnects. There is no `connect()` / `close()` pair, and a `Client` cannot be reused after the block ends.
1717
* Inside the block the connection facts are already there as plain properties.
1818

@@ -24,7 +24,7 @@ The server at the top is only there so you have something to connect to. The cli
2424
* A URL string (`Client("http://localhost:8000/mcp")`): Streamable HTTP, the production path.
2525
* A **transport**: anything you can `async with ... as (read, write)`, such as `stdio_client(...)` wrapping a subprocess.
2626

27-
Everything else on this page is identical across all three. Headers, subprocesses, timeouts, and the `Transport` protocol get their own chapter: **[Client transports](transports.md)**.
27+
Everything else on this page is identical across all three. Headers, subprocesses, timeouts, and the `Transport` protocol get their own page: **[Client transports](transports.md)**.
2828

2929
### What's on a connected client
3030

@@ -104,7 +104,7 @@ That is why `main` narrows with `isinstance(block, TextContent)` before touching
104104

105105
`structured_content` is the tool's return value as JSON, matching the tool's declared `output_schema`. No string parsing, no guessing.
106106

107-
When both are present they say the same thing twice on purpose: `content` is for a model, `structured_content` is for code. Where the structured half comes from, and how to control it, is the **[Structured Output](../servers/structured-output.md)** chapter.
107+
When both are present they say the same thing twice on purpose: `content` is for a model, `structured_content` is for code. Where the structured half comes from, and how to control it, is the **[Structured Output](../servers/structured-output.md)** page.
108108

109109
### `is_error`: whether the tool failed
110110

@@ -181,7 +181,7 @@ A server with a completion handler can autocomplete prompt and resource-template
181181
* `ref` says *which* prompt or template you're filling in: a `PromptReference` or a `ResourceTemplateReference`.
182182
* `argument` is `{"name": ..., "value": ...}`: the argument and what the user has typed so far.
183183

184-
The answer is in `result.completion.values`. Type `"p"` and the server comes back with `['poetry']`. The server side, and how a handler uses the *other* already-filled arguments to narrow its suggestions, is the **[Completions](../servers/completions.md)** chapter.
184+
The answer is in `result.completion.values`. Type `"p"` and the server comes back with `['poetry']`. The server side, and how a handler uses the *other* already-filled arguments to narrow its suggestions, is the **[Completions](../servers/completions.md)** page.
185185

186186
## Pagination
187187

@@ -197,7 +197,7 @@ This loop is correct against every server. `MCPServer` returns everything in one
197197

198198
`Client(mcp)` with no process and no port is already a test harness for your server.
199199

200-
There is one constructor flag built for that: `Client(mcp, raise_exceptions=True)`. It only has an effect on in-memory connections, and **[Testing](../get-started/testing.md)** is the chapter that explains it and builds the whole pattern around it.
200+
There is one constructor flag built for that: `Client(mcp, raise_exceptions=True)`. It only has an effect on in-memory connections, and **[Testing](../get-started/testing.md)** is the page that explains it and builds the whole pattern around it.
201201

202202
## Recap
203203

docs/client/oauth-clients.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Some MCP servers are protected. Send them a request without a token and they ans
44

55
**`OAuthClientProvider`** is how you get the token. It is not an MCP object at all. It is an `httpx.Auth`, the standard httpx hook for "do something to every request". You attach it to an `httpx.AsyncClient`, hand that client to the Streamable HTTP transport, and stop thinking about it.
66

7-
This chapter is the client side. Making your own server demand a token is **[Authorization](../run/authorization.md)**.
7+
This page is the client side. Making your own server demand a token is **[Authorization](../run/authorization.md)**.
88

99
## The provider
1010

@@ -87,9 +87,9 @@ You wrote none of it. Three keyword arguments remain (`timeout`, `client_metadat
8787

8888
### Try it
8989

90-
Everything else in these docs you have checked with an in-memory `Client(server)`. Not this: the whole point of the flow is an HTTP `401`, and there is no HTTP between an in-memory client and its server.
90+
Every other example in these docs you can check with an in-memory `Client(server)`. Not this: the whole point of the flow is an HTTP `401`, and there is no HTTP between an in-memory client and its server.
9191

92-
The repository ships the live version. `examples/servers/simple-auth/` runs a standalone authorization server and a protected MCP server; `examples/clients/simple-auth-client/` is this chapter's client grown into a small CLI. Its README has the two commands: start the servers, run the client against them, and you watch the four steps go by.
92+
The repository ships the live version. `examples/servers/simple-auth/` runs a standalone authorization server and a protected MCP server; `examples/clients/simple-auth-client/` is this page's client grown into a small CLI. Its README has the two commands: start the servers, run the client against them, and you watch the four steps go by.
9393

9494
## Machine to machine
9595

@@ -119,7 +119,7 @@ By default the secret travels as HTTP Basic auth on the token request (`client_s
119119
the same pattern: construct one, put it on `auth=`. The same module ships
120120
`SignedJWTParameters` and `static_assertion_provider`, two helpers that build its assertion.
121121

122-
There is one more no-human situation: the client belongs to an enterprise whose identity provider, not the user, decides which MCP servers it may reach. That is a different grant with its own trust model and its own chapter, **[Identity assertion](identity-assertion.md)**.
122+
There is one more no-human situation: the client belongs to an enterprise whose identity provider, not the user, decides which MCP servers it may reach. That is a different grant with its own trust model and its own page, **[Identity assertion](identity-assertion.md)**.
123123

124124
## When it fails
125125

docs/client/transports.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ No subprocess, no port, no bytes on a wire. The client and the server are two ob
1818

1919
That makes it two things at once:
2020

21-
* **A test harness.** Every example in this documentation is exercised this way, and the **[Testing](../get-started/testing.md)** chapter builds the whole pattern around it.
21+
* **A test harness.** Every example in this documentation is exercised this way, and the **[Testing](../get-started/testing.md)** page builds the whole pattern around it.
2222
* **An embedding API.** An application that constructs the server doesn't need a network hop to call its tools.
2323

2424
## Streamable HTTP

docs/get-started/first-steps.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# First steps
22

3-
On the landing page you wrote a server, ran it, and called a tool.
3+
The **[landing page](../index.md)** moves fast: write a server, run it, call a tool.
44

5-
Now do it again, slowly, with all three things a server can expose, and the names for everything you just saw.
5+
This page takes it slowly, with all three things a server can expose, and a name for everything along the way.
66

77
## Host, client, and server
88

@@ -12,7 +12,7 @@ Three words you'll see on every page from here on:
1212
* A **client** lives inside the host and speaks MCP. The host runs one client per server it's connected to.
1313
* A **server** is what you build with this SDK. It exposes things to clients. It never talks to the model directly.
1414

15-
You write the server. Hosts are someone else's product. The SDK also gives you a `Client`. You'll use it to test your servers, and it shows up later in this chapter.
15+
You write the server. Hosts are someone else's product. The SDK also gives you a `Client`. You'll use it to test your servers, and it shows up later on this page.
1616

1717
## The three primitives
1818

@@ -70,7 +70,7 @@ Hello, World!
7070

7171
**Prompts.** One entry: `summarize`, with a single required `text` argument. Get it with some text and you receive one message with `role: user` and your rendered string as the content. That's all a prompt is: a function that builds messages.
7272

73-
The Inspector ran your server over **stdio**, one of the transports an MCP server can speak. You don't pick one yet; **[Running your server](../run/index.md)** is the chapter for that.
73+
The Inspector ran your server over **stdio**, one of the transports an MCP server can speak. You don't pick one yet; **[Running your server](../run/index.md)** is the page for that.
7474

7575
## Capabilities
7676

docs/get-started/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Two different things can go wrong, and this flag only touches one of them.
7878

7979
An exception inside one of **your tools** is not a protocol failure. It becomes a normal result with
8080
`is_error=True`, and the model reads the message. `raise_exceptions` doesn't change that: with or
81-
without it, `call_tool` returns the same `is_error=True` result. There's a whole chapter on it:
81+
without it, `call_tool` returns the same `is_error=True` result. There's a whole page on it:
8282
**[Handling errors](../servers/handling-errors.md)**.
8383

8484
A failure **outside** a tool body is different. On the connection `Client(mcp)` gives you, the

docs/handlers/context.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ The injected object is small. Besides `request_id`:
6666
* `ctx.headers`: the request headers the transport carried, or `None` on stdio. Read a custom header with `(ctx.headers or {}).get("x-...")`. Headers are client-supplied input - fine for a locale or a feature flag, never an identity.
6767
* `ctx.request_context`: the raw per-request record. The field you'll reach for is `lifespan_context`, the object your startup code yielded (see **[Lifespan](lifespan.md)**).
6868

69-
Logging is deliberately not on that list. A server logs with Python's `logging` module, like any other Python program. **[Logging](logging.md)** is the short chapter on why.
69+
Logging is deliberately not on that list. A server logs with Python's `logging` module, like any other Python program. **[Logging](logging.md)** is the short page on why.
7070

7171
!!! tip
7272
Injection only happens for the function you registered. A helper that your tool calls doesn't get
@@ -124,6 +124,6 @@ On a 2026-07-28 connection, clients receive change notifications only on a `subs
124124
* `ctx.request_id` identifies the request; `ctx.request_context.lifespan_context` is what your startup yielded.
125125
* `await ctx.read_resource(uri)` lets a tool read the server's own resources.
126126
* `ctx.session` is the channel back to the client: `send_tool_list_changed()` and its siblings tell it to re-fetch a list you changed.
127-
* Progress reporting and elicitation also start at `Context`; each has its own chapter.
127+
* Progress reporting and elicitation also start at `Context`; each has its own page.
128128

129129
Next: parameters the model never sees, filled by your own functions, in **[Dependencies](dependencies.md)**.

0 commit comments

Comments
 (0)