docs: cover the remaining Tier 1 audit items (#3325)

This commit is contained in:
Max 2026-08-17 20:18:30 +01:00 • committed by GitHub
commit 9057285683
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 247 additions and 1 deletions

View file

@ -81,6 +81,24 @@ A suffix it doesn't recognise falls back to `application/octet-stream`.
`Audio` from MP3 bytes that way and the client is told `mime_type="audio/wav"`, then
faithfully fails to decode it. When you pass `data=`, pass `format=`.
## Embedding a resource
A tool can also return a document: some text or bytes together with the URI it lives at and a MIME type. That is an **`EmbeddedResource`**, another kind of content block. Unlike a plain `str` it tells the client what the content is, so the client can show it as an attachment or recognise a resource it already knows.
```python title="server.py" hl_lines="7 14 16-18"
--8<-- "docs_src/media/tutorial005.py"
```
* `brand://guidelines` is an ordinary resource (**[Resources](resources.md)** covers those). The tool hands the same document to the model on request, and calling `guidelines()` directly keeps one source of truth.
* `EmbeddedResource` and `TextResourceContents` come from `mcp.types`. There is no helper as there is for images: the block you build goes into the result untouched, and there is no `structured_content`.
* Use the URI the resource is registered under, so a client can tell that the attachment and `brand://guidelines` are the same document. Any URI is legal, registered or not.
```python
result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))]
```
For binary content, use `BlobResourceContents(uri=..., mime_type=..., blob=...)` with the bytes base64-encoded into `blob`, in place of `TextResourceContents`. To send only a pointer the client can `resources/read` later, return a `ResourceLink(name=..., uri=...)` instead; it is a content block too.
## Icons
An `Icon` is metadata, not content. It doesn't carry the image; it points at one with a URI, and a client may fetch it and show it next to your server's name, a tool, a resource, or a prompt.
@ -110,6 +128,7 @@ A tool's icons are on the `Tool` object from `tools/list`, a resource's on the `
* Return an `Image` or `Audio` from a tool and the client receives an `ImageContent` / `AudioContent` block: your bytes base64-encoded, with a MIME type.
* Build one from a `path=` and let the suffix decide the MIME type, or from in-memory `data=` plus an explicit `format=`.
* Return an `EmbeddedResource` to put a document (text or a base64 blob, with its URI and MIME type) in the result, or a `ResourceLink` to send just the pointer.
* Media results carry no `structured_content` and no output schema.
* 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.

View file

@ -134,10 +134,55 @@ The `prompts/list` entry now carries everything a client needs to draw a good fo
```
!!! info
If you have read **[Tools](tools.md)**, you already know everything on this page. Same decorator, same
If you have read **[Tools](tools.md)**, you already know everything up to this point. 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).
## More than text
`UserMessage` and `AssistantMessage` also accept a content block, or an `Image` / `Audio` helper, wherever they accept a `str`. Two cases come up in prompts: attaching a document and attaching a picture.
### Embedding a file
```python title="server.py" hl_lines="5 12 21 23"
--8<-- "docs_src/prompts/tutorial004.py"
```
* The style guide is a resource at `style://python` (**[Resources](resources.md)** covers those), read from a `style-guide.md` next to `server.py`. Put any Markdown file there.
* `EmbeddedResource(resource=TextResourceContents(...))`, both from `mcp.types`, carries the file with its URI and MIME type as the first message; the request that refers to it follows as plain text.
* Embedding, rather than pasting the guide into the f-string, lets the client show it as an attachment and reopen `style://python` later, and the model receives the file verbatim. For a binary file use `BlobResourceContents` with a base64 `blob`.
Rendered, the first message's `content` is a `resource` block:
```json
{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}
```
### Attaching an image
```python title="server.py" hl_lines="4 15"
--8<-- "docs_src/prompts/tutorial005.py"
```
* `Image` is the helper from **[Images, audio & icons](media.md)**. `UserMessage` converts it to an `ImageContent` block (the file base64-encoded, MIME type guessed from `.png`) when the prompt renders; `Audio` becomes an `AudioContent` the same way.
* Put any PNG named `architecture.png` beside `server.py`. Prompt arguments are strings, so the picture always comes from the server; `component` only supplies the words.
```json
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
```
## Changing the list at runtime
Prompts can be added while clients are connected, for example to let a user save an instruction as a menu entry of their own. Register the prompt, then notify:
```python title="server.py" hl_lines="5 23-27"
--8<-- "docs_src/prompts/tutorial006.py"
```
* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` registers a function exactly as `@mcp.prompt()` would, and `mcp.remove_prompt(name)` is the reverse. `add_prompt` keeps an existing entry of the same name rather than overwrite it, so the tool removes any old one first to make saving a replace. `prompts/list` reflects the change immediately.
* `await ctx.notify_prompts_changed()` sends `notifications/prompts/list_changed` to every `2026-07-28` client listening on a `subscriptions/listen` stream (**[Subscriptions](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` sends it to the calling client when that client is pre-2026 (**[Serving legacy clients](../run/legacy-clients.md)**). Call both; each does nothing when there is nobody to tell.
* A client that receives the notification calls `prompts/list` again. In the Python `Client` that is `async with client.listen(prompts_list_changed=True) as sub:`, which yields a `PromptsListChanged` event.
## Recap
* `@mcp.prompt()` on a function makes it a prompt. Name from the function, description from the docstring.
@ -146,5 +191,7 @@ The `prompts/list` entry now carries everything a client needs to draw a good fo
* Return a `str` and it becomes one user message. Return a list of `UserMessage` / `AssistantMessage` to seed a multi-turn conversation.
* `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.
* Wrap an `EmbeddedResource` or an `Image` in a `UserMessage` to attach a document or a picture.
* Add or remove prompts at runtime with `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, then `await ctx.notify_prompts_changed()` and `await ctx.session.send_prompt_list_changed()`.
Server-side autocomplete for a prompt's (or a resource template's) arguments is **[Completions](completions.md)**.

View file

@ -34,6 +34,8 @@ From those type hints the SDK generates a JSON Schema and sends it to the client
Both arguments are in `required` because neither has a default. You'll fix that in a moment. (The `title` keys are Pydantic artifacts; the properties, their types, and `required` are the contract.)
There is no `$schema` key either: MCP treats a schema without one as **JSON Schema 2020-12**, which is what Pydantic generates, so there is nothing to choose until you write schemas by hand on the **[low-level Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**.
!!! tip
Type hints aren't documentation here. They are **the contract**. If a client sends `"limit": "ten"`,
the SDK rejects it before your function ever runs.