docs: publish translated docs in twelve languages and the tool that maintains them (#3280)

This commit is contained in:
Max 2026-08-14 17:07:34 +01:00 • committed by GitHub
commit 52ad0a8876
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 89737 additions and 54 deletions

View file

@ -0,0 +1,130 @@
---
translation:
sections: [72f9c964769076dd, 9a2c14e10935b515, 235299eb78ab12d7, 8aee1e78c8237fb8, 9bd86acd4112138f, 55343cb7f250dc7b]
tool: 1
---
# Автодоповнення {#completions}
Клієнт, що будує інтерфейс поверх вашого сервера, хоче автоматично доповнювати значення аргументів, поки користувач їх вводить: назви мов, назви репозиторіїв, шляхи до файлів.
**Автодоповнення** (completions) — це спосіб, у який сервер надає такі підказки.
## Що варто доповнювати {#something-worth-completing}
Автодоповнення стосується рівно двох речей: аргументів **промпту** і параметрів **шаблону ресурсу**. Тож почніть із сервера, де є по одному з них:
```python title="server.py" hl_lines="6 12"
--8<-- "docs_src/completions/tutorial001.py"
```
Тут поки нічого про автодоповнення.
* `review_code` приймає `language`. Користувач не повинен вгадувати, які варіанти написання ви приймаєте.
* `github_repo` приймає `owner` і `repo`. Два поля вільного введення — це погана форма.
## Обробник автодоповнення {#the-completion-handler}
Додайте **одну** функцію з декоратором `@mcp.completion()`:
```python title="server.py" hl_lines="21-29"
--8<-- "docs_src/completions/tutorial002.py"
```
* Обробник один на сервер. Кожен запит на автодоповнення потрапляє сюди, а ви розгалужуєте логіку залежно від того, що саме доповнюється.
* Він має бути `async def`: SDK викликає його через await.
* Він отримує три аргументи:
* `ref`: *який саме* промпт або шаблон ресурсу — як `PromptReference` або `ResourceTemplateReference`. Розрізняють їх через `isinstance`.
* `argument`: `argument.name` — аргумент, що доповнюється, `argument.value` — те, що користувач уже встиг ввести.
* `context`: уже визначені аргументи. Поки що ігноруйте його.
* Повертаєте `Completion(values=[...])` або `None`, коли запропонувати нічого.
!!! tip
`argument.value` — це префікс, який ввів користувач. SDK **не** фільтрує за вас: що покладете
у `values`, те й покаже інтерфейс. `startswith` пишете ви самі.
### Спробуйте самі {#try-it}
Перевірте його за допомогою `Client` у пам'яті зі сторінки **[Тестування](../get-started/testing.md)**. Викличте
`client.complete()` з `ref=PromptReference(name="review_code")` і
`argument={"name": "language", "value": "py"}`:
```python
result.completion.values # ['python']
```
* `ref` — той самий тип посилання, що його отримує обробник.
* `argument` — звичайний словник із рівно двома ключами, `name` і `value`.
Надішліть порожнє `value` — і повернеться весь список. `lang.startswith("")` істинне для кожної мови:
```python
result.completion.values # ['go', 'javascript', 'python', 'rust', 'typescript']
```
Запитайте про `code` (аргумент, якого обробник не знає) — він поверне `None`, а SDK перетворить його на порожній список:
```python
result.completion.values # []
```
`None` означає *«підказок немає»*, а не помилку. Інтерфейс просто показує звичайне текстове поле.
## Можливість, яку ви не оголошували {#a-capability-you-never-declared}
Реєстрація обробника і є оголошенням. Під'єднайте клієнт і погляньте:
```python
client.server_capabilities.completions # CompletionsCapability()
```
Ви ніде не вказували `completions`. SDK побачив обробник і оголосив можливість за вас. Так працює кожна *необов'язкова* можливість: обробник і є оголошенням. (Три примітиви не є необов'язковими: `MCPServer` оголошує їх завжди, з обробниками чи без.)
!!! check
Поверніться до першого `server.py` (того, що без обробника) і все одно надішліть запит. Виклик
завершиться помилкою JSON-RPC:
```text
Method not found
```
А `client.server_capabilities.completions` дорівнює `None`. У цьому й сенс можливості:
коректний клієнт перевіряє її й ніколи не надсилає запит, на який ви не можете відповісти.
## Залежні аргументи {#dependent-arguments}
`github://repos/{owner}/{repo}` має два параметри, і корисні значення для `repo` залежать від того, якого `owner` обрали спершу.
Саме для цього є `context`. Він містить аргументи, які користувач **уже визначив**:
```python title="server.py" hl_lines="8-11 34-38"
--8<-- "docs_src/completions/tutorial003.py"
```
* Нова гілка спрацьовує для параметра `repo` шаблону.
* `context.arguments` — це `dict[str, str] | None` зі значеннями, вибраними досі (тут — `owner`).
* Немає `owner` — немає й осмислених підказок, тож обробник повертає `None`.
Клієнт надсилає ці визначені значення через `context_arguments=`. Цього разу `ref` — це
`ResourceTemplateReference(uri="github://repos/{owner}/{repo}")`. Запитайте `repo` з
порожнім `value` і передайте `context_arguments={"owner": "modelcontextprotocol"}`:
```python
result.completion.values # ['python-sdk', 'typescript-sdk', 'inspector']
```
Приберіть `context_arguments=` — і той самий виклик поверне `[]`. Обробник не може знати, які репозиторії пропонувати, доки не знає власника.
!!! info
`Completion` також приймає `total=` і `has_more=`. Задавайте їх, коли `values` — лише зріз
довшого списку, щоб інтерфейс міг показати *«і ще 200»*. Більшості обробників вони ніколи не знадобляться.
## Підсумки {#recap}
* Автодоповнення — це підказки для **аргументів промптів** і **параметрів шаблонів ресурсів**. Ні для чого іншого.
* `@mcp.completion()` реєструє єдиний обробник. Це `async def (ref, argument, context) -> Completion | None`.
* Розгалужуйтеся за `isinstance(ref, ...)` та за `argument.name`. Фільтруйте за `argument.value` самостійно.
* `None` стає порожнім списком. Це ніколи не помилка.
* `context.arguments` містить уже визначені значення; клієнт передає їх як `context_arguments=`.
* Можливість `completions` з'являється, щойно ви реєструєте обробник. Без нього відповідь на запит — `Method not found`.
Підказки допомагають, поки користувач ще *заповнює* промпт чи шаблон; щоб поставити йому запитання *посеред* виклику інструмента, потрібна **[Еліцитація](../handlers/elicitation.md)** (elicitation). Усе, що інструмент може повернути, крім тексту, — на сторінці **[Зображення, аудіо та значки](media.md)**.

View file

@ -0,0 +1,139 @@
---
translation:
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
tool: 1
---
# Обробка помилок {#handling-errors}
Інструмент може завершитися невдачею двома способами, і SDK обробляє їх зовсім по-різному.
Викиньте звичайний виняток — і його побачить **модель**. Викиньте `MCPError` — і його побачить **протокол**.
Ця сторінка — про те, як вибрати.
## Помилка, яку модель може виправити {#an-error-the-model-can-fix}
Візьмімо інструмент, який щось шукає, і нехай пошук нічого не знайде:
```python title="server.py" hl_lines="11-12"
--8<-- "docs_src/handling_errors/tutorial001.py"
```
У цих двох рядках немає нічого специфічного для MCP. `get_author` викидає звичайний `ValueError`, як це зробила б будь-яка функція Python.
Викличте його з назвою, якої немає в каталозі, і подивіться на результат:
```python
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author: No book titled 'Nothing' in the catalog.")]
result.structured_content # None
```
* Запит **виконався успішно**. Результат є; на боці того, хто викликав, нічого не викинуто.
* `is_error` дорівнює `True`, а повідомлення вашого винятку (з назвою інструмента на початку) лежить у `content` — саме там, де читає модель.
* `structured_content` дорівнює `None`. У невдалого виклику немає значення, яке можна було б структурувати.
Це **помилка інструмента**, і так за замовчуванням обробляється *будь-який* виняток, який викидає ваш інструмент. І майже завжди це саме те, що потрібно.
Ваш інструмент викликає саме модель. Це вона обрала аргументи. Тож помилка інструмента — це репліка в розмові: модель читає *«No book titled 'Nothing' in the catalog.»*, розуміє, що не вгадала назву, і викликає знову з кращою. Один `raise` — і маєте агента, що сам виправляє свої помилки.
!!! tip
Ніколи не повертайте повідомлення про помилку з інструмента через `return`. Повернутий рядок має `is_error=False`, тож для
моделі (і для кожного клієнтського інтерфейсу) це виглядає так, ніби інструмент спрацював і цей рядок і є відповіддю.
Пишіть `raise`. Сигнал — саме прапорець.
## Помилка, яку модель не може виправити {#an-error-the-model-cannot-fix}
Тепер замініть `ValueError` на `MCPError`.
```python title="server.py" hl_lines="1 3 14"
--8<-- "docs_src/handling_errors/tutorial002.py"
```
`MCPError` — це **помилка протоколу** в SDK. Це єдиний виняток, який обгортка інструмента *не* перехоплює: він поширюється далі, і весь запит `tools/call` завершується помилкою JSON-RPC замість результату.
```json
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog."
}
```
* **Результату немає**. Ні `content`, ні `is_error` — моделі нема чого читати.
* Натомість помилку отримує застосунок-**хост** — так само, як отримав би, якби інструмента взагалі не існувало.
* `code`, `message` і `data` доходять без змін. `INVALID_PARAMS` — це `-32602`; `mcp.types` експортує його та інші коди помилок JSON-RPC (`INVALID_REQUEST`, `INTERNAL_ERROR`, ...) як константи, тож набирати магічні числа вручну не доведеться.
!!! check
Той самий пошук, той самий промах, але тепер виклик на боці клієнта *викидає виняток* замість того, щоб повернути результат:
```text
mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.
```
Перша версія давала моделі речення, на яке вона могла відреагувати. Ця не дає їй нічого.
Для `get_author` це однозначно гірше — і саме про це наступний розділ.
## Який із них викидати {#which-one-to-raise}
Ці два шляхи відповідають на два різні запитання.
* **Викидайте будь-який виняток** у разі збою *виконання*: те, що інструмент намагався зробити, не вдалося. Виклик обрала модель, тож саме модель має побачити наслідок і отримати шанс виправитися. Назва з помилкою, зовнішній API, що не відповів вчасно, рядок, якого не існує, — усе це помилки інструмента.
* **Викидайте `MCPError`**, коли слід відхилити *сам запит*: клієнтові бракує можливості, від якої залежить інструмент, сервер не в тому стані, щоб обслуговувати будь-кого, той, хто викликає, пропустив обов'язковий крок. Жодна повторна спроба моделі нічого з цього не виправить, тож передавати їй повідомлення немає сенсу.
Вирішує одне запитання: **чи могла б розумніша модель цього уникнути?** Так -> звичайний виняток. Ні -> `MCPError`.
За цим критерієм друга версія `get_author` зробила хибний вибір: краща назва все виправляє, тож модель заслуговувала побачити повідомлення. Ця версія тут, щоб показати механізм, а не щоб його рекомендувати.
!!! info
`MCPError` імпортується як `from mcp import MCPError` і приймає `code`, `message` та необов'язкове
корисне навантаження `data`. Усе, що ви в них покладете, клієнт і отримає: SDK пересилає викинутий
`MCPError` дослівно, не очищуючи його.
## Ресурс, якого не існує {#a-resource-that-doesnt-exist}
Ресурси проводять ту саму межу й мають один іменований виняток для типового випадку.
```python title="server.py" hl_lines="2 13"
--8<-- "docs_src/handling_errors/tutorial003.py"
```
`books://{title}` — це **шаблон**. Він збігається з *будь-якою* назвою, тому «URI коректний» і «книга існує» — два різні запитання, і відповісти на друге може лише ваша функція.
Коли не може — викиньте `ResourceNotFoundError`. SDK перетворює його на помилку протоколу, яку специфікація призначає для відсутнього ресурсу: `-32602` із запитаним URI в `data`, тож клієнт знає, *яке саме* читання не вдалося.
```json
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog.",
"data": {"uri": "books://Nothing"}
}
```
Зверніть увагу: тут немає напіврезультату з `is_error=True`. Читання ресурсу або повертає вміст, або завершується помилкою: у ресурсів є лише протокольний шлях. Про шаблони й усе інше, що стосується ресурсів, — на сторінці **[Ресурси](resources.md)**.
## Помилки, які ви ніколи не викидаєте {#errors-you-never-raise}
Некоректний аргумент ніколи не доходить до вашої функції.
Надішліть `get_author` параметр `title`, що не є рядком, — і SDK відхилить його за вхідною схемою **ще до** виклику вашої функції, як таку саму помилку інструмента з `is_error=True`, яку модель може прочитати й виправити. На сторінці **[Інструменти](tools.md)** показано таке саме відхилення з обмеженням `Field(le=50)`.
Це означає цілий клас інструкцій `raise`, які писати не треба: не перевіряйте повторно власні анотації типів.
!!! info
Усе на цій сторінці — те, що бачить **клієнт**, і `Client` у пам'яті, з яким ви писатимете
тести, бачить рівно те саме. Навіть `raise_exceptions=True` не перетворює помилку інструмента
назад на трасування: до моменту, коли цей прапорець міг би спрацювати, ваш виняток уже став
результатом з `is_error=True`. Перевіряйте результат через assert. Цей підхід описано на сторінці **[Тестування](../get-started/testing.md)**.
## Підсумки {#recap}
* Викиньте **будь-який виняток** в інструменті -> виклик повертає `is_error=True` з вашим повідомленням у `content`. Модель читає його й може повторити спробу. Це поведінка за замовчуванням.
* Викиньте **`MCPError`** -> сам виклик завершується помилкою JSON-RPC. Модель нічого не бачить; розбирається хост. `code`, `message` і `data` доходять без змін.
* Вирішальне запитання: *чи могла б розумніша модель цього уникнути?* Так -> виняток. Ні -> `MCPError`.
* `ResourceNotFoundError` з обробника ресурсу -> протокольний `-32602` з URI в `data`.
* Некоректні аргументи відхиляються за схемою ще до запуску вашої функції; для них `raise` не потрібен.
* `from mcp import MCPError`; константи кодів помилок — з `mcp.types`.
З помилками розібралися. Це все, що сервер *надає назовні*. Про те, що кожен обробник може читати і що робити у відповідь клієнтові під час роботи, — наступний розділ: **[Усередині обробника](../handlers/index.md)**.
Точний текст помилок SDK, з якими ви найімовірніше зіткнетеся, що кожна з них означає і як виправити кожну одним рухом, — на сторінці **[Усунення несправностей](../troubleshooting.md)**.

View file

@ -0,0 +1,37 @@
---
translation:
sections: [09defc170a0da89d]
tool: 1
---
# Сервери {#servers}
`MCPServer` надає під'єднаному клієнтові три примітиви. Вони різняться тим,
хто вирішує їх використати:
* **[Інструмент](tools.md)** — це дія, яку обирає й викликає *модель*. Саме
ця сторінка зазвичай потрібна першою, а
**[Структурований вивід](structured-output.md)** — її довідковий супутник:
усе про форму того, що повертає інструмент.
* **[Ресурс](resources.md)** — це дані лише для читання, які вирішує
прочитати *застосунок*. **[URI-шаблони](uri-templates.md)** — його
довідковий супутник: повний синтаксис адресації та правила безпеки шляхів.
* **[Промпт](prompts.md)** — це шаблон повідомлення, який *людина* викликає
за іменем, з меню або через слеш-команду.
Довкола цих трьох примітивів — решта того, що оголошує сервер:
* **[Доповнення](completions.md)** — серверне автодоповнення аргументів
промптів і шаблонів ресурсів.
* **[Зображення, аудіо та іконки](media.md)** охоплює все, що інструмент
може повернути, окрім тексту, а також іконки, які клієнт показує поруч із
вашим сервером.
* **[Обробка помилок](handling-errors.md)** пояснює різницю між помилкою,
після якої модель може відновитися, і помилкою, яку вона ніколи не має
побачити.
Кожна сторінка тут самодостатня; переходьте одразу до потрібної. Якщо сервер
ще не створено, почніть натомість із **[Перших кроків](../get-started/first-steps.md)**.
Те, що відбувається *всередині* зареєстрованих функцій (`Context`, впровадження
залежностей, запит додаткового вводу від користувача посеред виклику), — це
наступний розділ, **[Усередині обробника](../handlers/index.md)**.

View file

@ -0,0 +1,122 @@
---
translation:
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
tool: 1
---
# Медіа {#media}
Текст — не єдине, що може повернути інструмент.
SDK містить два допоміжні класи для двійкових результатів (**`Image`** і **`Audio`**) та тип **`Icon`**, що дає серверу, інструментам, ресурсам і промптам власне обличчя в інтерфейсі клієнта.
## Повернення зображення {#returning-an-image}
Оголосіть тип результату як `Image`, вкажіть файл і поверніть об'єкт:
```python title="server.py" hl_lines="8 12 14"
--8<-- "docs_src/media/tutorial001.py"
```
* `Image` приймає рівно один із двох аргументів: `path` (файл, який треба прочитати) або `data` (сирі байти).
* MIME-тип, який бачить клієнт, визначається за розширенням: `logo.png` оголошується як `image/png`.
* У логотипах тут немає нічого особливого. Підійде будь-який PNG поруч із `server.py`: графік, який побудував ваш код, діаграма, фото.
`Image` — це зручність SDK, а не тип протоколу. У переданих даних повернене значення стає блоком **`ImageContent`** (байти файлу в кодуванні base64 плюс MIME-тип):
```python
result.content # [ImageContent(type="image", data="iVBORw0KGgoAAAANSUhEUg...", mime_type="image/png")]
result.structured_content # None
```
Зверніть увагу на дві речі:
* `data` — це base64. Байтів ви не торкалися: SDK прочитав файл і закодував його сам.
* `structured_content` дорівнює `None`. `Image` — це вміст, на який дивиться модель, а не дані, які розбирає застосунок: схеми виводу немає. (Порівняйте зі **[структурованим виводом](structured-output.md)**, де анотація результату *і є* схемою.)
!!! info
`ImageContent` і `AudioContent` містяться в `mcp.types`, поруч із `TextContent`,
на який перетворюється звичайний результат `str` (**[Інструменти](tools.md)**). Результат інструмента — це список блоків вмісту; `Image` і `Audio` —
найкоротший спосіб отримати два двійкові різновиди.
### Спробуйте самі {#try-it}
Покладіть будь-який PNG поруч із `server.py`, назвіть його `logo.png` і запустіть:
```console
uv run mcp dev server.py
```
Відкрийте вкладку **Tools** і викличте `logo`. Результат — не рядок: це блок вмісту `image`, і Inspector показує ваше зображення. Усе між файлом на диску й пікселями на екрані зробив SDK.
## Повернення аудіо {#returning-audio}
`Audio` має ту саму форму. Залиште `logo.png` на місці й покладіть поруч будь-який WAV під назвою `chime.wav`:
```python title="server.py" hl_lines="18-21"
--8<-- "docs_src/media/tutorial002.py"
```
Результат — блок **`AudioContent`**:
```python
result.content # [AudioContent(type="audio", data="UklGR...", mime_type="audio/wav")]
result.structured_content # None
```
Те саме: на вході — файл на диску, на виході — base64 і MIME-тип, без схеми виводу.
## Байти чи файл {#bytes-or-a-file}
Обидва допоміжні класи приймають також `data=` (сирі байти) замість `path=`. Це режим для байтів, які ніколи не були окремим файлом: стовпець бази даних, HTTP-відповідь, щось щойно намальоване в Pillow:
```python title="server.py" hl_lines="14 15"
--8<-- "docs_src/media/tutorial003.py"
```
Із `path=` оголошувати нічого не потрібно: файл читається під час побудови результату, а MIME-тип визначається за розширенням:
* `Image`: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`.
* `Audio`: `.wav`, `.mp3`, `.ogg`, `.flac`, `.aac`, `.m4a`.
Нерозпізнане розширення дає `application/octet-stream`.
!!! check
Із `data=` імені файлу немає, тож визначати тип немає з чого. Забудете `format=` —
і SDK візьме типове значення: `image/png` для зображень, `audio/wav` для аудіо. Створіть
так `Audio` з байтів MP3 — і клієнту повідомлять `mime_type="audio/wav"`, після чого
він сумлінно не зможе це декодувати. Передаєте `data=` — передавайте й `format=`.
## Іконки {#icons}
`Icon` — це метадані, а не вміст. Він не містить зображення, а вказує на нього через URI, і клієнт може завантажити його й показати поруч із назвою сервера, інструментом, ресурсом чи промптом.
```python title="server.py" hl_lines="4-5 7 10 16"
--8<-- "docs_src/media/tutorial004.py"
```
* `src` — це URI, який клієнт може розв'язати: `https:` або `data:`, якщо потрібно вбудувати іконку без додаткового запиту.
* `mime_type` і `sizes` (`"48x48"` або `"any"` для масштабованого формату) дають клієнту змогу вибрати потрібну, коли ви пропонуєте кілька.
* `theme="light"` або `theme="dark"` позначає іконку для однієї колірної схеми.
Той самий іменований аргумент `icons=[...]` приймають `MCPServer(...)`, `@mcp.tool()`, `@mcp.resource()` і `@mcp.prompt()`.
### Де їх бачить клієнт {#where-a-client-sees-them}
Іконки передаються разом із тим, що вони прикрашають. Іконки сервера надходять під час підключення клієнта, у `client.server_info` (на з'єднаннях покоління 2026 це поле необов'язкове, тож спершу звузьте тип):
```python
assert client.server_info is not None # python-sdk servers identify themselves by default
client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_type="image/png", sizes=["48x48"])]
```
Іконки інструмента містяться в об'єкті `Tool` з `tools/list`, ресурсу — в `Resource` з `resources/list`, промпту — в `Prompt` з `prompts/list`. Поле завжди називається `icons`.
## Підсумки {#recap}
* Поверніть з інструмента `Image` або `Audio` — і клієнт отримає блок `ImageContent` / `AudioContent`: ваші байти в кодуванні base64 з MIME-типом.
* Створюйте їх із `path=`, і тоді MIME-тип визначить розширення, або з `data=` у пам'яті плюс явний `format=`.
* Медіарезультати не мають ні `structured_content`, ні схеми виводу.
* `Icon` — це вказівник: URI `src` плюс необов'язкові `mime_type`, `sizes` і `theme`.
* `icons=[...]` працює на сервері, інструментах, ресурсах і промптах, а клієнти знаходять їх у відповідних об'єктах.
Це все, що інструмент може покласти *в* результат. Що відбувається, коли інструмент *зазнає невдачі* (і хто має про це дізнатися), — на сторінці **[Обробка помилок](handling-errors.md)**.

View file

@ -0,0 +1,155 @@
---
translation:
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
tool: 1
---
# Промпти {#prompts}
**Промпт** — це шаблон повідомлення, який обирає користувач.
Інструменти призначені для моделі. Промпт — навпаки: користувач обирає його з меню у своєму клієнті (слеш-команда, кнопка), заповнює аргументи, і згенеровані повідомлення потрапляють у розмову так, ніби він набрав їх сам.
Щоб оголосити промпт, поставте `@mcp.prompt()` над функцією, яка повертає текст.
## Ваш перший промпт {#your-first-prompt}
```python title="server.py" hl_lines="6-9"
--8<-- "docs_src/prompts/tutorial001.py"
```
SDK зчитує ті самі три речі, що й з інструмента:
* **Ім'я** — це ім'я функції: `review_code`.
* **Опис**, який показує клієнт, — це docstring: `Review a piece of code.`
* **Аргументи** беруться з параметрів. `code` не має типового значення, тому він обов'язковий.
Ось що клієнт отримує у відповідь на `prompts/list`:
```json
{
"name": "review_code",
"description": "Review a piece of code.",
"arguments": [
{"name": "code", "required": true}
]
}
```
Тут немає JSON Schema. Аргументи промпту — це плоский список **іменованих рядкових значень**: форма, яку заповнює людина, а не дані, які конструює модель.
### Генерування {#rendering-it}
Клієнт генерує повідомлення за шаблоном через `prompts/get`, передаючи аргументи. Ваша функція виконується, і повернутий `str` стає **одним повідомленням користувача**:
```json
{
"description": "Review a piece of code.",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this code:\n\ndef add(a, b): return a + b"
}
}
],
"resultType": "complete"
}
```
Оце й усе життя промпту: його показують у списку за іменем, генерують на вимогу і вставляють у чат.
!!! check
`required` перевіряється ще до запуску вашої функції. Згенеруйте `review_code` без `code` —
і сам запит завершиться помилкою JSON-RPC (код `-32603`):
```text
mcp.shared.exceptions.MCPError: Internal server error
```
Результату з помилкою на кшталт інструмента, який можна було б передати моделі, тут немає, бо моделі в цьому ланцюжку немає взагалі:
виклик викидає виняток. Причина (`Missing required arguments: {'code'}`) потрапляє в лог вашого сервера.
### Спробуйте самі {#try-it}
Запустіть сервер із MCP Inspector:
```console
uv run mcp dev server.py
```
Відкрийте вкладку **Prompts** і виберіть `review_code`. Inspector намалює форму з одним обов'язковим полем `code`. Заповніть його, згенеруйте промпт — і отримаєте точно те повідомлення користувача, що наведене вище.
## Більше ніж одне повідомлення {#more-than-one-message}
Рев'ю коду — це одне повідомлення. Сеанс налагодження — це розмова, і промпт може закласти її цілком.
Поверніть список повідомлень замість `str`:
```python title="server.py" hl_lines="2 13-20"
--8<-- "docs_src/prompts/tutorial002.py"
```
* `UserMessage` і `AssistantMessage` імпортуються з `mcp.server.mcpserver.prompts.base`. Передайте їм `str`, і вони самі загорнуть його в `TextContent`. Роль — це ім'я класу.
* `Message` — їхній спільний базовий клас. Використовуйте його як анотацію типу результату.
Генерування `debug_error` тепер дає три повідомлення по порядку:
```json
{
"description": "Start a debugging conversation.",
"messages": [
{"role": "user", "content": {"type": "text", "text": "I'm seeing this error:"}},
{"role": "user", "content": {"type": "text", "text": "TypeError: 'int' object is not iterable"}},
{
"role": "assistant",
"content": {"type": "text", "text": "I'll help debug that. What have you tried so far?"}
}
],
"resultType": "complete"
}
```
Зверніть увагу на останнє. Заздалегідь заповнена репліка `assistant` — це спосіб спрямувати *наступну* відповідь моделі, не змушуючи користувача набирати ці настанови самому.
## Заголовки та описи аргументів {#titles-and-argument-descriptions}
`review_code` — це ім'я функції, а не підпис. Дайте клієнту щось краще для напису на кнопці й опишіть кожен аргумент, щоб форма пояснювала себе сама:
```python title="server.py" hl_lines="10-13"
--8<-- "docs_src/prompts/tutorial003.py"
```
* `title="Code review"` — це зрозуміла людині назва, точно як `title` в інструмента.
* `Annotated[str, Field(description=...)]` — той самий шаблон, яким на сторінці **[Інструменти](tools.md)** описано параметри інструмента. Тут опис потрапляє на аргумент, а не в схему.
* `language` має типове значення, тому перестає бути обов'язковим.
Запис у `prompts/list` тепер містить усе, що потрібно клієнту, щоб намалювати хорошу форму:
```json
{
"name": "review_code",
"title": "Code review",
"description": "Review a piece of code.",
"arguments": [
{"name": "code", "description": "The code to review.", "required": true},
{"name": "language", "description": "The language the code is written in.", "required": false}
]
}
```
!!! info
Якщо ви читали сторінку **[Інструменти](tools.md)**, то вже знаєте все, що є на цій. Той самий декоратор, той самий
docstring як опис, ті самі `Annotated`/`Field`. Змінюється лише те, хто
його запускає (користувач) і куди йде результат (у розмову).
## Підсумки {#recap}
* `@mcp.prompt()` над функцією робить її промптом. Ім'я — з функції, опис — з docstring.
* Промптами **керує користувач**: клієнт показує їхній список, користувач обирає один і заповнює аргументи.
* Аргументи — це плоский список іменованих рядків (без схеми). Параметр із типовим значенням необов'язковий.
* Поверніть `str` — і він стане одним повідомленням користувача. Поверніть список `UserMessage` / `AssistantMessage`, щоб закласти багатоходову розмову.
* `title=` і `Field(description=...)` — це те, що клієнт показує у своєму інтерфейсі.
* Відсутній обов'язковий аргумент провалює весь запит. Окремого результату з помилкою для промпту немає.
Серверне автодоповнення аргументів промпту (або шаблону ресурсу) — це **[Автодоповнення](completions.md)**.

View file

@ -0,0 +1,146 @@
---
translation:
sections: [09df998c2a799f78, 0cf131146d16d4f9, 4e6b91e3f8025346, 8fe4eef576db17ed, 0d0d1ed43e3d0a53]
tool: 1
---
# Ресурси {#resources}
**Ресурс** — це дані, які ви надаєте застосунку для читання.
У цьому й полягає розмежування. Інструмент — це те, що вирішує викликати **модель**. Ресурс — це те, що вирішує завантажити **застосунок** (файл конфігурації, запис, документ) і покласти перед моделлю як контекст.
Щоб оголосити ресурс, повісьте `@mcp.resource(uri)` на звичайну функцію Python.
## Ваш перший ресурс {#your-first-resource}
```python title="server.py" hl_lines="6-8"
--8<-- "docs_src/resources/tutorial001.py"
```
Форма та сама, що й в інструмента, плюс одна річ: **URI**. Ресурси мають адресу, а не ім'я. Клієнт запитує `config://app`, а не `get_config`.
Решту SDK, як і раніше, зчитує з функції:
* **Ім'я** — це ім'я функції: `get_config`.
* **Опис**, який бачить клієнт, — це docstring.
* **Вміст** — те, що ви повертаєте.
Під час `resources/list` клієнт отримує ось що:
```json
{
"name": "get_config",
"uri": "config://app",
"description": "The active shop configuration.",
"mimeType": "text/plain"
}
```
А коли він читає `config://app`, виконується ваша функція, і повернене значення приходить назад як текст:
```python
result.contents # [TextResourceContents(uri="config://app", mime_type="text/plain", text="theme=dark\nlanguage=en")]
```
!!! tip
Перелік коштує дешево. Ваша функція **не** викликається під час `resources/list` — лише
під час `resources/read`, і лише для запитаного URI. Надайте тисячу ресурсів —
і платитимете тільки за ті, які хтось відкриє.
### Спробуйте самі {#try-it}
Запустіть сервер у MCP Inspector:
```console
uv run mcp dev server.py
```
Відкрийте URL, який він виведе, і перейдіть на вкладку **Resources**. `config://app` є в списку разом з описом. Клацніть його — Inspector його прочитає: ось ваші два рядки конфігурації.
## Шаблони ресурсів {#resource-templates}
Один URI на запис не масштабується. Додайте в URI **заповнювач** і відповідний параметр у функцію:
```python title="server.py" hl_lines="12-13"
--8<-- "docs_src/resources/tutorial002.py"
```
`{user_id}` в URI, `user_id: str` у функції. Оце й увесь контракт.
Тепер це **шаблон ресурсу**, і він переїжджає: зникає з `resources/list` і натомість з'являється в `resources/templates/list` — як зразок, а не як адреса:
```json
{
"name": "get_user_profile",
"uriTemplate": "users://{user_id}/profile",
"description": "A customer's profile.",
"mimeType": "text/plain"
}
```
Клієнт підставляє значення замість заповнювача й читає конкретний URI: `users://42/profile`, `users://ada/profile`. На всі відповідає одна функція, а зіставлене значення передається як `user_id`:
```python
result.contents # [TextResourceContents(uri="users://42/profile", text="User 42: 12 orders since 2021.")]
```
Зверніть увагу на `uri` в результаті. Це **конкретний** URI, який запитав клієнт, а не шаблон.
!!! check
Заповнювачі та параметри мають збігатися. Перейменуйте параметр функції на
`user`, поки в URI досі вказано `{user_id}`, — і декоратор відмовить **під час імпорту**,
задовго до того, як до нього наблизиться будь-який клієнт:
```text
ValueError: Mismatch between URI parameters {'user_id'} and function parameters {'user'}
```
Розбіжність може бути лише помилкою, тож SDK робить неможливим запуск сервера з нею.
Синтаксис заповнювачів — [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570): `{+path}` для багатосегментних значень, `{?q,lang}` для необов'язкових параметрів запиту тощо. Крім того, SDK за замовчуванням застосовує до видобутих значень перевірки безпеки шляхів. Повний довідник — на сторінці **[Шаблони URI та безпека шляхів](uri-templates.md)**.
`get_user_profile` також може приймати параметр з анотацією `Context`. SDK впроваджує його, ніколи не трактуючи як параметр URI, а що саме він дає — описано на сторінці **[Об'єкт Context](../handlers/context.md)**.
## Що повертати {#what-you-return}
Ви не обмежені типом `str`. Задайте кожному ресурсу `mime_type` і повертайте те, що пасує:
```python title="server.py" hl_lines="8-9 14-15 20-21"
--8<-- "docs_src/resources/tutorial003.py"
```
* `readme` повертає `str`, тож його надсилають як є. Це найпоширеніший випадок.
* `catalog_stats` повертає `dict`, тому SDK серіалізує його в **JSON-текст** за вас:
```json
{
"books": 1204,
"authors": 391
}
```
* `placeholder_cover` повертає `bytes`, тож клієнт отримує `BlobResourceContents` замість `TextResourceContents`, а ваші байти закодовано в base64 у полі `blob`.
Те саме правило стосується всього, що серіалізується в JSON: списку, моделі Pydantic, dataclass. Якщо це не `str` і не `bytes`, воно стає JSON.
`mime_type` ви оголошуєте самі, і за замовчуванням це `text/plain`. SDK ніколи не аналізує повернене значення, щоб його вгадати, тож ресурс із `dict`, який ви не позначили, усе одно оголошується як звичайний текст.
!!! tip
`@mcp.resource()` також приймає `name=`, `title=` і `description=`, коли їх не хочеться
виводити з функції. А коли функцію взагалі писати не треба,
у `mcp.server.mcpserver.resources` є готові класи `Resource` (`TextResource`,
`BinaryResource`, `FileResource`, `HttpResource`, `DirectoryResource`), які реєструють
через `mcp.add_resource(...)`.
Клієнт також може **підписатися** на ресурс і отримувати сповіщення про його зміни; це клієнтська половина історії, і описана вона на сторінці **[Клієнт](../client/index.md)**.
## Підсумки {#recap}
* `@mcp.resource(uri)` на функції робить її ресурсом. URI — це адреса, повернене значення — вміст, docstring — опис.
* `{placeholder}` в URI перетворює його на **шаблон**: він потрапляє в `resources/templates/list`, і одна функція обслуговує всі URI, що збігаються.
* Імена заповнювачів мають дорівнювати іменам параметрів функції. Помилитеся — і дізнаєтеся про це під час імпорту, а не в продакшені.
* Ваша функція виконується, коли ресурс **читають**, а не коли його перелічують.
* `str` стає текстом, `bytes` — base64-блобом, усе інше — JSON-текстом. `mime_type=` — це те, як ви його позначаєте.
* Інструменти — щоб модель діяла. Ресурси — щоб застосунок читав.
Третій примітив, той, що його людина вибирає з меню, — це **[Промпти](prompts.md)**.

View file

@ -0,0 +1,250 @@
---
translation:
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
tool: 1
---
# Структурований вивід {#structured-output}
Інструмент, що повертає звичайний `str`, видає результат двічі: як текст у `content` і як `{"result": "..."}` у `structured_content`.
Ця сторінка — про той другий канал: звідки він береться, яких форм може набувати і як SDK стежить, щоб він не брехав.
Коротко: **анотація типу, що повертається, і є схемою виводу**. Ви її вже написали.
## Схема виводу {#the-output-schema}
```python title="server.py" hl_lines="9"
--8<-- "docs_src/structured_output/tutorial001.py"
```
Важливий рядок — сигнатура: `-> int`.
Завдяки їй інструмент, який SDK надсилає під час `tools/list`, несе `output_schema` поруч зі схемою вводу, побудованою з параметрів (її описано на сторінці **[Інструменти](tools.md)**):
```json
{
"properties": {
"result": {"title": "Result", "type": "integer"}
},
"required": ["result"],
"title": "get_temperatureOutput",
"type": "object"
}
```
Голий `int` — це не JSON-об'єкт, тому SDK **загортає** його в `{"result": ...}`. Викличте інструмент — і обидва канали заповнені:
```python
result.content # [TextContent(text="17")]
result.structured_content # {"result": 17}
```
Таку саму обгортку отримує кожен скаляр: `str`, `int`, `float`, `bool`, `bytes`, `None`.
## Два канали {#two-channels}
Навіщо надсилати те саме значення двічі?
* `content` — для **моделі**. Мовна модель читає текст; це єдина частина результату, яку вона бачить.
* `structured_content` — для **застосунку**, усередині якого працює модель: коду, якому потрібне `17`, а не речення зі словом «17».
* `output_schema` — це контракт між ними, опублікований ще до першого виклику інструмента.
Ви повертаєте одне значення Python. SDK заповнює всі три.
## Повернення моделі {#return-a-model}
Оголосіть форму як `BaseModel` з Pydantic і поверніть екземпляр:
```python title="server.py" hl_lines="8-11 15"
--8<-- "docs_src/structured_output/tutorial002.py"
```
Тепер схемою **є** сам `WeatherData`. Без обгортки, без ключа `result`:
```json
{
"properties": {
"temperature": {"description": "Degrees Celsius.", "title": "Temperature", "type": "number"},
"humidity": {"description": "Relative humidity, 0 to 1.", "title": "Humidity", "type": "number"},
"conditions": {"title": "Conditions", "type": "string"}
},
"required": ["temperature", "humidity", "conditions"],
"title": "WeatherData",
"type": "object"
}
```
`structured_content` — це сам об'єкт, поле в поле:
```python
result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions": "Overcast"}
```
І модель не лишається осторонь. SDK серіалізує той самий об'єкт у JSON-текст для `content`:
```json
{
"temperature": 16.2,
"humidity": 0.83,
"conditions": "Overcast"
}
```
Зверніть увагу: `Field(description=...)` на `temperature` і `humidity` потрапили до схеми. Той самий `Field`, що описував **вхідні дані**, описує й вихідні.
!!! info
Якщо ви користувалися `response_model` у FastAPI, це вам знайомо: модель Pydantic як оголошена
відповідь, серіалізована й задокументована за вас. Єдина відмінність — тут усе оголошення
вичерпується анотацією типу, що повертається.
## `TypedDict` {#a-typeddict}
Не кожна форма заслуговує на клас. `TypedDict` дає таку саму схему:
```python title="server.py" hl_lines="8"
--8<-- "docs_src/structured_output/tutorial003.py"
```
Під час виконання `TypedDict` — це звичайний `dict`, тож саме його ви будуєте й повертаєте. Схема, валідація і `structured_content` ідентичні версії з `BaseModel` (за винятком описів, для яких у `TypedDict` немає місця).
## Dataclass {#a-dataclass}
Dataclass теж підходять, як і будь-який звичайний клас, атрибути якого мають анотації типів. SDK усередині будує з анотацій модель Pydantic.
```python title="server.py" hl_lines="8-9"
--8<-- "docs_src/structured_output/tutorial004.py"
```
Три записи — одна схема. Беріть той, що вже є у вашій кодовій базі.
## Списки {#lists}
`list[...]` — теж не JSON-об'єкт, тому він отримує обгортку `{"result": ...}`, а тип елемента всередині неї — посилання на `$defs`:
```python title="server.py" hl_lines="15"
--8<-- "docs_src/structured_output/tutorial005.py"
```
```json
{
"$defs": {
"WeatherData": {
"properties": {
"temperature": {"title": "Temperature", "type": "number"},
"humidity": {"title": "Humidity", "type": "number"},
"conditions": {"title": "Conditions", "type": "string"}
},
"required": ["temperature", "humidity", "conditions"],
"title": "WeatherData",
"type": "object"
}
},
"properties": {
"result": {"items": {"$ref": "#/$defs/WeatherData"}, "title": "Result", "type": "array"}
},
"required": ["result"],
"title": "get_forecastOutput",
"type": "object"
}
```
Запитайте прогноз на два дні — і `structured_content` буде `{"result": [{...}, {...}]}`. `content` перетворюється на **два** блоки `TextContent`, по одному на елемент: список для моделі розгортається, а не зливається в один рядок.
`tuple[...]`, об'єднання типів і `Optional[...]` загортаються так само.
## Словники {#dictionaries}
`dict[str, ...]` — єдиний узагальнений тип, що вже *є* JSON-об'єктом, тому він не загортається:
```python title="server.py" hl_lines="9"
--8<-- "docs_src/structured_output/tutorial006.py"
```
```json
{
"additionalProperties": {"type": "number"},
"title": "get_temperaturesDictOutput",
"type": "object"
}
```
```python
result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
```
Ключі мають бути `str`. `dict[int, float]` не може бути JSON-об'єктом, тож він повертається до обгортки `{"result": ...}`.
## Валідація {#validation}
`output_schema` — це не документація. Усе, що повертає функція, **перевіряється на відповідність їй** перед тим, як залишити сервер.
Поки значення будується вручну, цього не помічаєш: Pydantic уже подбав, щоб `WeatherData` був `WeatherData`. Помічаєш того дня, коли дані приходять звідкись, що ви не контролюєте:
```python title="server.py" hl_lines="9 21"
--8<-- "docs_src/structured_output/tutorial007.py"
```
Анотація обіцяє `WeatherData`. Відповідь зовнішнього сервісу перестала надсилати `humidity`.
!!! check
Викличте `get_weather` — і він не передасть клієнту тихцем напівпорожній об'єкт. Виклик завершується помилкою,
і перші рядки помилки називають поле:
```text
Error executing tool get_weather: 1 validation error for WeatherData
humidity
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
```
Цей текст повертається як результат інструмента з `is_error=True`, тож модель знає, що виклик не вдався,
замість того щоб упевнено читати погоду, якої немає.
До речі, повертати звичайний `dict` з інструмента з `-> WeatherData` цілком можна. Саме це й видав `json.loads`. Перевіряється значення, а не тип Python.
## Відмова від структурованого виводу {#opting-out}
Іноді анотація типу, що повертається, призначена для перевірки типів, а не для протоколу. Передайте `structured_output=False` — і інструмент стане лише текстовим:
```python title="server.py" hl_lines="6"
--8<-- "docs_src/structured_output/tutorial008.py"
```
Ні `output_schema`, ні обгортки, ні валідації. `structured_content` дорівнює `None`, а `content` — рядок, який ви повернули.
Протилежне, `structured_output=True`, перетворює автоматичне визначення на вимогу: інструмент, чий тип повернення не може дати схему, викидає виняток під час імпорту замість відкоту до тексту.
## Клас без анотацій типів {#a-class-without-type-hints}
Є один спосіб опинитися без структурованого виводу, не просивши про це: повернути клас, у **тілі якого немає анотацій**.
```python title="server.py" hl_lines="6-9"
--8<-- "docs_src/structured_output/tutorial009.py"
```
`Station` задає `name` і `online` всередині `__init__`, але сам *клас* нічого не оголошує. SDK читає анотації класу, не знаходить жодної й здається.
!!! warning
Здається він **мовчки**. `output_schema` — `None`, `structured_content` — `None`, а текст,
який читає модель, — це `repr` об'єкта:
```text
"<server.Station object at 0x7f539d75b230>"
```
Ні помилки, ні попередження — непридатний інструмент. Перенесіть анотації в тіло класу або передайте
`structured_output=True`, що перетворює це на жорстку помилку в момент імпорту модуля:
`Function get_station: return type <class 'server.Station'> is not serializable for structured output`.
!!! tip
Потрібен повний контроль (самостійно збудувати `CallToolResult` або додати `_meta`, які
застосунок бачить, а модель — ні)? Це — **[Низькорівневий Server](../advanced/low-level-server.md)**.
## Підсумки {#recap}
* **Анотація типу, що повертається**, — це схема виводу. Вона публікується в `tools/list` як `output_schema`.
* Скаляри, списки, кортежі й об'єднання типів загортаються в `{"result": ...}`. Моделі, `TypedDict`, dataclass, анотовані класи й `dict[str, ...]` — уже об'єкти й лишаються як є.
* Кожен результат несе `content` (текст, для моделі) **і** `structured_content` (дані, для застосунку).
* Те, що ви повертаєте, перевіряється на відповідність схемі. Невідповідність — це помилка інструмента, а не зіпсований результат.
* `structured_output=False` вимикає це для інструмента. Клас без анотацій типів вимикає це мовчки; пильнуйте.
Тепер ви володієте всім, що інструмент може сказати у відповідь. Далі — другий примітив: **[Ресурси](resources.md)**.

View file

@ -0,0 +1,177 @@
---
translation:
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
tool: 1
---
# Інструменти {#tools}
**Інструмент** — це функція, яку може викликати модель.
Щоб оголосити інструмент, додайте `@mcp.tool()` до звичайної Python-функції. Оце й увесь API.
## Ваш перший інструмент {#your-first-tool}
```python title="server.py" hl_lines="6-8"
--8<-- "docs_src/tools/tutorial001.py"
```
Погляньте, що ви написали. Жодних схем, жодного JSON, жодного протоколу — просто функція. SDK зчитує з неї три речі:
* **Ім'я** інструмента — це ім'я функції: `search_books`.
* **Опис**, який бачить модель, — це docstring: `Search the catalog by title or author.`
* **Аргументи**, які дозволено передавати моделі, беруться з анотацій типів: `query: str` і `limit: int`.
### Вхідна схема {#the-input-schema}
З цих анотацій типів SDK генерує JSON Schema і надсилає її клієнту під час `tools/list`:
```json
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"},
"limit": {"title": "Limit", "type": "integer"}
},
"required": ["query", "limit"],
"title": "search_booksArguments"
}
```
Обидва аргументи потрапили в `required`, бо жоден не має типового значення. За мить ви це виправите. (Ключі `title` — артефакти Pydantic; контракт складають властивості, їхні типи та `required`.)
!!! tip
Анотації типів тут — не документація. Вони і є **контракт**. Якщо клієнт надішле `"limit": "ten"`,
SDK відхилить запит ще до того, як ваша функція запуститься.
### Що отримує модель у відповідь {#what-the-model-gets-back}
Викличте інструмент із `{"query": "dune", "limit": 5}` — і результат матиме дві частини:
```python
result.content # [TextContent(text="Found 3 books matching 'dune' (showing up to 5).")]
result.structured_content # {'result': "Found 3 books matching 'dune' (showing up to 5)."}
```
`content` — це текст, який читає **модель**. `structured_content` — типізовані дані для **клієнтського застосунку**. Вони там, бо ви оголосили тип повернення як `-> str`.
Поки що не переймайтеся `structured_content`. Повертайте з інструментів справжні Python-об'єкти — і все відбудеться правильно; цьому цілком присвячена сторінка **[Структурований вивід](structured-output.md)**.
### Спробуйте самі {#try-it}
Запустіть сервер через MCP Inspector:
```console
uv run mcp dev server.py
```
Відкрийте URL, який він надрукує, перейдіть на вкладку **Tools** і викличте `search_books`.
Inspector покаже форму з обов'язковим текстовим полем `query` та обов'язковим числовим полем `limit`. Цю форму він побудував із ваших анотацій типів. Так само зробить і будь-який інший MCP-клієнт.
## Необов'язкові аргументи {#optional-arguments}
Дайте параметру типове значення — і він перестане бути обов'язковим. От і все. Це звичайний Python.
```python title="server.py" hl_lines="7"
--8<-- "docs_src/tools/tutorial002.py"
```
Схема змінюється відповідно:
```json
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"},
"limit": {"default": 10, "title": "Limit", "type": "integer"}
},
"required": ["query"],
"title": "search_booksArguments"
}
```
`limit` зник із `required` і отримав `"default": 10`. Клієнт, який його пропустить, отримає `10` — точнісінько як у Python.
## Багатші схеми з `Field` {#richer-schemas-with-field}
Анотації типів дають чимало, але іноді хочеться *описати* аргумент або обмежити його.
Загорніть тип в `Annotated` і додайте `Field` із Pydantic:
```python title="server.py" hl_lines="12-14"
--8<-- "docs_src/tools/tutorial003.py"
```
Три нові речі, усі — на параметрах:
* `Field(description=...)`: опис окремого аргументу, який модель читає разом із docstring.
* `Field(ge=1, le=50)`: числові межі. У схемі вони стають `"minimum": 1, "maximum": 50`.
* `Literal["fiction", "non-fiction", "poetry"]`: перелік (enum). Модель може вибрати лише одне з цих значень.
!!! check
Обмеження — не прикраса. Викличте інструмент із `limit=999` — і SDK відповість
помилкою інструмента **ще до запуску вашої функції**:
```text
Input should be less than or equal to 50
```
Ця помилка повертається моделі як результат інструмента; модель її читає і повторює виклик
із коректним значенням. Ви один раз написали `le=50` і задарма отримали агентів, що самі себе виправляють.
!!! info
Якщо ви користувалися FastAPI чи Pydantic, то все це вже знаєте. Той самий `Field`,
той самий `Annotated`, та сама валідація. Нічого специфічного для MCP тут вчити не треба.
## Модель як параметр {#a-model-as-a-parameter}
Коли інструмент приймає більше ніж кілька аргументів, згрупуйте їх у Pydantic-модель:
```python title="server.py" hl_lines="8-11 15"
--8<-- "docs_src/tools/tutorial004.py"
```
Схема `Book` вкладається у вхідну схему інструмента (як посилання `$defs`), модель заповнює її як JSON-об'єкт, а ваша функція отримує **справжній екземпляр `Book`**, уже провалідований, з атрибутами `.title`, `.author` і `.year`.
Можна поєднувати як завгодно: звичайні параметри поруч із параметрами-моделями, вкладені моделі, списки моделей. Це Pydantic аж до самого низу.
## `async def` {#async-def}
Якщо інструмент виконує ввід-вивід (викликає API, читає файл, робить запит до бази даних), оголосіть його як `async def` і використовуйте `await` всередині. SDK його дочекається.
Інструмент зі звичайним `def` теж працює: SDK запускає його в окремому потоці, тож він ніколи не блокує сервер.
Більше нічого налаштовувати не потрібно.
## Імена, заголовки й анотації {#names-titles-and-annotations}
Усе, що SDK виводить сам, можна перевизначити в декораторі:
```python title="server.py" hl_lines="7-10"
--8<-- "docs_src/tools/tutorial005.py"
```
* `title` — зрозуміла людині назва для інтерфейсів. Клієнти показують *«Search the catalog»* замість `search_books`.
* `annotations` — поведінкові **підказки** для клієнта:
* `read_only_hint=True`: цей інструмент нічого не змінює.
* `open_world_hint=False`: він працює із замкненою множиною речей (цим каталогом), а не з відкритим вебом.
* Дві інші, `destructive_hint` та `idempotent_hint`, описують інструмент, який *пише*: чи може він
щось видалити, і чи два виклики дають те саме, що й один? Специфікація визначає обидві
лише для інструментів, що не є read-only, тож на `search_books` вони нічого б не сказали.
Чемний клієнт використовує їх, щоб вирішувати на кшталт *«чи треба спитати користувача, перш ніж це запускати?»*. Це підказки, а не механізм безпеки. Ніколи не покладайтеся на те, що клієнт їх дотримається.
!!! tip
`@mcp.tool()` також приймає `name=` і `description=`, якщо ви не хочете виводити їх
з імені функції та docstring. Зазвичай хочете.
## Підсумки {#recap}
* `@mcp.tool()` на функції робить її інструментом. Ім'я — від функції, опис — із docstring.
* Анотації типів **і є** вхідною схемою. Типові значення роблять аргументи необов'язковими.
* `Annotated[..., Field(...)]` додає описи й обмеження; `Literal` додає переліки.
* Параметр — Pydantic-модель — це спосіб прийняти структуроване «тіло».
* Некоректні аргументи відхиляються за вас, із помилкою, яку модель може прочитати й після якої здатна відновитися.
* `async def` для вводу-виводу, звичайний `def` для всього іншого.
Що відбувається зі значенням, яке ви повертаєте через `return`, — на сторінці **[Структурований вивід](structured-output.md)**.

View file

@ -0,0 +1,283 @@
---
translation:
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
tool: 1
---
# URI-шаблони та безпека шляхів {#uri-templates-and-path-safety}
Це довідник із синтаксису URI-шаблонів, який приймає
[`@mcp.resource`](resources.md), і з політики безпеки шляхів,
яку SDK застосовує до видобутих значень. Щоб дізнатися, що таке
ресурси й коли їх використовувати, почніть зі сторінки
**[Ресурси](resources.md)**; ця сторінка передбачає, що ви вже впевнено оголошуєте
ресурси й хочете побачити повний набір операторів, параметри безпеки або
низькорівневу інтеграцію.
Синтаксис шаблонів описано в [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570).
SDK підтримує підмножину, відібрану для зіставлення вхідних URI запитів
`resources/read`, плюс шар безпеки, який відхиляє значення, що вказували б
за межі каталогу, який ви збираєтеся обслуговувати. Подробиці
протокольного рівня (формати повідомлень, життєвий цикл, пагінація)
дивіться в
[специфікації ресурсів MCP](https://modelcontextprotocol.io/specification/latest/server/resources).
## Повний набір операторів {#the-full-operator-set}
Простий заповнювач `{user_id}` — це той, який представлено на сторінці **[Ресурси](resources.md)**. Є ще чотири
форми операторів; ось вони всі на одному сервері, щоб їх можна було
порівняти поруч:
```python title="server.py" hl_lines="16-17 22-23 28-29 34-35 40-41"
--8<-- "docs_src/uri_templates/tutorial001.py"
```
Кожен виділений декоратор — це окремий спосіб розібрати URI на частини.
Розділи нижче розглядають їх згори донизу.
### Проста підстановка: `{name}` {#simple-expansion-name}
`books://{isbn}` — проста, повсякденна форма. Заповнювач відповідає
параметру `isbn`, тож клієнт, який читає `books://978-0441172719`, викликає
`get_book("978-0441172719")`.
Простий `{name}` зупиняється на першому `/`. `books://978/extra` не
збігається, бо скісна риска після `978` завершує захоплення, а `/extra`
лишається зайвим.
### Перетворення типів {#type-conversion}
Видобуті значення надходять як рядки, але можна оголосити точніший
тип, і SDK перетворить значення. `orders://{order_id}` потрапляє у функцію
з параметром `order_id: int`, тож читання `orders://12345` викликає
`get_order(12345)`, а не `get_order("12345")`. Обробник виконує над ним
арифметику (`order_id + 1`) без приведення типу.
### Багатосегментні шляхи: `{+name}` {#multi-segment-paths-name}
Щоб захопити значення, що містить скісні риски, використовуйте `{+name}`. З
шаблоном `manuals://{+path}`:
* `manuals://returns.md` дає `path = "returns.md"`
* `manuals://printing/setup.md` дає `path = "printing/setup.md"`
Беріть `{+name}` щоразу, коли значення ієрархічне: шляхи файлової
системи, вкладені ключі об'єктів, URL-шляхи, які ви проксіюєте.
### Параметри запиту: `{?a,b,c}` {#query-parameters-abc}
`reviews://{isbn}{?limit,sort}` розміщує `limit` і `sort` після `?`.
Шлях визначає, *яку* книжку читати; рядок запиту уточнює, *як* саме.
Параметри запиту зіставляються поблажливо: порядок не має значення, зайві
ігноруються, а пропущені беруться з типових значень функції. Тож
`reviews://978-0441172719` використовує `limit=10, sort="newest"`, а
`reviews://978-0441172719?sort=top` перевизначає лише `sort`.
### Сегменти шляху як список: `{/name*}` {#path-segments-as-a-list-name}
Якщо кожен сегмент шляху потрібен як окремий елемент списку, а не як один
рядок зі скісними рисками, використовуйте `{/name*}`. З шаблоном
`shelves://browse{/path*}` клієнт, який читає `shelves://browse/fiction/sci-fi`,
викликає `browse_shelf(["fiction", "sci-fi"])`.
### Довідник із шаблонів {#template-reference}
Найуживаніші шаблони:
| Шаблон | Приклад вхідних даних | Результат |
|--------------|-----------------------|-------------------------|
| `{name}` | `alice` | `"alice"` |
| `{name}` | `docs/intro.md` | *немає збігу* (зупиняється на `/`) |
| `{+path}` | `docs/intro.md` | `"docs/intro.md"` |
| `{.ext}` | `.json` | `"json"` |
| `{/segment}` | `/v2` | `"v2"` |
| `{?key}` | `?key=value` | `"value"` |
| `{?a,b}` | `?a=1&b=2` | `"1"`, `"2"` |
| `{/path*}` | `/a/b/c` | `["a", "b", "c"]` |
### Що відхиляє парсер {#what-the-parser-rejects}
Деякі форми шаблонів виявляються наперед, а не збоять на першому
запиті. `@mcp.resource` розбирає шаблон під час виконання декоратора,
тож жодна з них ніколи не дістанеться сервера, що працює.
`UriTemplate.parse()` викидає `InvalidUriTemplate` у таких випадках:
* **Дві змінні без нічого між ними.** `manuals://{+path}{ext}`
відхиляється: під час зіставлення неможливо визначити, де закінчується
`path` і починається `ext`. Поставте між ними літерал
(`manuals://{+path}/{ext}`) або скористайтеся оператором, який сам
дає роздільник. `manuals://{+path}{.ext}` приймається, бо `{.ext}`
сам додає `.`.
* **Більш ніж одна багатосегментна змінна.** Щонайбільше одна з `{+var}`,
`{#var}` або змінна з explode-модифікатором (`{/var*}`, `{.var*}`, `{;var*}`)
на шаблон. Дві — за своєю природою неоднозначні: немає обґрунтованого
способу вирішити, яка з них поглине зайвий сегмент.
* **Звичайні синтаксичні помилки**: незакрита фігурна дужка, двічі
використане ім'я змінної або можливість RFC 6570, яку SDK не підтримує,
як-от модифікатор префікса `{var:3}` чи explode у параметрах запиту
`{?vars*}`.
Крім того, `@mcp.resource` викидає `ValueError`, коли параметр обробника
прив'язаний до змінної запиту в кінцевій групі `{?...}`/`{&...}` шаблону,
але не має типового значення в Python. Ці змінні зіставляються поблажливо
(клієнт може пропустити будь-яку з них), тож параметр без типового
значення проявився б лише як незрозуміла внутрішня помилка на першому
запиті, що його пропускає. `reviews://{isbn}{?limit,sort}` на сервері
вище — правильно сформована версія: і `limit`, і `sort` мають типові
значення.
## Безпека {#security}
Параметри шаблону надходять від клієнта. Якщо вони без перевірки
потрапляють в операції з файловою системою чи базою даних, значення на
кшталт `../../etc/passwd` можуть вказати за межі каталогу, який ви
збиралися обслуговувати.
### Що SDK перевіряє за замовчуванням {#what-the-sdk-checks-by-default}
Перш ніж запуститься обробник, SDK відхиляє будь-який параметр, що:
* виходив би за межі початкового каталогу через компоненти `..`
* схожий на абсолютний шлях (`/etc/passwd`, `C:\Windows`) або шлях
Windows відносно диска (`C:foo`). Значення відносно диска та
ідентифікатор із простором імен на кшталт `x:y` як рядки нерозрізненні,
тому будь-яке значення «одна літера плюс двокрапка» за замовчуванням
відхиляється; звільніть параметр від перевірки, якщо він закономірно
отримує такі значення
* містить нульовий байт (`\x00`)
Перевірка на `..` працює з компонентами шляху, а не шукає підрядок.
Значення на кшталт `v1.0..v2.0` чи `HEAD~3..HEAD` проходять, бо `..` там
не є окремим сегментом шляху.
Ці перевірки застосовуються до декодованого значення, тож вони ловлять
обхід каталогів незалежно від того, як його закодовано в URI (`../etc`,
`..%2Fetc`, `%2E%2E/etc`, `..%5Cetc`, `%00` — усе це виявляється).
!!! check
Прочитайте `manuals://../etc/passwd` із сервера вище — і запит буде
відхилено одразу: зіставлення шаблонів зупиняється на першій невдачі,
тож жоден наступний (потенційно поблажливіший) шаблон не випробовується
як запасний. Клієнт бачить ту саму помилку `-32602` «Unknown resource»,
що й для URI, який не збігається з жодним шаблоном, а `read_manual`
ніколи не запускається.
### Обробники файлової системи: використовуйте safe_join {#filesystem-handlers-use-safe_join}
Вбудовані перевірки зупиняють типові випадки, але не можуть знати меж
вашої пісочниці. Для доступу до файлової системи використовуйте
`safe_join`, щоб розв'язати шлях і впевнитися, що він лишається всередині
базового каталогу:
```python title="server.py" hl_lines="4 14"
--8<-- "docs_src/uri_templates/tutorial002.py"
```
`safe_join` ловить виходи через символьні посилання, послідовності `..`
і трюки з абсолютними шляхами, які проста перевірка рядка пропустила б.
Якщо розв'язаний шлях виходить за межі `DOCS_ROOT`, функція викидає
`PathEscapeError`, який дістається клієнта як `ResourceError`.
### Коли типова поведінка заважає {#when-the-defaults-get-in-the-way}
Іноді перевірки блокують закономірні значення. Інструмент імпорту
каталогу може навмисно отримувати абсолютний шлях, або параметр може бути
відносним посиланням на кшталт `../sibling`, яке обробник безпечно
тлумачить, не торкаючись файлової системи. Звільніть такий параметр від
перевірки або послабте політику для всього сервера:
```python title="server.py" hl_lines="9 16-19"
--8<-- "docs_src/uri_templates/tutorial003.py"
```
* `security=ResourceSecurity(exempt_params={"source"})` на декораторі
пропускає перевірки для цього одного параметра цього одного ресурсу.
Решта сервера зберігає типову політику.
* `resource_security=` у конструкторі `MCPServer` задає типову політику
для кожного ресурсу. Тут `relaxed` повністю вимикає перевірку на `..`.
Налаштовувані перевірки:
| Налаштування | За замовчуванням | Що робить |
|-------------------------|---------|-------------------------------------|
| `reject_path_traversal` | `True` | Відхиляє послідовності `..`, що виходять за межі початкового каталогу |
| `reject_absolute_paths` | `True` | Відхиляє `/foo`, `C:\foo`, UNC-шляхи та відносний щодо диска `C:foo` (також ловить `x:y`) |
| `reject_null_bytes` | `True` | Відхиляє значення, що містять `\x00` |
| `exempt_params` | порожньо | Імена параметрів, для яких перевірки пропускаються |
Ці перевірки — евристичний попередній фільтр; для доступу до файлової
системи межею ізоляції лишається `safe_join`.
!!! tip
Якщо обробник не може виконати запит (файл не існує, ідентифікатор
невідомий), викиньте виняток. SDK перетворить його на відповідь з
помилкою. Про різницю між помилкою протоколу та помилкою інструмента —
на сторінці **[Обробка помилок](handling-errors.md)**.
## Ресурси на низькорівневому Server {#resources-on-the-low-level-server}
Якщо ви будуєте на низькорівневому `Server` (див. **[Низькорівневий
Server](../advanced/low-level-server.md)**), обробники для протокольних методів
`resources/list` і `resources/read` реєструються напряму. Декоратора
немає; типи протоколу ви повертаєте самі.
### Статичні ресурси {#static-resources}
Для фіксованих URI тримайте реєстр і диспетчеризуйте за точним збігом:
```python title="server.py" hl_lines="17 21 27"
--8<-- "docs_src/uri_templates/tutorial004.py"
```
Обробник списку повідомляє клієнтам, що доступно; обробник читання віддає
вміст. Спершу перевірте реєстр, далі перейдіть до шаблонів (нижче), якщо
вони є, а для всього іншого викидайте виняток.
### Шаблони {#templates}
Рушій шаблонів, який використовує `MCPServer`, міститься в
`mcp.shared.uri_template` і працює самостійно. Розбір і зіставлення ті
самі; маршрутизацію та політику безпеки ви під'єднуєте самі.
```python title="server.py" hl_lines="13-16 22-25 29 33 45"
--8<-- "docs_src/uri_templates/tutorial005.py"
```
У виділених рядках відбуваються три речі:
* **Розбір один раз, зіставлення на кожен запит.** `UriTemplate.parse()`
будує шаблон; `template.match(uri)` повертає видобуті змінні як `dict`
або `None`, якщо URI не підходить. Декодування URL відбувається всередині
`match()`; декодовані значення повертаються як є, без перевірки безпеки
шляхів. Значення виходять рядками: перетворюйте їх самі
(`int(matched["id"])`, `Path(matched["path"])`).
* **Застосовуйте перевірки безпеки самі.** Перевірки на `..` та абсолютні
шляхи, які `MCPServer` виконує за замовчуванням, містяться в
`mcp.shared.path_security`. `read_manual_safely` викликає їх, перш ніж
торкнутися `MANUALS`. Якщо параметр не є шляхом файлової системи (ISBN,
пошуковий запит), пропустіть перевірки для цього значення: політикою ви
керуєте в кожному обробнику окремо, а не через об'єкт конфігурації.
* **Список шаблонів із того самого джерела.** Клієнти дізнаються про
шаблони через `resources/templates/list`. `str(template)` повертає
початковий рядок шаблону, тож список і зіставлювач мають одне джерело
істини.
## Підсумки {#recap}
* `{name}` збігається з одним сегментом; `{+name}` зберігає скісні риски;
`{?a,b}` бере значення з рядка запиту; `{/name*}` розбиває сегменти на
список.
* Дві змінні без нічого між ними або друга багатосегментна змінна
відхиляються під час розбору. Параметр, прив'язаний до змінної запиту в
кінцевій групі `{?...}`/`{&...}`, мусить оголошувати типове значення в
Python.
* Анотуйте параметр (`order_id: int`) — і SDK перетворить значення.
* Типова політика безпеки відхиляє `..`, абсолютні шляхи та нульові
байти, перш ніж запуститься обробник; перевизначайте для окремого
ресурсу через `security=ResourceSecurity(...)` або для всього сервера
через `resource_security=`.
* Для доступу до файлової системи межею ізоляції є `safe_join`.
* На низькорівневому `Server` розбирайте через `UriTemplate.parse()`,
зіставляйте через `.match()` і застосовуйте `mcp.shared.path_security`
самі.