docs: convert bold cross-references into links, link SEP and RFC mentions (#3017)
This commit is contained in:
parent
f2e63c979a
commit
24fdd909ac
15 changed files with 167 additions and 167 deletions
|
|
@ -39,7 +39,7 @@ Add **one** function decorated with `@mcp.completion()`:
|
|||
|
||||
### Try it
|
||||
|
||||
Drive it with the in-memory `Client`, the same one you use in **Testing**. Call
|
||||
Drive it with the in-memory `Client`, the same one you use in **[Testing](testing.md)**. Call
|
||||
`client.complete()` with `ref=PromptReference(name="review_code")` and
|
||||
`argument={"name": "language", "value": "py"}`:
|
||||
|
||||
|
|
@ -122,4 +122,4 @@ Drop `context_arguments=` and the same call returns `[]`. The handler can't know
|
|||
* `context.arguments` holds the already-resolved values; the client supplies them as `context_arguments=`.
|
||||
* The `completions` capability appears the moment you register the handler. Without it, the request is `Method not found`.
|
||||
|
||||
Suggestions help *before* a tool runs. To ask the user a question in the *middle* of one, you want **Elicitation**.
|
||||
Suggestions help *before* a tool runs. To ask the user a question in the *middle* of one, you want **[Elicitation](elicitation.md)**.
|
||||
|
|
|
|||
|
|
@ -60,13 +60,13 @@ The number is whichever request this happened to be. Call the tool again and it
|
|||
The injected object is small. Besides `request_id`:
|
||||
|
||||
* `await ctx.read_resource(uri)`: read one of the server's **own** resources from inside a tool. The next section.
|
||||
* `await ctx.report_progress(progress, total, message)`: stream progress back to the caller during a long call. The whole story is in **Progress**.
|
||||
* `await ctx.elicit(message, schema)` and `await ctx.elicit_url(...)`: pause the tool and ask the user a question. That's **Elicitation**.
|
||||
* `await ctx.report_progress(progress, total, message)`: stream progress back to the caller during a long call. The whole story is in **[Progress](progress.md)**.
|
||||
* `await ctx.elicit(message, schema)` and `await ctx.elicit_url(...)`: pause the tool and ask the user a question. That's **[Elicitation](elicitation.md)**.
|
||||
* `ctx.session`: the server's side of the conversation with this client. Notifications you send to the client live here; the last section uses it.
|
||||
* `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.
|
||||
* `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**).
|
||||
* `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)**).
|
||||
|
||||
Logging is deliberately not on that list. A server logs with Python's `logging` module, like any other Python program. **Logging** is the short chapter on why.
|
||||
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.
|
||||
|
||||
!!! tip
|
||||
Injection only happens for the function you registered. A helper that your tool calls doesn't get
|
||||
|
|
@ -124,4 +124,4 @@ The siblings are `send_resource_list_changed()`, `send_prompt_list_changed()`, a
|
|||
* `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.
|
||||
* Progress reporting and elicitation also start at `Context`; each has its own chapter.
|
||||
|
||||
Next: parameters the model never sees, filled by your own functions, in **Dependencies**.
|
||||
Next: parameters the model never sees, filled by your own functions, in **[Dependencies](dependencies.md)**.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Here is the input schema `tools/list` reports for `reserve_book`:
|
|||
}
|
||||
```
|
||||
|
||||
One property. Like the `Context` in **The Context**, a resolved parameter is a contract between you and the SDK: `stock` is not in the schema, the model is never told about it, and a client that sends a `stock` value anyway is ignored. The resolver's value is the only one your tool can receive.
|
||||
One property. Like the `Context` in **[The Context](context.md)**, a resolved parameter is a contract between you and the SDK: `stock` is not in the schema, the model is never told about it, and a client that sends a `stock` value anyway is ignored. The resolver's value is the only one your tool can receive.
|
||||
|
||||
That last part is the point. A parameter the model cannot supply is a parameter the model cannot get wrong.
|
||||
|
||||
|
|
@ -84,16 +84,16 @@ A resolver's parameters resolve exactly like a tool's: another `Resolve(...)`, t
|
|||
!!! warning
|
||||
On HTTP transports the `Context` includes `ctx.headers`. Headers are **client-supplied input**,
|
||||
like any tool argument: fine for a locale or a feature flag, never an identity. Who the caller
|
||||
is comes from your authorization layer (**Authorization**), not from a header anyone can set.
|
||||
is comes from your authorization layer (**[Authorization](../advanced/authorization.md)**), not from a header anyone can set.
|
||||
|
||||
!!! tip
|
||||
*Once per call* means exactly that: the next `tools/call` runs `check_stock` again. A resource
|
||||
that should outlive a request - a database pool, an HTTP client - belongs in **Lifespan**, and
|
||||
that should outlive a request - a database pool, an HTTP client - belongs in **[Lifespan](lifespan.md)**, and
|
||||
a resolver can reach it through `ctx.request_context.lifespan_context`.
|
||||
|
||||
## Ask when you must
|
||||
|
||||
A resolver doesn't have to know the answer. It can return `Elicit(message, Model)` and the SDK asks the user - the **Elicitation** machinery, run for you:
|
||||
A resolver doesn't have to know the answer. It can return `Elicit(message, Model)` and the SDK asks the user - the **[Elicitation](elicitation.md)** machinery, run for you:
|
||||
|
||||
```python title="server.py" hl_lines="26-32 39"
|
||||
--8<-- "docs_src/dependencies/tutorial003.py"
|
||||
|
|
@ -114,7 +114,7 @@ And if the user won't answer at all - declines the question, or cancels it?
|
|||
Error executing tool order_book: Resolver for parameter 'backorder' could not resolve: elicitation was decline
|
||||
```
|
||||
|
||||
That's the right default for a precondition: no answer, no order. When declining is an outcome your tool wants to handle - skip the backorder but still suggest another title - annotate `ElicitationResult[Backorder]` instead and the tool receives the full accept/decline/cancel outcome to branch on. **Elicitation** shows that form, and everything else about asking: the schema rules, the three answers, the client's side of the conversation.
|
||||
That's the right default for a precondition: no answer, no order. When declining is an outcome your tool wants to handle - skip the backorder but still suggest another title - annotate `ElicitationResult[Backorder]` instead and the tool receives the full accept/decline/cancel outcome to branch on. **[Elicitation](elicitation.md)** shows that form, and everything else about asking: the schema rules, the three answers, the client's side of the conversation.
|
||||
|
||||
## Recap
|
||||
|
||||
|
|
@ -124,4 +124,4 @@ That's the right default for a precondition: no answer, no order. When declining
|
|||
* Bad graphs fail at registration with `InvalidSignature`, not mid-call.
|
||||
* Return `Elicit(message, Model)` to ask the user, only when you have to. Unwrapped annotations abort on decline; `ElicitationResult[T]` lets the tool branch.
|
||||
|
||||
Next: what happens when your tool fails, and how to choose who finds out, in **Handling errors**.
|
||||
Next: what happens when your tool fails, and how to choose who finds out, in **[Handling errors](handling-errors.md)**.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ There are two modes:
|
|||
--8<-- "docs_src/elicitation/tutorial001.py"
|
||||
```
|
||||
|
||||
* The **`Context`** parameter is what gives you `ctx.elicit`; any tool can take one. That object has its own chapter: **The Context**.
|
||||
* The **`Context`** parameter is what gives you `ctx.elicit`; any tool can take one. That object has its own chapter: **[The Context](context.md)**.
|
||||
* `AlternativeDate` is the **schema** of the answer you want.
|
||||
* The tool is `async def`. It has to be: it stops in the middle and waits for a person.
|
||||
* On any other date the tool returns straight away. It only asks when it has to.
|
||||
|
|
@ -48,7 +48,7 @@ The client gets your message and, next to it, a JSON Schema generated from the m
|
|||
}
|
||||
```
|
||||
|
||||
That schema is the form. `Field(description=...)` is the label; a default pre-fills the input and makes the field optional. It's the same Pydantic-to-JSON-Schema machinery you already used for a tool's arguments in **Tools**.
|
||||
That schema is the form. `Field(description=...)` is the label; a default pre-fills the input and makes the field optional. It's the same Pydantic-to-JSON-Schema machinery you already used for a tool's arguments in **[Tools](tools.md)**.
|
||||
|
||||
!!! warning
|
||||
An elicitation schema is not as expressive as a tool's input schema. Flat, primitive fields
|
||||
|
|
@ -95,7 +95,7 @@ A parameter annotated `Annotated[T, Resolve(fn)]` is filled by running `fn` befo
|
|||
|
||||
Annotate the unwrapped model (`Annotated[Confirm, Resolve(confirm_delete)]`) instead when the tool doesn't need to branch: it receives the model on accept and the call aborts with an error on decline or cancel.
|
||||
|
||||
Asking is only one thing a resolver can do. The general mechanism - dependencies that compute without asking, dependencies of dependencies, what the model can and cannot supply - is the **Dependencies** chapter.
|
||||
Asking is only one thing a resolver can do. The general mechanism - dependencies that compute without asking, dependencies of dependencies, what the model can and cannot supply - is the **[Dependencies](dependencies.md)** chapter.
|
||||
|
||||
## Send the user to a URL
|
||||
|
||||
|
|
@ -122,17 +122,17 @@ Servers ask. Clients answer by passing an **`elicitation_callback`** to `Client(
|
|||
* One callback handles both modes. `params` is a union of `ElicitRequestFormParams` and `ElicitRequestURLParams`; `isinstance` is the branch.
|
||||
* For a URL, you show `params.url` to the user and return the action they chose. Never any `content`.
|
||||
* For a form, a real application renders `params.requested_schema` and returns the user's input as `content`. This one always says yes with a canned answer, which is exactly the callback you want in a test.
|
||||
* Passing the callback is also the **capability declaration**: it's how the server learns this client can be asked. The other things a client can answer for a server live in **Client callbacks**.
|
||||
* Passing the callback is also the **capability declaration**: it's how the server learns this client can be asked. The other things a client can answer for a server live in **[Client callbacks](../client/callbacks.md)**.
|
||||
|
||||
!!! info
|
||||
Elicitation is a request from the *server* to the *client*, and those only exist on a
|
||||
classic-handshake session, which is why this client passes `mode="legacy"`.
|
||||
On a **2026-07-28** connection a tool asks by *returning* the question from the call
|
||||
instead; that flow is **Multi-round-trip requests**.
|
||||
instead; that flow is **[Multi-round-trip requests](../advanced/multi-round-trip.md)**.
|
||||
|
||||
### Try it
|
||||
|
||||
Start the form-mode `server.py` (the first one on this page) on Streamable HTTP (**Running your server** has the one-liner), then run the client's `main()` and ask `book_table` for Christmas day.
|
||||
Start the form-mode `server.py` (the first one on this page) on Streamable HTTP (**[Running your server](../run/index.md)** has the one-liner), then run the client's `main()` and ask `book_table` for Christmas day.
|
||||
|
||||
The callback prints the question it was sent:
|
||||
|
||||
|
|
@ -167,6 +167,6 @@ Now swap in the URL-mode `server.py` and point the same `main()` at `pay_deposit
|
|||
* `result.action` is `"accept"`, `"decline"` or `"cancel"`; `result.data` exists only on accept.
|
||||
* `await ctx.elicit_url(message, url, elicitation_id)` is for everything that must not pass through the model; `ctx.session.send_elicit_complete(elicitation_id)` says the out-of-band part is done.
|
||||
* The client answers with one `elicitation_callback`, branching on the params type; registering it is what declares the capability.
|
||||
* On a 2026-07-28 connection the server returns the question instead of pushing it; the same callback is fed by **Multi-round-trip requests**.
|
||||
* On a 2026-07-28 connection the server returns the question instead of pushing it; the same callback is fed by **[Multi-round-trip requests](../advanced/multi-round-trip.md)**.
|
||||
|
||||
A tool that can ask is good. A tool that says how far along it is (**Progress**) is next.
|
||||
A tool that can ask is good. A tool that says how far along it is (**[Progress](progress.md)**) is next.
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ Hello, World!
|
|||
|
||||
**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.
|
||||
|
||||
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** is the chapter for that.
|
||||
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.
|
||||
|
||||
## Capabilities
|
||||
|
||||
|
|
@ -110,11 +110,11 @@ That dictionary is the server's half of the handshake:
|
|||
|
||||
`MCPServer` serves all three primitives, so all three are always declared.
|
||||
|
||||
Notice what isn't there. `completions` (argument autocomplete for resource templates and prompts) needs a handler you write, this server doesn't have one, so the capability is absent and a well-behaved client won't ask. That's the rule for everything optional: register the thing and the capability appears; **Completions** proves it.
|
||||
Notice what isn't there. `completions` (argument autocomplete for resource templates and prompts) needs a handler you write, this server doesn't have one, so the capability is absent and a well-behaved client won't ask. That's the rule for everything optional: register the thing and the capability appears; **[Completions](completions.md)** proves it.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` is the same in-memory client every example in this tutorial is tested with, and
|
||||
it's how you'll test yours. It gets a whole chapter: **Testing**.
|
||||
it's how you'll test yours. It gets a whole chapter: **[Testing](testing.md)**.
|
||||
|
||||
## What you did not write
|
||||
|
||||
|
|
@ -136,4 +136,4 @@ That ratio is the whole point of the SDK.
|
|||
* The server's **capabilities** are declared for you, and a client only asks for what a server declares.
|
||||
* `Client(mcp)` connects to the server object in memory: your test harness from day one.
|
||||
|
||||
Each primitive now gets its own chapter, starting with the one the model drives: **Tools**.
|
||||
Each primitive now gets its own chapter, starting with the one the model drives: **[Tools](tools.md)**.
|
||||
|
|
|
|||
|
|
@ -104,13 +104,13 @@ When it can't, raise `ResourceNotFoundError`. The SDK turns it into the protocol
|
|||
}
|
||||
```
|
||||
|
||||
Notice there is no `is_error=True` half-result here. A resource read either returns contents or fails: resources have only the protocol path. Templates and everything else about resources live in **Resources**.
|
||||
Notice there is no `is_error=True` half-result here. A resource read either returns contents or fails: resources have only the protocol path. Templates and everything else about resources live in **[Resources](resources.md)**.
|
||||
|
||||
## Errors you never raise
|
||||
|
||||
A bad argument never reaches your function.
|
||||
|
||||
Send `get_author` a `title` that isn't a string and the SDK rejects it against the input schema **before** calling you, as the same kind of `is_error=True` tool error the model can read and correct. You saw this in **Tools** with `Field(le=50)`.
|
||||
Send `get_author` a `title` that isn't a string and the SDK rejects it against the input schema **before** calling you, as the same kind of `is_error=True` tool error the model can read and correct. You saw this in **[Tools](tools.md)** with `Field(le=50)`.
|
||||
|
||||
It means a whole class of `raise` statements you don't write: don't re-validate your own type hints.
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ It means a whole class of `raise` statements you don't write: don't re-validate
|
|||
Everything on this page is what a **client** sees, and the in-memory `Client` you'll write
|
||||
tests with sees exactly the same thing. Even `raise_exceptions=True` doesn't turn a tool error
|
||||
back into a traceback: by the time that flag could act, your exception is already the
|
||||
`is_error=True` result. Assert on the result. **Testing** covers the pattern.
|
||||
`is_error=True` result. Assert on the result. **[Testing](testing.md)** covers the pattern.
|
||||
|
||||
## Recap
|
||||
|
||||
|
|
@ -129,4 +129,4 @@ It means a whole class of `raise` statements you don't write: don't re-validate
|
|||
* Bad arguments are rejected against the schema before your function runs; you don't `raise` for those.
|
||||
* `from mcp import MCPError`; the error-code constants come from `mcp_types`.
|
||||
|
||||
Errors handled. Next: the things your server sets up once, before the first call ever arrives, the **Lifespan**.
|
||||
Errors handled. Next: the things your server sets up once, before the first call ever arrives, the **[Lifespan](lifespan.md)**.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ Nothing new. `ctx` is a **Context** parameter, so the SDK injects it and it neve
|
|||
|
||||
`genre` is the only argument the model can pass. The lifespan is your server's business.
|
||||
|
||||
`@mcp.resource()` and `@mcp.prompt()` functions can take a `ctx` parameter too, written as a bare `Context` for a reason the next section gets to. Everything `ctx` carries is in **The Context**.
|
||||
`@mcp.resource()` and `@mcp.prompt()` functions can take a `ctx` parameter too, written as a bare `Context` for a reason the next section gets to. Everything `ctx` carries is in **[The Context](context.md)**.
|
||||
|
||||
### It really is typed
|
||||
|
||||
|
|
@ -99,4 +99,4 @@ Strip the server down to the lifecycle: give `Database` a `connected` flag, flip
|
|||
* `ctx: Context[AppContext]` makes that access fully typed in tools. Resources and prompts take the bare `Context`.
|
||||
* No `lifespan=` means an empty `dict`, never `None`.
|
||||
|
||||
Next: tools that return more than text, **Media**.
|
||||
Next: tools that return more than text, **[Media](media.md)**.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Log from a tool the way you log from any other Python function: with the standard library.
|
||||
|
||||
MCP has a protocol-level **logging capability**: a server could push its log messages to the client as notifications, through methods on the `Context` object. The 2026-07-28 revision of the spec **deprecates that capability and does not replace it**, so this tutorial doesn't teach it. The full list of what's deprecated and what to do instead is in **Deprecated features**.
|
||||
MCP has a protocol-level **logging capability**: a server could push its log messages to the client as notifications, through methods on the `Context` object. The 2026-07-28 revision of the spec **deprecates that capability and does not replace it**, so this tutorial doesn't teach it. The full list of what's deprecated and what to do instead is in **[Deprecated features](../advanced/deprecated.md)**.
|
||||
|
||||
What you do instead is what you do in every other Python program: the standard library.
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ went to standard error: the terminal, not the wire.
|
|||
!!! info
|
||||
If what you actually want is *tracing* (every request, how long it took, whether it failed), you
|
||||
don't want log lines, you want spans. Your server already emits them: the SDK traces every
|
||||
message with OpenTelemetry out of the box. See **OpenTelemetry**.
|
||||
message with OpenTelemetry out of the box. See **[OpenTelemetry](../advanced/opentelemetry.md)**.
|
||||
|
||||
## Recap
|
||||
|
||||
|
|
@ -75,4 +75,4 @@ went to standard error: the terminal, not the wire.
|
|||
* Standard error is yours; stdout belongs to the protocol. Never `print()` in a stdio server.
|
||||
* `MCPServer(..., log_level="DEBUG")` sets the level, and a logging configuration you made first is left alone.
|
||||
|
||||
Next: the in-memory client that has been running every example on these pages, and how to point it at your own server, in **Testing**.
|
||||
Next: the in-memory client that has been running every example on these pages, and how to point it at your own server, in **[Testing](testing.md)**.
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ result.structured_content # None
|
|||
Two things to notice:
|
||||
|
||||
* `data` is base64. You returned raw `bytes`; the SDK did the encoding.
|
||||
* `structured_content` is `None`. An `Image` is content for the model to look at, not data for the application to parse: there is no output schema. (Contrast **Structured Output**, where the return annotation *is* the schema.)
|
||||
* `structured_content` is `None`. An `Image` is content for the model to look at, not data for the application to parse: there is no output schema. (Contrast **[Structured Output](structured-output.md)**, where the return annotation *is* the schema.)
|
||||
|
||||
!!! info
|
||||
`ImageContent` and `AudioContent` live in `mcp_types`, right next to the `TextContent`
|
||||
you met in **Tools**. A tool result is a list of content blocks; `Image` and `Audio` are
|
||||
you met in **[Tools](tools.md)**. A tool result is a list of content blocks; `Image` and `Audio` are
|
||||
the shortest way to produce the two binary kinds.
|
||||
|
||||
### Try it
|
||||
|
|
@ -105,4 +105,4 @@ A tool's icons are on the `Tool` object from `tools/list`, a resource's on the `
|
|||
* An `Icon` is a pointer: a `src` URI plus optional `mime_type`, `sizes`, and `theme`.
|
||||
* `icons=[...]` works on the server, on tools, on resources, and on prompts, and clients find them on the matching objects.
|
||||
|
||||
That is everything a tool can put *into* a result. Helping the user fill in a prompt's or a resource template's arguments *before* anything runs is **Completions**.
|
||||
That is everything a tool can put *into* a result. Helping the user fill in a prompt's or a resource template's arguments *before* anything runs is **[Completions](completions.md)**.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Three arguments, and you decide what they mean:
|
|||
* `total`: how much there is in total, if you know. Optional.
|
||||
* `message`: one human-readable line about *this* step. Optional.
|
||||
|
||||
`ctx` is injected because of its type hint and the model never sees it: `import_catalog`'s input schema has a single property, `urls`. **The Context** chapter is all about that object; progress is one of the things it gives you.
|
||||
`ctx` is injected because of its type hint and the model never sees it: `import_catalog`'s input schema has a single property, `urls`. **[The Context](context.md)** chapter is all about that object; progress is one of the things it gives you.
|
||||
|
||||
## Listen for it from the client
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ anyio.run(main)
|
|||
The callback is an `async` function taking exactly what the server reported: `progress`, `total`, `message`.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` connects straight to the server object, in memory, the same client the **Testing**
|
||||
`Client(mcp)` connects straight to the server object, in memory, the same client the **[Testing](testing.md)**
|
||||
chapter is built on. `progress_callback` is the same parameter whatever transport the `Client`
|
||||
uses; the *timing* you are about to see is the in-memory connection's. It runs your callback
|
||||
inline, so every report lands before `call_tool` returns. Over a real transport the
|
||||
|
|
@ -114,4 +114,4 @@ The callback receives `total=None`. A client can still show *activity* ("3 impor
|
|||
* No callback on the call means `report_progress` does nothing. Report unconditionally.
|
||||
* Omit `total` when you don't know it; the callback gets `None`.
|
||||
|
||||
Progress is what a running tool shows the *user*. The lines it logs for *you*, the person operating the server, are a different channel: **Logging** is next.
|
||||
Progress is what a running tool shows the *user*. The lines it logs for *you*, the person operating the server, are a different channel: **[Logging](logging.md)** is next.
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ Notice the last one. Pre-filling an `assistant` turn is how you steer the model'
|
|||
```
|
||||
|
||||
* `title="Code review"` is the human-readable name, exactly like a tool's `title`.
|
||||
* `Annotated[str, Field(description=...)]` is the same pattern you used in **Tools**. Here the description lands on the argument instead of in a schema.
|
||||
* `Annotated[str, Field(description=...)]` is the same pattern you used in **[Tools](tools.md)**. Here the description lands on the argument instead of in a schema.
|
||||
* `language` has a default, so it stops being required.
|
||||
|
||||
The `prompts/list` entry now carries everything a client needs to draw a good form:
|
||||
|
|
@ -134,7 +134,7 @@ The `prompts/list` entry now carries everything a client needs to draw a good fo
|
|||
```
|
||||
|
||||
!!! info
|
||||
If you have read **Tools**, you already know everything on this page. Same decorator, same
|
||||
If you have read **[Tools](tools.md)**, you already know everything on this page. Same decorator, same
|
||||
docstring-as-description, same `Annotated`/`Field`. The only things that change are who
|
||||
triggers it (the user) and where the result goes (into the conversation).
|
||||
|
||||
|
|
@ -147,4 +147,4 @@ The `prompts/list` entry now carries everything a client needs to draw a good fo
|
|||
* `title=` and `Field(description=...)` are what a client puts in its UI.
|
||||
* A missing required argument fails the whole request. There is no per-prompt error result.
|
||||
|
||||
Next up: the one extra parameter a tool, resource or prompt can ask the SDK for, **The Context**.
|
||||
Next up: the one extra parameter a tool, resource or prompt can ask the SDK for, **[The Context](context.md)**.
|
||||
|
|
|
|||
|
|
@ -92,9 +92,9 @@ Notice the `uri` in the result. It is the **concrete** URI the client asked for,
|
|||
|
||||
A mismatch can only ever be a bug, so the SDK makes it impossible to start the server with one.
|
||||
|
||||
The placeholder syntax is RFC 6570: `{+path}` for multi-segment values, `{?q,lang}` for optional query parameters, and more. The SDK also applies path-safety checks to extracted values by default. See **[URI templates and path safety](../advanced/uri-templates.md)** for the full reference.
|
||||
The placeholder syntax is [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570): `{+path}` for multi-segment values, `{?q,lang}` for optional query parameters, and more. The SDK also applies path-safety checks to extracted values by default. See **[URI templates and path safety](../advanced/uri-templates.md)** for the full reference.
|
||||
|
||||
`get_user_profile` can also take a parameter annotated `Context`. The SDK injects it without ever treating it as a URI parameter, and **The Context** chapter covers what it gives you.
|
||||
`get_user_profile` can also take a parameter annotated `Context`. The SDK injects it without ever treating it as a URI parameter, and **[The Context](context.md)** chapter covers what it gives you.
|
||||
|
||||
## What you return
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ The same rule applies to anything else JSON-serialisable: a list, a Pydantic mod
|
|||
`BinaryResource`, `FileResource`, `HttpResource`, `DirectoryResource`) that you register
|
||||
with `mcp.add_resource(...)`.
|
||||
|
||||
A client can also **subscribe** to a resource and be notified when it changes; that's the client's half of the story and it lives in **The Client**.
|
||||
A client can also **subscribe** to a resource and be notified when it changes; that's the client's half of the story and it lives in **[The Client](../client/index.md)**.
|
||||
|
||||
## Recap
|
||||
|
||||
|
|
@ -138,4 +138,4 @@ A client can also **subscribe** to a resource and be notified when it changes; t
|
|||
* `str` becomes text, `bytes` becomes a base64 blob, anything else becomes JSON text. `mime_type=` is how you label it.
|
||||
* Tools are for the model to act. Resources are for the application to read.
|
||||
|
||||
Next: the third primitive, the one a person picks from a menu, **Prompts**.
|
||||
Next: the third primitive, the one a person picks from a menu, **[Prompts](prompts.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Structured Output
|
||||
|
||||
In **Tools** you returned a `str` and the result came back twice: as text in `content`, and as `{"result": "..."}` in `structured_content`.
|
||||
In **[Tools](tools.md)** you returned a `str` and the result came back twice: as text in `content`, and as `{"result": "..."}` in `structured_content`.
|
||||
|
||||
This chapter is about that second channel: where it comes from, every shape it can take, and how the SDK keeps it honest.
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ The short version: **the return type annotation is the output schema**. You alre
|
|||
|
||||
The line that matters is the signature: `-> int`.
|
||||
|
||||
Because of it, the tool the SDK sends during `tools/list` carries an `output_schema` next to the input schema you met in **Tools**:
|
||||
Because of it, the tool the SDK sends during `tools/list` carries an `output_schema` next to the input schema you met in **[Tools](tools.md)**:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -232,7 +232,7 @@ There is one way to end up unstructured without asking for it: return a class th
|
|||
|
||||
!!! tip
|
||||
Need full control (building the `CallToolResult` yourself, or attaching `_meta` that the
|
||||
application can see but the model can't)? That's **The low-level Server**.
|
||||
application can see but the model can't)? That's **[The low-level Server](../advanced/low-level-server.md)**.
|
||||
|
||||
## Recap
|
||||
|
||||
|
|
@ -242,4 +242,4 @@ There is one way to end up unstructured without asking for it: return a class th
|
|||
* What you return is validated against the schema. A mismatch is a tool error, not a corrupt result.
|
||||
* `structured_output=False` opts a tool out. A class without type hints opts out silently; watch for it.
|
||||
|
||||
You now own everything a tool can say back. Next, the second primitive: **Resources**.
|
||||
You now own everything a tool can say back. Next, the second primitive: **[Resources](resources.md)**.
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ Two different things can go wrong, and this flag only touches one of them.
|
|||
An exception inside one of **your tools** is not a protocol failure. It becomes a normal result with
|
||||
`is_error=True`, and the model reads the message. `raise_exceptions` doesn't change that: with or
|
||||
without it, `call_tool` returns the same `is_error=True` result. There's a whole chapter on it:
|
||||
**Handling errors**.
|
||||
**[Handling errors](handling-errors.md)**.
|
||||
|
||||
A failure **outside** a tool body is different. On the connection `Client(mcp)` gives you, the
|
||||
server sanitises it into a generic `"Internal server error"` before the client sees it. You should
|
||||
|
|
@ -103,4 +103,4 @@ example file is exercised by the SDK's own test suite through exactly this clien
|
|||
same tool the SDK uses on itself.
|
||||
|
||||
The tutorial ends here. Putting your tested server in front of a real client, over a real
|
||||
transport, is **Running your server**.
|
||||
transport, is **[Running your server](../run/index.md)**.
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ result.structured_content # {'result': "Found 3 books matching 'dune' (showing
|
|||
|
||||
`content` is the text the **model** reads. `structured_content` is typed data for the **client application**. It's there because you declared the return type as `-> str`.
|
||||
|
||||
Don't worry about `structured_content` yet. Return real Python objects from your tools and the right thing happens; the **Structured Output** chapter is all about it.
|
||||
Don't worry about `structured_content` yet. Return real Python objects from your tools and the right thing happens; the **[Structured Output](structured-output.md)** chapter is all about it.
|
||||
|
||||
### Try it
|
||||
|
||||
|
|
@ -169,4 +169,4 @@ A well-behaved client uses them to decide things like *"do I need to ask the use
|
|||
* Bad arguments are rejected for you, with an error the model can read and recover from.
|
||||
* `async def` for I/O, plain `def` for everything else.
|
||||
|
||||
Next up, **Structured Output**: what happens to the value you `return`.
|
||||
Next up, **[Structured Output](structured-output.md)**: what happens to the value you `return`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue