docs: load media examples from disk instead of inline base64 (#3108)

This commit is contained in:
Max 2026-07-16 20:41:05 +01:00 • committed by GitHub
commit 3a6f2996cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -6,17 +6,17 @@ The SDK ships two helpers for binary results (**`Image`** and **`Audio`**) and a
## Returning an image
Annotate the return type as `Image` and return one:
Annotate the return type as `Image`, point it at a file, and return it:
```python title="server.py" hl_lines="14 16"
```python title="server.py" hl_lines="8 12 14"
--8<-- "docs_src/media/tutorial001.py"
```
* `Image` takes exactly one of `data` (raw bytes) or `path` (a file to read).
* `format="png"` becomes the MIME type the client sees: `image/png`.
* The bytes here are a one-pixel placeholder so the file runs on its own. In a real server they come from Pillow, matplotlib, a headless browser, or anything else that hands you `bytes`.
* `Image` takes exactly one of `path` (a file to read) or `data` (raw bytes).
* The MIME type the client sees is guessed from the suffix: `logo.png` is announced as `image/png`.
* Nothing here is special about logos. Any PNG next to `server.py` works: a chart your code rendered, a diagram, a photo.
`Image` is an SDK convenience, not a protocol type. On the wire your return value becomes an **`ImageContent`** block (your bytes base64-encoded, plus the MIME type):
`Image` is an SDK convenience, not a protocol type. On the wire your return value becomes an **`ImageContent`** block (the file's bytes base64-encoded, plus the MIME type):
```python
result.content # [ImageContent(type="image", data="iVBORw0KGgoAAAANSUhEUg...", mime_type="image/png")]
@ -25,7 +25,7 @@ result.structured_content # None
Two things to notice:
* `data` is base64. You returned raw `bytes`; the SDK did the encoding.
* `data` is base64. You never touched the bytes; the SDK read the file and 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](structured-output.md)**, where the return annotation *is* the schema.)
!!! info
@ -35,32 +35,40 @@ Two things to notice:
### Try it
Drop any PNG next to `server.py`, name it `logo.png`, and run:
```console
uv run mcp dev server.py
```
Open the **Tools** tab and call `logo`. The result is not a string: it is an `image` content block, and the Inspector renders it as a picture. You returned `bytes`; everything between that and the pixels on screen was the SDK.
Open the **Tools** tab and call `logo`. The result is not a string: it is an `image` content block, and the Inspector renders your picture. Everything between the file on disk and the pixels on screen was the SDK.
## Returning audio
`Audio` is the same shape:
`Audio` is the same shape. Keep `logo.png` where it was, and put any WAV beside it as `chime.wav`:
```python title="server.py" hl_lines="21-24"
```python title="server.py" hl_lines="18-21"
--8<-- "docs_src/media/tutorial002.py"
```
The result is an **`AudioContent`** block:
```python
result.content # [AudioContent(type="audio", data="UklGRjQAAABXQVZFZm1...", mime_type="audio/wav")]
result.content # [AudioContent(type="audio", data="UklGR...", mime_type="audio/wav")]
result.structured_content # None
```
Same deal: raw bytes in, base64 and a MIME type out, no output schema.
Same deal: a file on disk in, base64 and a MIME type out, no output schema.
## Bytes or a file
Both helpers also accept `path=` instead of `data=`. The file is read when the result is built, and the MIME type is guessed from the suffix:
Both helpers also accept `data=` (raw bytes) instead of `path=`. That is the mode for bytes that never came from a file of their own — a database column, an HTTP response, something Pillow just drew:
```python title="server.py" hl_lines="14 15"
--8<-- "docs_src/media/tutorial003.py"
```
With `path=` there is nothing to declare: the file is read when the result is built, and the MIME type is guessed from the suffix:
* `Image`: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`.
* `Audio`: `.wav`, `.mp3`, `.ogg`, `.flac`, `.aac`, `.m4a`.
@ -78,7 +86,7 @@ A suffix it doesn't recognise falls back to `application/octet-stream`.
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.
```python title="server.py" hl_lines="5-6 8 11 17"
--8<-- "docs_src/media/tutorial003.py"
--8<-- "docs_src/media/tutorial004.py"
```
* `src` is a URI the client can resolve: `https:`, or a `data:` URI if you want the icon embedded with no extra fetch.
@ -100,7 +108,7 @@ A tool's icons are on the `Tool` object from `tools/list`, a resource's on the `
## Recap
* 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 in-memory `data=` plus an explicit `format=`, or from a `path=` and let the suffix decide.
* Build one from a `path=` and let the suffix decide the MIME type, or from in-memory `data=` plus an explicit `format=`.
* 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.