docs: refresh translations for recent English changes (#3379)
This commit is contained in:
parent
d8b638304e
commit
c5d7d0b6d0
18 changed files with 3149 additions and 1047 deletions
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
|
||||
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
|
||||
tool: 1
|
||||
---
|
||||
# Низькорівневий Server {#the-low-level-server}
|
||||
|
|
@ -116,6 +116,17 @@ asyncio.run(main())
|
|||
|
||||
Сервер ніколи не порівнює ці два поля. `Client` цього SDK — порівнює: поверніть `structured_content`, що не відповідає оголошеній вами `output_schema`, і `call_tool` викине `RuntimeError`, який починається з `Invalid structured content returned by tool search_books` і далі цитує помилку `jsonschema`. Пообіцяти схему легко; дотримати її — ваша справа. Уся драбина типів повернення та схем — на сторінці **[Структурований вивід](../servers/structured-output.md)**.
|
||||
|
||||
## Діалект — JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12}
|
||||
|
||||
`input_schema` і `output_schema` — це JSON Schema, а [специфікація MCP](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) фіксує діалект: схема без ключа `$schema` — це **JSON Schema 2020-12**. Схеми, які генерує `MCPServer`, покладаються на це типове значення (Pydantic пише 2020-12 і пропускає ключ), і написаний вручну словник теж має його дотримуватися, тож доступний увесь набір ключових слів 2020-12:
|
||||
|
||||
```python title="server.py" hl_lines="8 14-15"
|
||||
--8<-- "docs_src/lowlevel/tutorial007.py"
|
||||
```
|
||||
|
||||
* Корінь `input_schema` мусить бути `"type": "object"`. Поруч із ним `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` з локальними `$ref` та решта ключових слів 2020-12 доходять до клієнта точно так, як написано.
|
||||
* Ключ `$schema` не потрібен. Додавайте його лише щоб перейти на давнішу чернетку стандарту: `Client` цього SDK, який перевіряє `structured_content` за `output_schema` інструмента, обирає валідатор за `$schema` і використовує 2020-12, коли ключа немає.
|
||||
|
||||
## `_meta`: для застосунку, а не для моделі {#\_meta-for-the-application-not-the-model}
|
||||
|
||||
`content` — це частина відповіді, яку читає модель. `structured_content` — та сама відповідь у вигляді типізованих даних. `_meta` — третій канал: дані, що їдуть разом із результатом для **клієнтського застосунку** і взагалі не є частиною відповіді.
|
||||
|
|
@ -167,7 +178,7 @@ asyncio.run(main())
|
|||
--8<-- "docs_src/lowlevel/tutorial006.py"
|
||||
```
|
||||
|
||||
* Перший аргумент — рядок методу. Для сповіщень є близнюк — `add_notification_handler`.
|
||||
* Перший аргумент — рядок методу. Для сповіщень є близнюк — `add_notification_handler`. Його обробники спрацьовують на stdio та на HTTP-з'єднаннях покоління рукостискання; на шляху Streamable HTTP версії `2026-07-28` POST-запит клієнта зі сповіщенням підтверджується кодом `202` і не диспетчеризується, бо ця редакція не визначає сповіщень від клієнта до сервера через HTTP.
|
||||
* `params_type` — це модель, за якою вхідні `params` перевіряються **до** запуску вашого обробника, тож власні методи *отримують* перевірку, якої інструменти не мають. Успадковуйтеся від `RequestParams`, щоб поле `_meta` розбиралося так само, як у кожного іншого методу.
|
||||
* Обробник повертає `BaseModel`, `dict` або `None`. SDK серіалізує це в результат JSON-RPC.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
|
||||
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
|
||||
tool: 1
|
||||
---
|
||||
# Middleware {#middleware}
|
||||
|
|
@ -33,8 +33,8 @@ translation:
|
|||
рядок методу; `ctx.params` — сирі параметри, **до** будь-якої валідації.
|
||||
* `call_next(ctx)` запускає решту ланцюжка: валідацію, пошук обробника, сам обробник.
|
||||
Поверніть те, що він повернув, — і відповідь залишиться недоторканою.
|
||||
* `try`/`finally` тут навмисно: обробник, що викидає виняток, однаково буде заміряно, бо збій
|
||||
доходить до middleware як виняток із `call_next`.
|
||||
* `try`/`finally` тут навмисно: для обробника, що викидає виняток, час однаково буде заміряно,
|
||||
бо збій доходить до middleware як виняток із `call_next`.
|
||||
* `server.middleware.append(...)` реєструє її. Список виконується від зовнішнього до
|
||||
внутрішнього, тож `middleware[0]` — найближча до мережі.
|
||||
|
||||
|
|
@ -55,8 +55,11 @@ tools/call took 0.1 ms
|
|||
|
||||
* Установлення з'єднання: `server/discover`, або `initialize` і `notifications/initialized`
|
||||
у сесії старого покоління.
|
||||
* Кожен запит і кожне сповіщення. Для сповіщення `ctx.request_id is None`,
|
||||
`call_next(ctx)` повертає `None`, а все, що повернете ви, відкидається.
|
||||
* Кожен запит і кожне сповіщення, що доходить до сервера. Для сповіщення
|
||||
`ctx.request_id is None`, `call_next(ctx)` повертає `None`, а все, що повернете ви,
|
||||
відкидається. (На шляху Streamable HTTP версії `2026-07-28` POST зі сповіщенням від клієнта
|
||||
транспорт підтверджує кодом `202` і ніколи не передає далі, тож до middleware воно теж не
|
||||
доходить; ця ревізія не визначає жодних сповіщень від клієнта до сервера через HTTP.)
|
||||
* Навіть метод, для якого сервер не має обробника: `call_next` викидає
|
||||
`MCPError(-32601, "Method not found")` *крізь* ваш middleware на шляху до клієнта.
|
||||
|
||||
|
|
@ -115,8 +118,9 @@ SDK постачає рівно один шар middleware, і він уже є
|
|||
* Middleware — це `async (ctx, call_next) -> result`; його передають як
|
||||
`MCPServer(middleware=[...])` (або додають до `mcp.middleware`), а в низькорівневому `Server`
|
||||
додають до `server.middleware`.
|
||||
* Він огортає **кожне** вхідне повідомлення (`server/discover`, `initialize`, запити,
|
||||
сповіщення, невідомі методи) і виконується від зовнішнього до внутрішнього.
|
||||
* Він огортає **кожне** вхідне повідомлення, що доходить до сервера (`server/discover`,
|
||||
`initialize`, запити, сповіщення, невідомі методи), і виконується від зовнішнього до
|
||||
внутрішнього.
|
||||
* `ctx.request_id is None` — так відрізняють сповіщення від запиту.
|
||||
* Викиньте виняток замість виклику `call_next`, щоб відхилити одне повідомлення; з'єднання
|
||||
вціліє.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ebef1e7a0df854f4, a4c687d3d627d516, 8e79141fc2985342, b345dd05b9c3c7ab, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||||
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||||
tool: 1
|
||||
---
|
||||
# Клієнт {#the-client}
|
||||
|
|
@ -27,9 +27,10 @@ translation:
|
|||
|
||||
* Екземпляр `MCPServer` (або низькорівневого `Server`): під'єднання **в межах процесу**.
|
||||
* Рядок з URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, шлях для робочого розгортання.
|
||||
* **Транспорт**: будь-що, що можна використати як `async with ... as (read, write)`, наприклад `stdio_client(...)`, що обгортає підпроцес.
|
||||
* `StdioServerParameters`: команда, яку буде запущено як **підпроцес**; спілкування з ним іде через його stdin і stdout.
|
||||
* **Транспорт**: будь-що, що можна використати як `async with ... as (read, write)`, наприклад `streamable_http_client(url, http_client=...)` навколо вашого власного HTTP-клієнта.
|
||||
|
||||
Усе інше на цій сторінці однакове для всіх трьох. Заголовки, підпроцеси, тайм-аути та протокол `Transport` мають власну сторінку: **[Транспорти клієнта](transports.md)**.
|
||||
Усе інше на цій сторінці однакове для всіх чотирьох. Заголовки, підпроцеси, тайм-аути та протокол `Transport` мають власну сторінку: **[Транспорти клієнта](transports.md)**.
|
||||
|
||||
### Що є в під'єднаного клієнта {#whats-on-a-connected-client}
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ tool.description # 'Search the catalog by title or author.'
|
|||
|
||||
`call_tool(name, arguments)` запускає інструмент і повертає `CallToolResult`.
|
||||
|
||||
```python title="client.py" hl_lines="26-33"
|
||||
```python title="client.py" hl_lines="27-34"
|
||||
--8<-- "docs_src/client/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -117,7 +118,7 @@ result.is_error # False
|
|||
|
||||
!!! check
|
||||
Попросіть у `lookup_book` `"Solaris"` (назву, якої немає в каталозі) — і функція викине
|
||||
`ValueError`. Виклик усе одно повернеться нормально:
|
||||
`ToolError`. Виклик усе одно повернеться нормально:
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
|
|
@ -125,9 +126,10 @@ result.is_error # False
|
|||
result.structured_content # None
|
||||
```
|
||||
|
||||
Повідомлення винятку потрапило в `content`, де його може прочитати **модель** і спробувати ще раз. Це
|
||||
навмисно: помилка інструмента — частина розмови, а не аварія. Завжди дивіться на `is_error`,
|
||||
перш ніж довіряти `structured_content`.
|
||||
Повідомлення `ToolError` потрапило в `content`, де його може прочитати **модель** і спробувати ще раз. Це
|
||||
навмисно: помилка інструмента — частина розмови, а не аварія. (Якби інструмент упав
|
||||
з якимось іншим винятком, у `content` було б лише `Error executing tool lookup_book`.) Завжди дивіться на
|
||||
`is_error`, перш ніж довіряти `structured_content`.
|
||||
|
||||
!!! warning
|
||||
`is_error=True` охоплює більше, ніж ваш власний `raise`. Попросіть інструмент, якого в сервера
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
|
||||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
|
||||
tool: 1
|
||||
---
|
||||
# Транспорти клієнта {#client-transports}
|
||||
|
|
@ -87,15 +87,15 @@ translation:
|
|||
|
||||
Сервер **stdio** — це підпроцес. Клієнт запускає його, пише JSON-RPC в його stdin і читає JSON-RPC з його stdout. Саме так десктопний хост запускає сервер на вашій машині: хост і *є* цим кодом плюс UI, а сторінка **[Під'єднання до справжнього хоста](../get-started/real-host.md)** показує ті самі стосунки з боку хоста — як конфігураційний файл.
|
||||
|
||||
Опишіть процес за допомогою `StdioServerParameters`, перетворіть його на транспорт через `stdio_client` і передайте *це* в `Client`:
|
||||
Опишіть процес за допомогою `StdioServerParameters` і передайте його в `Client`:
|
||||
|
||||
```python title="client.py" hl_lines="4-8 12"
|
||||
```python title="client.py" hl_lines="3-7 11"
|
||||
--8<-- "docs_src/client_transports/tutorial004.py"
|
||||
```
|
||||
|
||||
`Client` не приймає сам об'єкт параметрів. `StdioServerParameters` — це конфігурація; `stdio_client(server)` — транспорт, який уміє запустити з неї процес. Завжди загортайте.
|
||||
Вхід у блок запускає процес. Вихід із нього завершує підпроцес: закриває stdin, чекає, вбиває, якщо той затримується. Прибирати за ним самостійно ніколи не доведеться.
|
||||
|
||||
Вихід із блоку `async with` також завершує підпроцес: закриває stdin, чекає, вбиває, якщо той затримується. Прибирати за ним самостійно ніколи не доведеться.
|
||||
stderr дочірнього процесу йде у ваш. Щоб спрямувати його деінде, зберіть транспорт самостійно через `stdio_client` (з `mcp`) і передайте натомість його: `Client(stdio_client(server, errlog=log_file))`.
|
||||
|
||||
!!! warning
|
||||
Дочірній процес **не** успадковує ваше середовище. Він отримує мінімальний список дозволених змінних (`HOME`, `LOGNAME`,
|
||||
|
|
@ -113,16 +113,16 @@ translation:
|
|||
|
||||
Для `Client` усе перелічене вище — одне й те саме.
|
||||
|
||||
**Транспорт** — це будь-який асинхронний контекстний менеджер, що повертає пару потоків повідомлень `(read, write)`: формально — протокол `Transport` у `mcp.client`. `Client` розв'язує свій аргумент за типом: об'єкт сервера під'єднується в межах процесу, `str` стає `streamable_http_client(url)`, а в будь-що інше він входить безпосередньо як у транспорт. Саме завдяки останньому правилу `stdio_client(...)`, `streamable_http_client(...)` і `sse_client(...)` стають на одне й те саме місце — і саме тому можна написати власний.
|
||||
**Транспорт** — це будь-який асинхронний контекстний менеджер, що повертає пару потоків повідомлень `(read, write)`: формально — протокол `Transport` у `mcp.client`. `Client` розв'язує свій аргумент за типом: об'єкт сервера під'єднується в межах процесу, `str` стає `streamable_http_client(url)`, `StdioServerParameters` стає `stdio_client(params)`, а в будь-що інше він входить безпосередньо як у транспорт. Саме завдяки останньому правилу `stdio_client(...)`, `streamable_http_client(...)` і `sse_client(...)` стають на одне й те саме місце — і саме тому можна написати власний.
|
||||
|
||||
## Підсумки {#recap}
|
||||
|
||||
* `Client(mcp)` (об'єкт сервера) під'єднується в пам'яті. Використовуйте для тестів і для вбудовування.
|
||||
* `Client("http://.../mcp")` (URL) під'єднується через Streamable HTTP, продакшен-транспорт.
|
||||
* Заголовки, автентифікація, проксі й тайм-аути належать `httpx2.AsyncClient`, який ви передаєте в `streamable_http_client(url, http_client=...)`. Іменованого аргументу `headers=` немає.
|
||||
* stdio — це `Client(stdio_client(StdioServerParameters(...)))`, ніколи не сам об'єкт параметрів.
|
||||
* stdio — це `Client(StdioServerParameters(...))`. Загортайте його в `stdio_client(...)` самостійно лише для того, щоб перенаправити stderr дочірнього процесу.
|
||||
* Підпроцес отримує середовище зі списку дозволених, а не ваше; `env=` його доповнює.
|
||||
* Транспорт — це будь-що, з чим можна зробити `async with x as (read, write)`. Усе, що не є об'єктом сервера чи URL, `Client` передає прямо цьому протоколу.
|
||||
* Транспорт — це будь-що, з чим можна зробити `async with x as (read, write)`. Усе, що не є об'єктом сервера, URL чи `StdioServerParameters`, `Client` передає прямо цьому протоколу.
|
||||
* Створення `Client` обирає транспорт. `async with` його відкриває.
|
||||
|
||||
Щойно транспорт відкрито, обидві сторони мають домовитися про версію протоколу. Зазвичай про це не думаєш; а коли доводиться — є сторінка **[Версії протоколу](../protocol-versions.md)**.
|
||||
Щойно транспорт відкрито, обидві сторони мають домовитися про версію протоколу. Зазвичай про це й не згадуєте; а коли все ж доведеться — є сторінка **[Версії протоколу](../protocol-versions.md)**.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
translation:
|
||||
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
|
||||
sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931]
|
||||
tool: 1
|
||||
---
|
||||
# Застарілі можливості {#deprecated-features}
|
||||
|
||||
Специфікація 2026-07-28 виводить з ужитку п'ять речей. SDK і далі реалізує кожну з них, і кожна тепер супроводжується **попередженням про застарілість**.
|
||||
Специфікація 2026-07-28 виводить з ужитку п'ять речей. SDK і далі реалізує кожну з них, і кожна тепер супроводжується **попередженням про застарілість**. Один допоміжний метод SDK оголошено застарілим окремо, і його наведено [наприкінці сторінки](#deprecated-sdk-helpers).
|
||||
|
||||
Таблиця нижче називає кожну застарілу можливість, пояснює, чому вона зникає, і вказує заміну, на яку варто спиратися.
|
||||
|
||||
|
|
@ -56,6 +56,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE
|
|||
потім намагається надіслати запит. Ці два методи працюють від початку до кінця лише на
|
||||
з'єднанні з `mode="legacy"`, клієнт якого зареєстрував відповідний колбек.
|
||||
|
||||
## `ping` у сесії старого покоління {#ping-on-a-legacy-session}
|
||||
|
||||
**Ping** — це порожній запит, який може надіслати будь-яка сторона, щоб перевірити, чи інша ще відповідає. Специфікація 2026-07-28 його вилучає ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): кожен запит, який надсилає сучасний клієнт, уже доводить, що сервер на місці, а сучасний сервер не має каналу, яким міг би надіслати свій. Обидва методи SDK і далі працюють у сесії покоління з рукостисканням. З боку клієнта:
|
||||
|
||||
```python
|
||||
async def main() -> None:
|
||||
async with Client("http://localhost:8000/mcp", mode="legacy") as client:
|
||||
await client.send_ping() # warns; returns an EmptyResult
|
||||
```
|
||||
|
||||
А з боку сервера, усередині будь-якого обробника:
|
||||
|
||||
```python
|
||||
@mcp.tool()
|
||||
async def check_client(ctx: Context) -> str:
|
||||
"""A tool that still pings the client mid-call."""
|
||||
await ctx.session.send_ping() # no warning; an EmptyResult while the client is connected
|
||||
return "client answered"
|
||||
```
|
||||
|
||||
* `client.send_ping()` попереджає через `MCPDeprecationWarning` під час кожного виклику. На з'єднанні за замовчуванням (`2026-07-28`) сервер натомість відповідає `MCPError: Method not found`.
|
||||
* `ctx.session.send_ping()` попередження не має. На сучасному з'єднанні він викидає ту саму помилку про відсутність зворотного каналу (back-channel), що й будь-який інший запит з ініціативи сервера.
|
||||
* Жодна зі сторін нічого не реєструє, щоб відповідати на ping.
|
||||
|
||||
## Сповіщення про зміну кореневих каталогів {#roots-change-notifications}
|
||||
|
||||
Клієнт покоління 2025, який оголосив можливість кореневих каталогів, може повідомити серверу, що теки його робочого простору змінилися, надіславши `notifications/roots/list_changed`; сервер у відповідь знову запитує `roots/list`. Специфікація 2026-07-28 вилучає це сповіщення разом із рештою push-варіанту роботи з кореневими каталогами. На клієнті саме передавання `list_roots_callback=` (**[Колбеки клієнта](client/callbacks.md)**) оголошує `"roots": {"listChanged": true}`, а один виклик дотримує цієї обіцянки:
|
||||
|
||||
```python
|
||||
async def open_folder(client: Client, uri: str, name: str) -> None:
|
||||
"""The user opened another folder: expose it through the roots callback, then tell the server."""
|
||||
workspace.append(Root(uri=FileUrl(uri), name=name))
|
||||
await client.send_roots_list_changed()
|
||||
```
|
||||
|
||||
На боці сервера обробник, що приймає це сповіщення, передають у низькорівневий `Server`:
|
||||
|
||||
```python
|
||||
async def roots_changed(ctx: ServerRequestContext, params: NotificationParams | None) -> None:
|
||||
"""The client's roots changed: ask for the new list."""
|
||||
roots = (await ctx.session.list_roots()).roots
|
||||
|
||||
|
||||
server = Server("Bookshop", on_roots_list_changed=roots_changed)
|
||||
```
|
||||
|
||||
* `workspace` — це список, який повертає ваш `list_roots_callback`. `client.send_roots_list_changed()` попереджає і потребує клієнта з `mode="legacy"`: на сучасному з'єднанні сповіщення мовчки відкидається. Після цього не закривайте сесію, бо наступний запит сервера `roots/list` надходить саме нею.
|
||||
* `MCPServer` не має гачка для цього сповіщення. У низькорівневому `Server` обробник реєструє параметр `on_roots_list_changed=` (теж застарілий, він попереджає під час створення екземпляра). Сповіщення не несе корисного навантаження, тож обробник викликає `ctx.session.list_roots()`, щоб отримати новий список.
|
||||
|
||||
## Приглушення попередження {#silencing-the-warning}
|
||||
|
||||
У новому коді — не робіть цього.
|
||||
|
|
@ -76,23 +125,33 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
|
|||
Розверніть фільтр у зворотний бік — і отримаєте безкоштовний регресійний тест. Додайте
|
||||
`"error::mcp.MCPDeprecationWarning"` до налаштування `filterwarnings` у конфігурації
|
||||
pytest — і застарілий виклик **викидатиме виняток** замість попередження. Інструмент
|
||||
з назвою `old_log`, який досі викликає `ctx.info()`, перестає проходити тест і починає
|
||||
повідомляти:
|
||||
з назвою `old_log`, який досі викликає `ctx.info()`, перестає проходити тест: виклик
|
||||
повертається з `is_error=True` і текстом `Error executing tool old_log`, а захоплений
|
||||
лог сервера називає винуватця:
|
||||
|
||||
```text
|
||||
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
|
||||
mcp.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
|
||||
```
|
||||
|
||||
Один рядок конфігурації pytest — і застарілий виклик більше ніколи не прокрадеться назад
|
||||
у вашу кодову базу, не проваливши тест.
|
||||
|
||||
## Застарілі допоміжні методи SDK {#deprecated-sdk-helpers}
|
||||
|
||||
Це не зміни специфікації, а лише внутрішні частини SDK, для яких є краща заміна. Вони попереджають тим самим `MCPDeprecationWarning` і будуть вилучені у версії 3.0.
|
||||
|
||||
| Застаріле | Що робити натомість |
|
||||
|---|---|
|
||||
| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()`, а потім `FuncMetadata.call_fn()`. Його викликав лише код, що працює з `FuncMetadata` безпосередньо (скажімо, власний підклас `Tool`). |
|
||||
|
||||
## Підсумки {#recap}
|
||||
|
||||
* Специфікація 2026-07-28 оголошує застарілими **кореневі каталоги**, **семплювання** з ініціативи сервера та протокольне **логування** (усе — [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), обмежує **перебіг виконання** напрямком від сервера до клієнта й вилучає **`ping`**.
|
||||
* Стовпець із замінами вказує, куди рухатися далі: **[Багатораундові запити](handlers/multi-round-trip.md)** для семплювання й кореневих каталогів, **[Логування](handlers/logging.md)** для логування, **[Перебіг виконання](handlers/progress.md)** для перебігу виконання. `ping` не потребує взагалі нічого.
|
||||
* Застарілість має рекомендаційний характер: жодних змін у переданих даних, усе й далі працює із сесіями до 2026, а ви отримуєте помітне попередження `MCPDeprecationWarning` (це `UserWarning`, тож воно ввімкнене за замовчуванням).
|
||||
* Семплювання й кореневі каталоги додатково потребують зворотного каналу (back-channel), якого сесія 2026-07-28 не має. На сучасному з'єднанні вони попереджають, а потім викидають виняток.
|
||||
* Семплювання й кореневі каталоги додатково потребують зворотного каналу, якого сесія 2026-07-28 не має. На сучасному з'єднанні вони попереджають, а потім викидають виняток.
|
||||
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` приглушує всю категорію; `"error::mcp.MCPDeprecationWarning"` у pytest перетворює її на провал тесту.
|
||||
* Один допоміжний метод SDK, `FuncMetadata.call_fn_with_arg_validation()`, оголошено застарілим окремо — його вилучать у версії 3.0.
|
||||
* Новий код не варто будувати на жодній із цих можливостей.
|
||||
|
||||
Усі інші сторінки цієї документації навчають чинного API.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
|
||||
sections: [3c4f2f06b4e978b6, 51ea5fbcb0e93563, 32d8808606ffdae0, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
|
||||
tool: 1
|
||||
---
|
||||
# Підключення до справжнього хоста {#connect-to-a-real-host}
|
||||
|
|
@ -11,7 +11,7 @@ translation:
|
|||
|
||||
## Один сервер, усі хости {#one-server-every-host}
|
||||
|
||||
```python title="server.py" hl_lines="3 33-34"
|
||||
```python title="server.py" hl_lines="4 34-35"
|
||||
--8<-- "docs_src/real_host/tutorial001.py"
|
||||
```
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
|||
|
||||
А хост — це не більше ніж застосунок з MCP-клієнтом усередині, тож роль хоста може
|
||||
зіграти й ваш власний Python: сторінка **[Транспорти клієнта](../client/transports.md)** запускає
|
||||
цей самий файл як підпроцес через `stdio_client(...)`, а **[Тестування](testing.md)**
|
||||
цей самий файл як підпроцес через `Client(StdioServerParameters(...))`, а **[Тестування](testing.md)**
|
||||
підключається до нього в пам'яті взагалі без процесу.
|
||||
|
||||
## Claude Desktop {#claude-desktop}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
|
||||
tool: 1
|
||||
---
|
||||
# Тестування {#testing}
|
||||
|
|
@ -84,13 +84,13 @@ async def test_call_add_tool(client: Client):
|
|||
Піти не так можуть дві різні речі, і цей прапорець стосується лише однієї з них.
|
||||
|
||||
Виняток усередині одного з **ваших інструментів** — це не збій протоколу. Він стає звичайним результатом
|
||||
з `is_error=True`, і модель читає повідомлення. `raise_exceptions` цього не змінює: з ним чи
|
||||
без нього `call_tool` повертає той самий результат з `is_error=True`. Про це є ціла сторінка:
|
||||
з `is_error=True` (а якщо це був `ToolError`, модель прочитає ваше повідомлення). `raise_exceptions` цього не
|
||||
змінює: з ним чи без нього `call_tool` повертає той самий результат з `is_error=True`. Про це є ціла сторінка:
|
||||
**[Обробка помилок](../servers/handling-errors.md)**.
|
||||
|
||||
Збій **поза** тілом інструмента — інша річ. На з'єднанні, яке дає `Client(mcp)`, сервер
|
||||
замінює його загальним `"Internal server error"`, перш ніж його побачить клієнт. Ніколи не слід
|
||||
розкривати подробиці неочікуваного падіння віддаленій стороні, що викликає. У тесті це саме те,
|
||||
замінює його загальним `"Internal server error"`, перш ніж його побачить клієнт. Ніколи не
|
||||
розкривайте подробиці неочікуваного падіння віддаленій стороні, що викликає. У тесті це саме те,
|
||||
чого ви *не* хочете, і саме це змінює `raise_exceptions=True`: тест бачить справжнє повідомлення
|
||||
замість узагальненого.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
|
||||
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
|
||||
tool: 1
|
||||
---
|
||||
# Еліцитація {#elicitation}
|
||||
|
|
@ -89,7 +89,8 @@ translation:
|
|||
!!! warning
|
||||
Схема еліцитації не така виразна, як вхідна схема інструмента. Лише пласкі примітивні поля:
|
||||
`str`, `int`, `float`, `bool` або `Literal` з рядків (він стає `enum`).
|
||||
Покладіть модель усередину моделі — і `ctx.elicit` викине виняток ще до того, як щось буде надіслано клієнтові:
|
||||
Покладіть модель усередину моделі — і `ctx.elicit` викине виняток ще до того, як щось буде надіслано клієнтові.
|
||||
Виклик інструмента завершується помилкою `Error executing tool <name>`, а причина — у лозі сервера:
|
||||
|
||||
```text
|
||||
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
|
||||
|
|
@ -112,8 +113,8 @@ translation:
|
|||
|
||||
!!! tip
|
||||
Відповідь валідується за вашою моделлю, перш ніж її побачить ваш код. Клієнт, що надсилає
|
||||
`"maybe"` для `bool`, не зіпсує бронювання: виклик завершується помилкою
|
||||
невідповідності схемі, а ваш `if` так і не виконується.
|
||||
`"maybe"` для `bool`, не зіпсує бронювання: `ctx.elicit` викидає `ValueError`, виклик
|
||||
завершується помилкою, а ваш `if` так і не виконується.
|
||||
|
||||
## Перенаправлення користувача на URL {#send-the-user-to-a-url}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5]
|
||||
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5]
|
||||
tool: 1
|
||||
---
|
||||
# Логування {#logging}
|
||||
|
|
@ -55,6 +55,8 @@ translation:
|
|||
|
||||
`logging.basicConfig()` ніколи не замінює обробники, що вже існують. Якщо налаштувати логування самостійно до створення сервера, ваше налаштування має перевагу.
|
||||
|
||||
Так само не потрібен `try`/`except` у кожному обробнику лише для того, щоб фіксувати збої. Коли функція інструмента чи ресурсу викидає виняток, SDK записує його в лог за вас. Що саме логується і на якому рівні, пояснено на сторінці **[Обробка помилок](../servers/handling-errors.md#any-other-exception)**.
|
||||
|
||||
## Спробуйте самі {#try-it}
|
||||
|
||||
Запустіть сервер з MCP Inspector:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
tool: 1
|
||||
---
|
||||
# Запуск сервера {#running-your-server}
|
||||
|
|
@ -70,9 +70,9 @@ Inspector робить рівно те саме, що й справжній хо
|
|||
|
||||
* `host` / `port`: де слухати. За замовчуванням `127.0.0.1` і `8000`.
|
||||
* `streamable_http_path`: де розташована кінцева точка MCP. За замовчуванням `/mcp`.
|
||||
* `json_response=True`: відповідати на кожен POST одним JSON-тілом замість SSE-потоку. У такому тілі є місце для відповіді й ні для чого іншого, тож інструмент, який посеред запиту звертається назад до клієнта (`ctx.elicit()`, семплювання (sampling)), на цьому відрізку викидає `NoBackChannelError`, а сповіщення, прив'язані до поточного виклику (перебіг виконання від `ctx.report_progress()`, повідомлення журналу окремого виклику), відкидаються; окремий потік `GET` і далі несе не пов'язані з ним.
|
||||
* `json_response=True`: відповідати на кожен POST одним JSON-тілом замість SSE-потоку. У такому тілі є місце для відповіді й ні для чого іншого, тож інструмент, який посеред запиту звертається назад до клієнта (`ctx.elicit()`, семплювання (sampling)), на цьому відрізку викидає `NoBackChannelError`, а сповіщення, прив'язані до поточного виклику (перебіг виконання від `ctx.report_progress()`, лог-повідомлення окремого виклику), відкидаються; окремий потік `GET` і далі несе не пов'язані з ним.
|
||||
* `stateless_http=True`: новий транспорт на кожен запит, без відстеження сесій.
|
||||
* `max_request_body_size`: найбільший прийнятний розмір тіла POST у байтах. За замовчуванням 4 МіБ; більші запити
|
||||
* `max_request_body_size`: найбільший прийнятний розмір тіла запиту в байтах. За замовчуванням 4 МіБ; більші запити
|
||||
отримують HTTP 413 ще до розбору чи створення сесії. Збільшуйте його лише тоді, коли легітимні MCP-повідомлення
|
||||
перевищують цей розмір.
|
||||
* `event_store`, `retry_interval`, `transport_security`: відновлюваність і захист від DNS-rebinding. Вони можуть зачекати, доки ви не розгорнете сервер деінде, крім localhost; `transport_security` описано на сторінці **[Розгортання та масштабування](deploy.md)**.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
|
||||
sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5]
|
||||
tool: 1
|
||||
---
|
||||
# Обробка помилок {#handling-errors}
|
||||
|
||||
Інструмент може завершитися невдачею двома способами, і SDK обробляє їх зовсім по-різному.
|
||||
Інструмент може завершитися невдачею трьома способами, і SDK обробляє кожен по-різному.
|
||||
|
||||
Викиньте звичайний виняток — і його побачить **модель**. Викиньте `MCPError` — і його побачить **протокол**.
|
||||
Викиньте `ToolError` — і ваше повідомлення побачить **модель**. Викиньте `MCPError` — і його побачить **протокол**. Викиньте будь-що інше — і це збій: модель дізнається лише, що виклик не вдався, а трасування потрапить у ваш лог.
|
||||
|
||||
Ця сторінка — про те, як вибрати.
|
||||
|
||||
|
|
@ -15,11 +15,11 @@ translation:
|
|||
|
||||
Візьмімо інструмент, який щось шукає, і нехай пошук нічого не знайде:
|
||||
|
||||
```python title="server.py" hl_lines="11-12"
|
||||
```python title="server.py" hl_lines="2 12-13"
|
||||
--8<-- "docs_src/handling_errors/tutorial001.py"
|
||||
```
|
||||
|
||||
У цих двох рядках немає нічого специфічного для MCP. `get_author` викидає звичайний `ValueError`, як це зробила б будь-яка функція Python.
|
||||
`ToolError` з `mcp.server.mcpserver.exceptions` — це спосіб, яким інструмент повідомляє моделі, що щось пішло не так.
|
||||
|
||||
Викличте його з назвою, якої немає в каталозі, і подивіться на результат:
|
||||
|
||||
|
|
@ -30,13 +30,15 @@ result.structured_content # None
|
|||
```
|
||||
|
||||
* Запит **виконався успішно**. Результат є; на боці того, хто викликав, нічого не викинуто.
|
||||
* `is_error` дорівнює `True`, а повідомлення вашого винятку (з назвою інструмента на початку) лежить у `content` — саме там, де читає модель.
|
||||
* `is_error` дорівнює `True`, а ваше повідомлення (з назвою інструмента на початку) лежить у `content` — саме там, де читає модель.
|
||||
* `structured_content` дорівнює `None`. У невдалого виклику немає значення, яке можна було б структурувати.
|
||||
|
||||
Це **помилка інструмента**, і так за замовчуванням обробляється *будь-який* виняток, який викидає ваш інструмент. І майже завжди це саме те, що потрібно.
|
||||
Це **помилка інструмента**, і майже завжди це саме те, що потрібно.
|
||||
|
||||
Ваш інструмент викликає саме модель. Це вона обрала аргументи. Тож помилка інструмента — це репліка в розмові: модель читає *«No book titled 'Nothing' in the catalog.»*, розуміє, що не вгадала назву, і викликає знову з кращою. Один `raise` — і маєте агента, що сам виправляє свої помилки.
|
||||
|
||||
На сервері `ToolError` — це один рядок рівня `INFO` у лозі, без трасування. Ви цього очікували, тож розслідувати нічого.
|
||||
|
||||
!!! tip
|
||||
Ніколи не повертайте повідомлення про помилку з інструмента через `return`. Повернутий рядок має `is_error=False`, тож для
|
||||
моделі (і для кожного клієнтського інтерфейсу) це виглядає так, ніби інструмент спрацював і цей рядок і є відповіддю.
|
||||
|
|
@ -44,7 +46,7 @@ result.structured_content # None
|
|||
|
||||
## Помилка, яку модель не може виправити {#an-error-the-model-cannot-fix}
|
||||
|
||||
Тепер замініть `ValueError` на `MCPError`.
|
||||
Тепер замініть `ToolError` на `MCPError`.
|
||||
|
||||
```python title="server.py" hl_lines="1 3 14"
|
||||
--8<-- "docs_src/handling_errors/tutorial002.py"
|
||||
|
|
@ -77,10 +79,10 @@ result.structured_content # None
|
|||
|
||||
Ці два шляхи відповідають на два різні запитання.
|
||||
|
||||
* **Викидайте будь-який виняток** у разі збою *виконання*: те, що інструмент намагався зробити, не вдалося. Виклик обрала модель, тож саме модель має побачити наслідок і отримати шанс виправитися. Назва з помилкою, зовнішній API, що не відповів вчасно, рядок, якого не існує, — усе це помилки інструмента.
|
||||
* **Викидайте `ToolError`** у разі збою *виконання*: те, що інструмент намагався зробити, не вдалося. Виклик обрала модель, тож саме модель має побачити наслідок і отримати шанс виправитися. Назва з помилкою, зовнішній API, що не відповів вчасно, рядок, якого не існує, — усе це помилки інструмента.
|
||||
* **Викидайте `MCPError`**, коли слід відхилити *сам запит*: клієнтові бракує можливості, від якої залежить інструмент, сервер не в тому стані, щоб обслуговувати будь-кого, той, хто викликає, пропустив обов'язковий крок. Жодна повторна спроба моделі нічого з цього не виправить, тож передавати їй повідомлення немає сенсу.
|
||||
|
||||
Вирішує одне запитання: **чи могла б розумніша модель цього уникнути?** Так -> звичайний виняток. Ні -> `MCPError`.
|
||||
Вирішує одне запитання: **чи могла б розумніша модель цього уникнути?** Так -> `ToolError`. Ні -> `MCPError`.
|
||||
|
||||
За цим критерієм друга версія `get_author` зробила хибний вибір: краща назва все виправляє, тож модель заслуговувала побачити повідомлення. Ця версія тут, щоб показати механізм, а не щоб його рекомендувати.
|
||||
|
||||
|
|
@ -89,6 +91,25 @@ result.structured_content # None
|
|||
корисне навантаження `data`. Усе, що ви в них покладете, клієнт і отримає: SDK пересилає викинутий
|
||||
`MCPError` дослівно, не очищуючи його.
|
||||
|
||||
## Будь-який інший виняток {#any-other-exception}
|
||||
|
||||
Тепер приберіть перевірку й дайте пошуку в словнику завершитися невдачею самому по собі:
|
||||
|
||||
```python title="server.py" hl_lines="11"
|
||||
--8<-- "docs_src/handling_errors/tutorial004.py"
|
||||
```
|
||||
|
||||
`CATALOG[title]` викидає `KeyError`. Ви його не передбачали, тож SDK вважає це збоєм:
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
result.content # [TextContent(text="Error executing tool get_author")]
|
||||
```
|
||||
|
||||
Виклик усе одно повертає `is_error=True`, тож модель знає, що він не вдався, і може рухатися далі. Чого вона не отримує — то це тексту винятку: `KeyError` з вашого коду чи купа SQL від драйвера на три бібліотеки глибше можуть описувати нутрощі вашого сервера, тому за його межі вони ніколи не виходять.
|
||||
|
||||
Натомість їх отримуєте ви. Сервер записує збій у лог на рівні `ERROR` з повним трасуванням як `Tool 'get_author' raised an unexpected exception`. Тож лог робочого середовища на рівні `WARNING` мовчить під час кожної `ToolError` і озивається, щойно щось справді зламалося.
|
||||
|
||||
## Ресурс, якого не існує {#a-resource-that-doesnt-exist}
|
||||
|
||||
Ресурси проводять ту саму межу й мають один іменований виняток для типового випадку.
|
||||
|
|
@ -109,7 +130,7 @@ result.structured_content # None
|
|||
}
|
||||
```
|
||||
|
||||
Зверніть увагу: тут немає напіврезультату з `is_error=True`. Читання ресурсу або повертає вміст, або завершується помилкою: у ресурсів є лише протокольний шлях. Про шаблони й усе інше, що стосується ресурсів, — на сторінці **[Ресурси](resources.md)**.
|
||||
Зверніть увагу: тут немає напіврезультату з `is_error=True`. Читання ресурсу або повертає вміст, або завершується помилкою: у ресурсів є лише протокольний шлях. `ResourceError` — те саме для збою, який не є «не знайдено» (`-32603`, ваше повідомлення), і обидва — це один рядок рівня `INFO` у вашому лозі. Будь-який інший виняток, окрім `MCPError`, — це збій: клієнт отримує `-32603`, де вказано лише URI, а трасування йде у ваш лог на рівні `ERROR`. Про шаблони й усе інше, що стосується ресурсів, — на сторінці **[Ресурси](resources.md)**.
|
||||
|
||||
## Помилки, які ви ніколи не викидаєте {#errors-you-never-raise}
|
||||
|
||||
|
|
@ -120,19 +141,21 @@ result.structured_content # None
|
|||
Це означає цілий клас інструкцій `raise`, які писати не треба: не перевіряйте повторно власні анотації типів.
|
||||
|
||||
!!! info
|
||||
Усе на цій сторінці — те, що бачить **клієнт**, і `Client` у пам'яті, з яким ви писатимете
|
||||
тести, бачить рівно те саме. Навіть `raise_exceptions=True` не перетворює помилку інструмента
|
||||
назад на трасування: до моменту, коли цей прапорець міг би спрацювати, ваш виняток уже став
|
||||
результатом з `is_error=True`. Перевіряйте результат через assert. Цей підхід описано на сторінці **[Тестування](../get-started/testing.md)**.
|
||||
Усе, що на цій сторінці бачить **клієнт**, бачить і `Client` у пам'яті, з яким ви писатимете
|
||||
тести. Навіть `raise_exceptions=True` не повертає виняток інструмента, що впав,
|
||||
тому, хто викликав: до моменту, коли цей прапорець міг би спрацювати, ваш виняток уже став
|
||||
результатом з `is_error=True`. Перевіряйте результат через assert. Якщо потрібне трасування збою, воно
|
||||
в лозі сервера, і `caplog` з pytest його перехоплює. Цей підхід описано на сторінці **[Тестування](../get-started/testing.md)**.
|
||||
|
||||
## Підсумки {#recap}
|
||||
|
||||
* Викиньте **будь-який виняток** в інструменті -> виклик повертає `is_error=True` з вашим повідомленням у `content`. Модель читає його й може повторити спробу. Це поведінка за замовчуванням.
|
||||
* Викиньте **`ToolError`** в інструменті -> виклик повертає `is_error=True` з вашим повідомленням у `content`. Модель читає його й може повторити спробу.
|
||||
* Викиньте **`MCPError`** -> сам виклик завершується помилкою JSON-RPC. Модель нічого не бачить; розбирається хост. `code`, `message` і `data` доходять без змін.
|
||||
* Вирішальне запитання: *чи могла б розумніша модель цього уникнути?* Так -> виняток. Ні -> `MCPError`.
|
||||
* Вирішальне запитання: *чи могла б розумніша модель цього уникнути?* Так -> `ToolError`. Ні -> `MCPError`.
|
||||
* Будь-який **інший виняток** — це збій -> `is_error=True` лише з `Error executing tool <name>` для моделі та запис рівня `ERROR` із трасуванням для вас.
|
||||
* `ResourceNotFoundError` з обробника ресурсу -> протокольний `-32602` з URI в `data`.
|
||||
* Некоректні аргументи відхиляються за схемою ще до запуску вашої функції; для них `raise` не потрібен.
|
||||
* `from mcp import MCPError`; константи кодів помилок — з `mcp.types`.
|
||||
* Імпорти: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError` і константи кодів помилок з `mcp.types`.
|
||||
|
||||
З помилками розібралися. Це все, що сервер *надає назовні*. Про те, що кожен обробник може читати і що робити у відповідь клієнтові під час роботи, — наступний розділ: **[Усередині обробника](../handlers/index.md)**.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
|
||||
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3]
|
||||
tool: 1
|
||||
---
|
||||
# Медіа {#media}
|
||||
|
|
@ -86,6 +86,24 @@ result.structured_content # None
|
|||
так `Audio` з байтів MP3 — і клієнту повідомлять `mime_type="audio/wav"`, після чого
|
||||
він сумлінно не зможе це декодувати. Передаєте `data=` — передавайте й `format=`.
|
||||
|
||||
## Вбудовування ресурсу {#embedding-a-resource}
|
||||
|
||||
Інструмент може повернути й документ: текст або байти разом з URI, за яким він доступний, і MIME-типом. Це **`EmbeddedResource`**, ще один різновид блока вмісту. На відміну від звичайного `str`, він повідомляє клієнту, що саме це за вміст, тож клієнт може показати його як вкладення або впізнати ресурс, який уже знає.
|
||||
|
||||
```python title="server.py" hl_lines="7 14 16-18"
|
||||
--8<-- "docs_src/media/tutorial005.py"
|
||||
```
|
||||
|
||||
* `brand://guidelines` — звичайний ресурс (про них — на сторінці **[Ресурси](resources.md)**). Інструмент на запит передає моделі той самий документ, а прямий виклик `guidelines()` зберігає єдине джерело істини.
|
||||
* `EmbeddedResource` і `TextResourceContents` беруться з `mcp.types`. Допоміжного класу, як для зображень, немає: побудований блок потрапляє в результат без змін, і `structured_content` теж немає.
|
||||
* Використовуйте URI, під яким ресурс зареєстровано, щоб клієнт міг зрозуміти, що вкладення й `brand://guidelines` — той самий документ. Дозволений будь-який URI, зареєстрований чи ні.
|
||||
|
||||
```python
|
||||
result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))]
|
||||
```
|
||||
|
||||
Для двійкового вмісту замість `TextResourceContents` використовуйте `BlobResourceContents(uri=..., mime_type=..., blob=...)` з байтами в кодуванні base64 у полі `blob`. Щоб надіслати лише вказівник, за яким клієнт зможе пізніше виконати `resources/read`, поверніть натомість `ResourceLink(name=..., uri=...)` — це теж блок вмісту.
|
||||
|
||||
## Іконки {#icons}
|
||||
|
||||
`Icon` — це метадані, а не вміст. Він не містить зображення, а вказує на нього через URI, і клієнт може завантажити його й показати поруч із назвою сервера, інструментом, ресурсом чи промптом.
|
||||
|
|
@ -115,6 +133,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_
|
|||
|
||||
* Поверніть з інструмента `Image` або `Audio` — і клієнт отримає блок `ImageContent` / `AudioContent`: ваші байти в кодуванні base64 з MIME-типом.
|
||||
* Створюйте їх із `path=`, і тоді MIME-тип визначить розширення, або з `data=` у пам'яті плюс явний `format=`.
|
||||
* Поверніть `EmbeddedResource`, щоб покласти в результат документ (текст або blob у base64 з його URI та MIME-типом), або `ResourceLink`, щоб надіслати лише вказівник.
|
||||
* Медіарезультати не мають ні `structured_content`, ні схеми виводу.
|
||||
* `Icon` — це вказівник: URI `src` плюс необов'язкові `mime_type`, `sizes` і `theme`.
|
||||
* `icons=[...]` працює на сервері, інструментах, ресурсах і промптах, а клієнти знаходять їх у відповідних об'єктах.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
|
||||
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd]
|
||||
tool: 1
|
||||
---
|
||||
# Промпти {#prompts}
|
||||
|
|
@ -139,10 +139,55 @@ uv run mcp dev server.py
|
|||
```
|
||||
|
||||
!!! info
|
||||
Якщо ви читали сторінку **[Інструменти](tools.md)**, то вже знаєте все, що є на цій. Той самий декоратор, той самий
|
||||
Якщо ви читали сторінку **[Інструменти](tools.md)**, то вже знаєте все, про що йшлося досі. Той самий декоратор, той самий
|
||||
docstring як опис, ті самі `Annotated`/`Field`. Змінюється лише те, хто
|
||||
його запускає (користувач) і куди йде результат (у розмову).
|
||||
|
||||
## Більше ніж текст {#more-than-text}
|
||||
|
||||
`UserMessage` і `AssistantMessage` також приймають блок вмісту або допоміжний об'єкт `Image` / `Audio` всюди, де приймають `str`. У промптах трапляються два випадки: прикріпити документ і прикріпити зображення.
|
||||
|
||||
### Вбудовування файлу {#embedding-a-file}
|
||||
|
||||
```python title="server.py" hl_lines="5 12 21 23"
|
||||
--8<-- "docs_src/prompts/tutorial004.py"
|
||||
```
|
||||
|
||||
* Посібник зі стилю — це ресурс за адресою `style://python` (про них — на сторінці **[Ресурси](resources.md)**), який читається з файлу `style-guide.md` поруч із `server.py`. Покладіть туди будь-який Markdown-файл.
|
||||
* `EmbeddedResource(resource=TextResourceContents(...))`, обидва з `mcp.types`, несе файл разом із його URI та MIME-типом як перше повідомлення; запит, що на нього посилається, іде слідом як звичайний текст.
|
||||
* Вбудовування замість вставлення посібника в f-рядок дає клієнту змогу показати його як вкладення й пізніше знову відкрити `style://python`, а модель отримує файл дослівно. Для двійкового файлу використовуйте `BlobResourceContents` із `blob` у base64.
|
||||
|
||||
Після генерування `content` першого повідомлення — це блок `resource`:
|
||||
|
||||
```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` — допоміжний клас зі сторінки **[Зображення, аудіо та піктограми](media.md)**. `UserMessage` перетворює його на блок `ImageContent` (файл закодовано в base64, MIME-тип вгадано з `.png`), коли промпт генерується; `Audio` так само стає `AudioContent`.
|
||||
* Покладіть будь-який PNG з іменем `architecture.png` поруч із `server.py`. Аргументи промпту — рядки, тому зображення завжди надходить із сервера; `component` лише дає слова.
|
||||
|
||||
```json
|
||||
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
|
||||
```
|
||||
|
||||
## Зміна списку під час роботи {#changing-the-list-at-runtime}
|
||||
|
||||
Промпти можна додавати, поки клієнти під'єднані, наприклад щоб користувач міг зберегти інструкцію як власний пункт меню. Зареєструйте промпт, а тоді надішліть сповіщення:
|
||||
|
||||
```python title="server.py" hl_lines="5 23-27"
|
||||
--8<-- "docs_src/prompts/tutorial006.py"
|
||||
```
|
||||
|
||||
* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` реєструє функцію точно так, як це зробив би `@mcp.prompt()`, а `mcp.remove_prompt(name)` — зворотна дія. `add_prompt` залишає наявний запис із тим самим іменем, а не перезаписує його, тому інструмент спершу видаляє старий, щоб збереження працювало як заміна. `prompts/list` відображає зміну одразу.
|
||||
* `await ctx.notify_prompts_changed()` надсилає `notifications/prompts/list_changed` кожному клієнту `2026-07-28`, що слухає потік `subscriptions/listen` (**[Підписки](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` надсилає його клієнту, який зробив виклик, якщо той старший за 2026 (**[Обслуговування клієнтів старого покоління](../run/legacy-clients.md)**). Викликайте обидва; кожен нічого не робить, коли сповіщати нікого.
|
||||
* Клієнт, що отримав сповіщення, знову викликає `prompts/list`. У Python-класі `Client` це `async with client.listen(prompts_list_changed=True) as sub:`, що видає подію `PromptsListChanged`.
|
||||
|
||||
## Підсумки {#recap}
|
||||
|
||||
* `@mcp.prompt()` над функцією робить її промптом. Ім'я — з функції, опис — з docstring.
|
||||
|
|
@ -151,5 +196,7 @@ uv run mcp dev server.py
|
|||
* Поверніть `str` — і він стане одним повідомленням користувача. Поверніть список `UserMessage` / `AssistantMessage`, щоб закласти багатоходову розмову.
|
||||
* `title=` і `Field(description=...)` — це те, що клієнт показує у своєму інтерфейсі.
|
||||
* Відсутній обов'язковий аргумент провалює весь запит. Окремого результату з помилкою для промпту немає.
|
||||
* Загорніть `EmbeddedResource` або `Image` у `UserMessage`, щоб прикріпити документ чи зображення.
|
||||
* Додавайте або видаляйте промпти під час роботи через `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, а тоді викликайте `await ctx.notify_prompts_changed()` і `await ctx.session.send_prompt_list_changed()`.
|
||||
|
||||
Серверне автодоповнення аргументів промпту (або шаблону ресурсу) — це **[Автодоповнення](completions.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
|
||||
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, 91be9b73602abcf1, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c7eff2a5698225fa, c851964bb3301907, 8f296f1f09e4c400, d715db6f8dccc9cc, a0c344a48450dbe4]
|
||||
tool: 1
|
||||
---
|
||||
# Структурований вивід {#structured-output}
|
||||
|
|
@ -105,7 +105,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions
|
|||
--8<-- "docs_src/structured_output/tutorial003.py"
|
||||
```
|
||||
|
||||
Під час виконання `TypedDict` — це звичайний `dict`, тож саме його ви будуєте й повертаєте. Схема, валідація і `structured_content` ідентичні версії з `BaseModel` (за винятком описів, для яких у `TypedDict` немає місця).
|
||||
Під час виконання `TypedDict` — це звичайний `dict`, тож саме його ви будуєте й повертаєте. Схема, валідація і `structured_content` підпорядковуються тим самим правилам, що й у версії з `BaseModel`: додайте docstring класу або `Annotated[..., Field(description=...)]` — і вони стануть описами, а ключ `NotRequired`, якого немає в словнику, не потрапить і до `structured_content`.
|
||||
|
||||
## Dataclass {#a-dataclass}
|
||||
|
||||
|
|
@ -187,18 +187,19 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
|
|||
Анотація обіцяє `WeatherData`. Відповідь зовнішнього сервісу перестала надсилати `humidity`.
|
||||
|
||||
!!! check
|
||||
Викличте `get_weather` — і він не передасть клієнту тихцем напівпорожній об'єкт. Виклик завершується помилкою,
|
||||
і перші рядки помилки називають поле:
|
||||
Викличте `get_weather` — і він не передасть клієнту тихцем напівпорожній об'єкт. Виклик завершується помилкою:
|
||||
клієнт отримує `is_error=True` з `Error executing tool get_weather`, тож модель знає, що виклик
|
||||
не вдався, замість того щоб упевнено читати погоду, якої немає. Назва поля — для вас,
|
||||
у лозі сервера на рівні `ERROR`:
|
||||
|
||||
```text
|
||||
Error executing tool get_weather: 1 validation error for WeatherData
|
||||
Tool 'get_weather' raised an unexpected exception
|
||||
...
|
||||
pydantic_core._pydantic_core.ValidationError: 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}
|
||||
|
|
@ -213,6 +214,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
|
|||
|
||||
Протилежне, `structured_output=True`, перетворює автоматичне визначення на вимогу: інструмент, чий тип повернення не може дати схему, викидає виняток під час імпорту замість відкоту до тексту.
|
||||
|
||||
## Блоки вмісту й медіа {#content-blocks-and-media}
|
||||
|
||||
Блоки вмісту й медіа (`TextContent`, `EmbeddedResource`, `Image`, `Audio` та подібні — самі по собі, як елементи `list`, `tuple` чи `Sequence` або як варіанти об'єднання типів) вимкнено за вас: вони призначені для читання моделлю, тож автоматичне визначення не виводить із них схеми (`Image` та `Audio` описано на сторінці **[Зображення, аудіо та іконки](media.md)**). `structured_output=True` усе одно примусово створює схему для класів блоків вмісту.
|
||||
|
||||
## Клас без анотацій типів {#a-class-without-type-hints}
|
||||
|
||||
Є один спосіб опинитися без структурованого виводу, не просивши про це: повернути клас, у **тілі якого немає анотацій**.
|
||||
|
|
@ -245,6 +250,6 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
|
|||
* Скаляри, списки, кортежі й об'єднання типів загортаються в `{"result": ...}`. Моделі, `TypedDict`, dataclass, анотовані класи й `dict[str, ...]` — уже об'єкти й лишаються як є.
|
||||
* Кожен результат несе `content` (текст, для моделі) **і** `structured_content` (дані, для застосунку).
|
||||
* Те, що ви повертаєте, перевіряється на відповідність схемі. Невідповідність — це помилка інструмента, а не зіпсований результат.
|
||||
* `structured_output=False` вимикає це для інструмента. Клас без анотацій типів вимикає це мовчки; пильнуйте.
|
||||
* `structured_output=False` вимикає це для інструмента. Блоки вмісту, `Image` та `Audio` вимкнено за замовчуванням; клас без анотацій типів вимикає це мовчки, тож пильнуйте.
|
||||
|
||||
Тепер ви володієте всім, що інструмент може сказати у відповідь. Далі — другий примітив: **[Ресурси](resources.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
|
||||
sections: [e4cc390d56573409, f30cf8103a6e918c, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
|
||||
tool: 1
|
||||
---
|
||||
# Інструменти {#tools}
|
||||
|
|
@ -39,6 +39,8 @@ translation:
|
|||
|
||||
Обидва аргументи потрапили в `required`, бо жоден не має типового значення. За мить ви це виправите. (Ключі `title` — артефакти Pydantic; контракт складають властивості, їхні типи та `required`.)
|
||||
|
||||
Ключа `$schema` теж немає: схему без нього MCP трактує як **JSON Schema 2020-12** — саме її й генерує Pydantic, тож вибирати нічого, доки не візьметеся писати схеми вручну для **[низькорівневого Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**.
|
||||
|
||||
!!! tip
|
||||
Анотації типів тут — не документація. Вони і є **контракт**. Якщо клієнт надішле `"limit": "ten"`,
|
||||
SDK відхилить запит ще до того, як ваша функція запуститься.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
|
||||
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd]
|
||||
tool: 1
|
||||
---
|
||||
# URI-шаблони та безпека шляхів {#uri-templates-and-path-safety}
|
||||
|
|
@ -170,7 +170,7 @@ SDK підтримує підмножину, відібрану для зіст
|
|||
`safe_join`, щоб розв'язати шлях і впевнитися, що він лишається всередині
|
||||
базового каталогу:
|
||||
|
||||
```python title="server.py" hl_lines="4 14"
|
||||
```python title="server.py" hl_lines="5 15"
|
||||
--8<-- "docs_src/uri_templates/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -211,9 +211,10 @@ SDK підтримує підмножину, відібрану для зіст
|
|||
|
||||
!!! tip
|
||||
Якщо обробник не може виконати запит (файл не існує, ідентифікатор
|
||||
невідомий), викиньте виняток. SDK перетворить його на відповідь з
|
||||
помилкою. Про різницю між помилкою протоколу та помилкою інструмента —
|
||||
на сторінці **[Обробка помилок](handling-errors.md)**.
|
||||
невідомий), викиньте `ResourceNotFoundError`, як це робить `read_manual`
|
||||
вище. Клієнт отримає `-32602` з вашим повідомленням і URI. Неочікуваний
|
||||
виняток натомість стає загальною помилкою `-32603`. Див.
|
||||
**[Обробка помилок](handling-errors.md#a-resource-that-doesnt-exist)**.
|
||||
|
||||
## Ресурси на низькорівневому Server {#resources-on-the-low-level-server}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 4835ed1772f1d113, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, 954dc21efdb532a3]
|
||||
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 8a6e351ec756904d, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, e4355f4c7cf4fb2e]
|
||||
tool: 1
|
||||
---
|
||||
# Усунення несправностей {#troubleshooting}
|
||||
|
|
@ -81,11 +81,11 @@ async def main() -> None:
|
|||
|
||||
`__aexit__` — це від'єднання, тому й немає `client.close()`, про який можна забути. Сторінка **[Тестування](get-started/testing.md)** побудована саме на цьому шаблоні.
|
||||
|
||||
## `Error executing tool <name>: <message>` і `Unknown tool: <name>` {#error-executing-tool-name-message-and-unknown-tool-name}
|
||||
## `Error executing tool <name>: <message>`, `Error executing tool <name>` і `Unknown tool: <name>` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name}
|
||||
|
||||
Перед вами **результат**, а не виняток. `call_tool` нічого не викинув і ніколи не викине для інструмента, що завершився збоєм.
|
||||
|
||||
Викличте `forecast` для міста, якого сервер не знає, — і виняток, який він викидає, повертається із запитом, позначеним як *успішний*:
|
||||
Викличте `forecast` для міста, якого сервер не знає, — і `ToolError`, який він викидає, повертається із запитом, позначеним як *успішний*:
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
|
|
@ -97,6 +97,8 @@ result.structured_content # None
|
|||
|
||||
Виправлення — на боці клієнта: **перевіряйте `result.is_error`**. `try/except` навколо `call_tool` не перехопить жодного з цих випадків, бо перехоплювати нічого. Це зроблено навмисно, і це найкорисніше, що варто засвоїти з цієї сторінки: виклик обрала *модель*, тож саме модель отримує повідомлення й шанс спробувати знову. Докладніше — на сторінці **[Обробка помилок](servers/handling-errors.md)**, зокрема про шлях через `MCPError`, який *таки* викидає виняток.
|
||||
|
||||
Коротка форма, `Error executing tool <name>` без повідомлення, означає, що інструмент **упав**: з нього вийшов виняток, якого він не передбачав (або його повернене значення не пройшло вихідну схему), і текст цього винятку в передані дані не потрапляє. Трасування — у **лозі сервера** на рівні `ERROR`, як `Tool '<name>' raised an unexpected exception`.
|
||||
|
||||
## `TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool` {#typeerror-the-tool-decorator-was-used-incorrectly-did-you-forget-to-call-it-use-tool-instead-of-tool}
|
||||
|
||||
Ви написали `@mcp.tool` замість `@mcp.tool()`. `tool()` — це *фабрика* декораторів: без дужок Python передає вашу функцію в її параметр `name=`.
|
||||
|
|
@ -410,7 +412,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key
|
|||
## Підсумки {#recap}
|
||||
|
||||
* `ExceptionGroup: unhandled errors in a TaskGroup` ніколи не є самою помилкою. Читайте **останній рядок**; перехоплення `MCPError` *всередині* блоку `async with Client(...)` повністю оминає обгортання.
|
||||
* `call_tool` не викидає виняток для інструмента, що завершився збоєм. `Error executing tool ...` і `Unknown tool: ...` — це результати: перевіряйте `result.is_error`.
|
||||
* `call_tool` не викидає виняток для інструмента, що завершився збоєм. `Error executing tool ...` і `Unknown tool: ...` — це результати: перевіряйте `result.is_error`. Якщо після імені інструмента немає повідомлення, він упав, а трасування — у лозі сервера.
|
||||
* `Client must be used within an async context manager` -> використовуйте `async with`. `Use @tool() instead of @tool` -> додайте дужки.
|
||||
* `Tool already exists:` у лозі сервера — єдина ознака того, що два однойменні інструменти злилися в один.
|
||||
* Один 421, три написання: `Server returned an error response` (python `Client`), `421 Misdirected Request` / `Invalid Host header` (усе інше), `Invalid Host header: <host>` (лог сервера). Виправлення: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e]
|
||||
sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e]
|
||||
tool: 1
|
||||
---
|
||||
# Що нового у v2 {#whats-new-in-v2}
|
||||
|
|
@ -46,9 +46,9 @@ v1 давав три вкладені шари: контекстний мене
|
|||
--8<-- "docs_src/client/tutorial001.py"
|
||||
```
|
||||
|
||||
`Client` приймає об'єкт сервера (у пам'яті, без транспорту: це сценарій тестування), URL (Streamable HTTP) або будь-який контекстний менеджер транспорту, як-от `stdio_client(...)`. Вхід в `async with` під'єднує та узгоджує версію протоколу, хай яким поколінням говорить сервер; після цього `client.server_capabilities` і `client.protocol_version` просто є, як і `client.server_info`, коли сервер себе ідентифікує (тепер це `Implementation | None`, бо ідентичність у поколінні 2026 необов'язкова). Колбеки семплювання й еліцитації, зареєстровані у v1, і далі працюють (їхні тіла зазнають того самого перейменування атрибутів у snake_case, що й усе інше на цій сторінці), тепер вони також відповідають на запити всередині результатів у стилі 2026 (нижче) і виконуються паралельно, а не по одному. `ClientSession` досі лежить під сподом для тих, кому потрібна низькорівнева поверхня, і `client.session` її віддає; вона теж змінилася (працює на новому рушії диспетчера, і деякі її власні сигнатури змінилися), тож прочитайте **[Посібник з міграції](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**, перш ніж спускатися нижче.
|
||||
`Client` приймає об'єкт сервера (у пам'яті, без транспорту: це сценарій тестування), URL (Streamable HTTP), `StdioServerParameters` (підпроцес stdio) або будь-який інший контекстний менеджер транспорту, як-от `sse_client(...)`. Вхід в `async with` під'єднує та узгоджує версію протоколу, хай яким поколінням говорить сервер; після цього `client.server_capabilities` і `client.protocol_version` просто є, як і `client.server_info`, коли сервер себе ідентифікує (тепер це `Implementation | None`, бо ідентичність у поколінні 2026 необов'язкова). Колбеки семплювання й еліцитації, зареєстровані у v1, і далі працюють (їхні тіла зазнають того самого перейменування атрибутів у snake_case, що й усе інше на цій сторінці), тепер вони також відповідають на запити всередині результатів у стилі 2026 (нижче) і виконуються паралельно, а не по одному. `ClientSession` досі лежить під сподом для тих, кому потрібна низькорівнева поверхня, і `client.session` її віддає; вона теж змінилася (працює на новому рушії диспетчера, і деякі її власні сигнатури змінилися), тож прочитайте **[Посібник з міграції](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**, перш ніж спускатися нижче.
|
||||
|
||||
**[Клієнт](client/index.md)** знайомить із ним, **[Транспорти клієнта](client/transports.md)** описує три форми під'єднання, **[Колбеки клієнта](client/callbacks.md)** — самі колбеки, а **[Тестування](get-started/testing.md)** показує шаблон роботи в пам'яті, що замінює допоміжну функцію `create_connected_server_and_client_session()` з v1.
|
||||
**[Клієнт](client/index.md)** знайомить із ним, **[Транспорти клієнта](client/transports.md)** описує чотири форми під'єднання, **[Колбеки клієнта](client/callbacks.md)** — самі колбеки, а **[Тестування](get-started/testing.md)** показує шаблон роботи в пам'яті, що замінює допоміжну функцію `create_connected_server_and_client_session()` з v1.
|
||||
|
||||
### Низькорівневий `Server` перебудовано, а не перейменовано {#the-low-level-server-was-rebuilt-not-renamed}
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB
|
|||
Перейменування заявляють про себе самі. А оце — ні:
|
||||
|
||||
* **Синхронні функції виконуються в робочому потоці.** Інструмент, оголошений через `def` (або ресурс, промпт чи резолвер), більше не блокує цикл подій; плата за це — його тіло більше не виконується *в* потоці циклу подій, що важливо для коду, прив'язаного до потоку. Обробники `async def` не зачеплено. **[Посібник з міграції](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**.
|
||||
* **`MCPError` (`McpError` у v1), викинутий усередині інструмента, тепер є помилкою протоколу.** Модель його ніколи не бачить. Будь-який інший виняток, як і раніше, стає результатом з `is_error=True`, який модель може прочитати й на який може відреагувати. Розмежування — на сторінці **[Обробка помилок](servers/handling-errors.md)**.
|
||||
* **`MCPError` (`McpError` у v1), викинутий усередині інструмента, тепер є помилкою протоколу.** Модель його ніколи не бачить. Будь-який інший виняток, як і раніше, стає результатом з `is_error=True`, але до моделі доходить лише повідомлення `ToolError`: будь-який інший виняток тепер читається як `Error executing tool <name>`, а трасування стека лишається в лозі вашого сервера. Розмежування — на сторінці **[Обробка помилок](servers/handling-errors.md)**.
|
||||
* **Результати перевіряються перед відправленням.** Зібраний вручну `Tool`, у якого `input_schema` дорівнює `{}`, тепер провалює `tools/list` (специфікація вимагає `"type": "object"`). Сервери, побудовані на `@mcp.tool()`, цього ніколи не бачать: їхні схеми пише SDK.
|
||||
* **Ваш клієнт перевіряє те, що отримує.** `list_tools()` і `call_tool()` звіряють відповідь сервера з узгодженою версією протоколу, тож не зовсім валідний сервер, який поблажливий розбір v1 терпів, тепер викидає `pydantic.ValidationError`. Якщо ви під'єднуєтеся до серверів, яких не контролюєте, готуйтеся бути тим, хто їх знайде; подробиці — у **[Посібнику з міграції](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**.
|
||||
* **URI-шаблони тепер — справжній RFC 6570.** `{+path}`, `{?query}` та подібні працюють, зіставлення точне, а не приблизне за регулярним виразом, а обхід шляху у видобутих значеннях за замовчуванням відхиляється. Суворіші шаблони падають під час декорування, а не на першому запиті. **[URI-шаблони](servers/uri-templates.md)**.
|
||||
|
|
|
|||
Loading…
Reference in a new issue