docs: convert bold cross-references into links, link SEP and RFC mentions (#3017)

This commit is contained in:
Max 2026-06-29 13:07:26 +02:00 • committed by GitHub
commit 24fdd909ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -12,7 +12,7 @@ For everything else, stay on `MCPServer`.
## The same tool, by hand
This is `search_books` from **Tools** (the nine-line `@mcp.tool()` file) with the sugar removed:
This is `search_books` from **[Tools](../tutorial/tools.md)** (the nine-line `@mcp.tool()` file) with the sugar removed:
```python title="server.py" hl_lines="23 27 33"
--8<-- "docs_src/lowlevel/tutorial001.py"
@ -61,7 +61,7 @@ The same text the `@mcp.tool()` version produced. Two honest differences:
## Nothing is checked for you
In **Tools** you saw a bad argument get rejected before your function ran. That was `MCPServer` validating the call against the schema it generated.
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.
`Server` does not do that. Your `input_schema` is *advertised* to the client; it is never *applied* to `params.arguments`.
@ -72,9 +72,9 @@ In **Tools** you saw a bad argument get rejected before your function ran. That
MCPError: Internal server error
```
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**.)
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)**.)
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**.
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)**.
## Two tools, one handler
@ -106,7 +106,7 @@ Call it and the result carries both representations:
}
```
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**.
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)**.
## `_meta`: for the application, not the model
@ -147,7 +147,7 @@ No `resources`, no `prompts`: there is nothing to back them. Pass `on_list_promp
* The lifespan is a `Callable[[Server[Catalog]], AbstractAsyncContextManager[Catalog]]`; `@asynccontextmanager` on an `async` generator gives you exactly that.
* Whatever it `yield`s becomes `ctx.lifespan_context`, and because the handlers are annotated `ServerRequestContext[Catalog]`, `.search(...)` autocompletes and type-checks.
* 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**.
* 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)**.
Without a `lifespan=`, `ctx.lifespan_context` is an empty `dict`.
@ -175,15 +175,15 @@ use Server.middleware to observe or wrap initialization
The handshake belongs to the runner. `server/discover`, `ping`, and every other built-in are yours to replace.
!!! tip
`Server.middleware`, mentioned in that error, wraps **every** inbound message, including `initialize`. If what you want is to observe or rewrite traffic rather than answer a new method, start at **Middleware**.
`Server.middleware`, mentioned in that error, wraps **every** inbound message, including `initialize`. If what you want is to observe or rewrite traffic rather than answer a new method, start at **[Middleware](middleware.md)**.
## The other handlers
Each of these is one idea you now have the vocabulary for; each has its own chapter.
* `on_call_tool` may return an `InputRequiredResult` instead of a `CallToolResult` to pause the call and ask the client for input; see **Multi-round-trip requests**.
* `on_call_tool` may return an `InputRequiredResult` instead of a `CallToolResult` to pause the call and ask the client for input; see **[Multi-round-trip requests](multi-round-trip.md)**.
* `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.
* `server.streamable_http_app()` returns the same Starlette app `MCPServer`'s does; deploy it the way **Running your server** 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.
* `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.
## Recap
@ -195,4 +195,4 @@ Each of these is one idea you now have the vocabulary for; each has its own chap
* `add_request_handler(method, params_type, handler)` serves any method. `initialize` is reserved.
* The capabilities a `Server` advertises are derived from which handlers you registered.
`Client(server)` treated both servers identically because they *are* the same protocol, which is the whole point. The next layer down isn't a class at all: it's **Middleware**.
`Client(server)` treated both servers identically because they *are* the same protocol, which is the whole point. The next layer down isn't a class at all: it's **[Middleware](middleware.md)**.