docs: refresh translations for recent English changes (#3379)

This commit is contained in:
Max 2026-08-24 19:29:54 +01:00 • committed by GitHub
commit c5d7d0b6d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 3149 additions and 1047 deletions

View file

@ -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.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
tool: 1
---
# Middleware {#middleware}
@ -42,7 +42,7 @@ tools/call took 0.1 ms
В этом и суть. Middleware оборачивает **каждое** входящее сообщение:
* Установку подключения: `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 по пути к клиенту.
## Что можно делать внутри {#what-you-can-do-inside-one}
@ -75,7 +75,7 @@ SDK поставляет ровно один слой middleware, и он уже
## Итоги {#recap}
* Middleware — это `async (ctx, call_next) -> result`; его передают как `MCPServer(middleware=[...])` (или добавляют в `mcp.middleware`), а в низкоуровневом `Server` добавляют в `server.middleware`.
* Middleware оборачивает **каждое** входящее сообщение (`server/discover`, `initialize`, запросы, уведомления, неизвестные методы) и выполняется начиная с внешнего слоя.
* Middleware оборачивает **каждое** входящее сообщение, доходящее до сервера (`server/discover`, `initialize`, запросы, уведомления, неизвестные методы), и выполняется начиная с внешнего слоя.
* `ctx.request_id is None` — так уведомление отличают от запроса.
* Чтобы отклонить одно сообщение, выбросьте исключение вместо вызова `call_next`; подключение это переживёт.
* Собственная трассировка OpenTelemetry в SDK — тоже middleware, уже в списке. См. **[OpenTelemetry](../run/opentelemetry.md)**.

View file

@ -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
---
# Объект Client {#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`. Запросите инструмент, которого у сервера вообще нет

View file

@ -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)`. `Client` передаёт всё, что не объект сервера и не URL, прямо в этот протокол.
* Транспорт — это всё, с чем можно написать `async with x as (read, write)`. `Client` передаёт всё, что не объект сервера, не URL и не `StdioServerParameters`, прямо в этот протокол.
* Создание `Client` выбирает транспорт. `async with` его открывает.
Когда транспорт открыт, двум сторонам нужно договориться о версии протокола. Обычно думать об этом не приходится; когда всё-таки придётся, нужная страница — **[Версии протокола](../protocol-versions.md)**.

View file

@ -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).
В таблице ниже перечислены все устаревшие возможности, причина, по которой каждая уходит, и замена, на которую стоит опираться.
@ -57,6 +57,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}
В новом коде — не отключайте.
@ -77,23 +126,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.

View file

@ -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,13 +11,13 @@ 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"
```
Два инструмента и ресурс, один файл. Три вещи в этом файле важны для каждого хоста ниже:
* `mcp.run()` без аргументов запускает **stdio**-сервер: он блокируется, читает сообщения протокола из stdin и пишет их в stdout. Это тот транспорт, на котором говорят все хосты на этой странице. Хост запускает ваш файл как дочерний процесс и владеет обоими каналами, поэтому подключение всегда сводится к «вот команда». Порт выбирать не нужно, и ничто на нём не слушает.
* `mcp.run()` без аргументов запускает **stdio**-сервер: он блокирует выполнение, читает сообщения протокола из stdin и пишет их в stdout. Это тот транспорт, на котором говорят все хосты на этой странице. Хост запускает ваш файл как дочерний процесс и владеет обоими каналами, поэтому подключение всегда сводится к «вот команда». Порт выбирать не нужно, и ничто на нём не слушает.
* `run()` стоит под `if __name__ == "__main__":`. Всё, что ниже, **импортирует** этот файл, а не выполняет его, так что незащищённый `run()` запускал бы сервер в тот момент, когда что-нибудь загружает модуль.
* Объект сервера — глобальная переменная уровня модуля с именем `mcp`. Это имя ищет `mcp run` (`server` и `app` тоже подходят). Назовёте иначе — придётся указать имя явно: `mcp run server.py:bookshop`.
@ -51,7 +51,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
А хост — это всего лишь приложение с MCP-клиентом внутри, так что роль хоста может
сыграть ваш собственный код на Python: **[Клиентские транспорты](../client/transports.md)**
запускают этот же файл как подпроцесс через `stdio_client(...)`, а
запускают этот же файл как подпроцесс через `Client(StdioServerParameters(...))`, а
**[Тестирование](testing.md)** подключается к нему в памяти вообще без процесса.
## Claude Desktop {#claude-desktop}

View file

@ -1,6 +1,6 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
tool: 1
---
# Тестирование {#testing}
@ -84,9 +84,10 @@ async def test_call_add_tool(client: Client):
Пойти не так могут две разные вещи, и этот флаг касается только одной из них.
Исключение внутри одного из **ваших инструментов** — не сбой протокола. Оно превращается в
обычный результат с `is_error=True`, и модель читает сообщение. `raise_exceptions` этого не
меняет: с ним или без него `call_tool` возвращает один и тот же результат с `is_error=True`.
Этому посвящена целая страница: **[Обработка ошибок](../servers/handling-errors.md)**.
обычный результат с `is_error=True` (а если это был `ToolError`, модель читает ваше сообщение).
`raise_exceptions` этого не меняет: с ним или без него `call_tool` возвращает один и тот же
результат с `is_error=True`. Этому посвящена целая страница:
**[Обработка ошибок](../servers/handling-errors.md)**.
Сбой **вне** тела инструмента — другое дело. На подключении, которое даёт `Client(mcp)`, сервер
очищает его до обобщённого `"Internal server error"`, прежде чем оно дойдёт до клиента. Детали

View file

@ -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}

View file

@ -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:

View file

@ -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}
@ -72,7 +72,7 @@ Inspector делает ровно то же, что и настоящий хос
* `streamable_http_path`: где находится конечная точка MCP. По умолчанию `/mcp`.
* `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)**.

View file

@ -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,12 +30,14 @@ result.structured_content # None
```
* Запрос **выполнен успешно**. Результат есть; на вызывающей стороне ничего не выброшено.
* `is_error` равен `True`, а сообщение вашего исключения (с префиксом в виде имени инструмента) лежит в `content` — ровно там, где читает модель.
* `is_error` равен `True`, а ваше сообщение (с префиксом в виде имени инструмента) лежит в `content` — ровно там, где читает модель.
* `structured_content` равен `None`. У неудачного вызова нет возвращаемого значения, которое можно было бы структурировать.
Это **ошибка инструмента**, и так по умолчанию обрабатывается *любое* исключение, выброшенное инструментом. И почти всегда это именно то, что нужно.
Это **ошибка инструмента**, и почти всегда это именно то, что нужно.
Ваш инструмент вызывает модель. Она же выбрала аргументы. Поэтому ошибка инструмента — это реплика в диалоге: модель читает *«No book titled 'Nothing' in the catalog.»*, понимает, что ошиблась с названием, и вызывает инструмент снова с более подходящим. Вы написали один `raise` и получили агента, который исправляет себя сам.
Вызывает ваш инструмент именно модель. Она же выбрала аргументы. Поэтому ошибка инструмента — это реплика в диалоге: модель читает *«No book titled 'Nothing' in the catalog.»*, понимает, что ошиблась с названием, и вызывает инструмент снова с более подходящим. Вы написали один `raise` и получили агента, который исправляет себя сам.
На сервере `ToolError` — это одна строка уровня `INFO` в логе, без трассировки. Вы её предвидели, так что расследовать нечего.
!!! tip
Никогда не возвращайте сообщение об ошибке из инструмента через `return`. У возвращённой строки
@ -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`. Проверяйте результат. Этот приём описан на странице **[Тестирование](../get-started/testing.md)**.
Всё, что на этой странице видит **клиент**, видит и `Client` в памяти, с которым вы будете
писать тесты. Даже `raise_exceptions=True` не возвращает исключение упавшего
инструмента вызывающей стороне: к моменту, когда этот флаг мог бы сработать, ваше исключение уже стало
результатом с `is_error=True`. Проверяйте результат. Если нужна трассировка сбоя, она в логе
сервера, и `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)**.

View file

@ -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=[...]` работает на сервере, инструментах, ресурсах и промптах, а клиенты находят их в соответствующих объектах.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd]
tool: 1
---
# Промпты {#prompts}
@ -140,9 +140,54 @@ uv run mcp dev server.py
```
!!! info
Если вы читали страницу **[Инструменты](tools.md)**, то уже знаете всё, что здесь написано. Тот же декоратор,
Если вы читали страницу **[Инструменты](tools.md)**, всё сказанное до этого места вам уже знакомо. Тот же декоратор,
та же строка документации в роли описания, те же `Annotated`/`Field`. Меняется только то, кто
запускает промпт (пользователь) и куда идёт результат (в диалог).
запускает промпт (пользователь), и куда идёт результат (в диалог).
## Больше, чем текст {#more-than-text}
`UserMessage` и `AssistantMessage` везде, где принимают `str`, принимают также блок содержимого или вспомогательный объект `Image` / `Audio`. В промптах встречаются два случая: вложить документ и вложить картинку.
### Встраивание файла {#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`.
* Положите рядом с `server.py` любой PNG с именем `architecture.png`. Аргументы промпта — строки, поэтому картинка всегда берётся с сервера; `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`. В классе `Client` на Python это `async with client.listen(prompts_list_changed=True) as sub:`, который выдаёт событие `PromptsListChanged`.
## Итоги {#recap}
@ -152,5 +197,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)**.

View file

@ -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}
Есть один способ оказаться без структурированного вывода, не прося об этом: вернуть класс, в **теле которого нет аннотаций**.
@ -242,9 +247,9 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
## Итоги {#recap}
* **Аннотация возвращаемого типа** — это выходная схема. Она публикуется в `tools/list` как `output_schema`.
* Скаляры, списки, кортежи и объединения оборачиваются в `{"result": ...}`. Модели, `TypedDict`, dataclass, классы с аннотациями и `dict[str, ...]` уже являются объектами и остаются как есть.
* Скаляры, списки, кортежи и объединения оборачиваются в `{"result": ...}`. Модели, `TypedDict`, dataclass, классы с аннотациями и `dict[str, ...]` — уже объекты и остаются как есть.
* Каждый результат несёт `content` (текст, для модели) **и** `structured_content` (данные, для приложения).
* Возвращаемое значение проверяется на соответствие схеме. Несоответствие — это ошибка инструмента, а не испорченный результат.
* `structured_output=False` отключает структурированный вывод для инструмента. Класс без аннотаций типов отключает его молча — следите за этим.
* `structured_output=False` отключает структурированный вывод для инструмента. Блоки контента, `Image` и `Audio` отключают его по умолчанию; класс без аннотаций типов отключает его молча — следите за этим.
Теперь вы владеете всем, что инструмент может сказать в ответ. Дальше — второй примитив: **[Ресурсы](resources.md)**.

View file

@ -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 отклонит вызов ещё до того, как запустится функция.

View file

@ -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,14 +170,14 @@ 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"
```
`safe_join` ловит выход через символические ссылки, последовательности
`..` и трюки с абсолютными путями, которые простая строковая проверка
пропустила бы. Если разрешённый путь выходит за `DOCS_ROOT`, функция
выбрасывает `PathEscapeError`, которое доходит до клиента как
выбрасывает исключение `PathEscapeError`, которое доходит до клиента как
`ResourceError`.
### Когда настройки по умолчанию мешают {#when-the-defaults-get-in-the-way}
@ -213,9 +213,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}

View file

@ -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=`.
@ -409,7 +411,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` (`Client` на Python), `421 Misdirected Request` / `Invalid Host header` (всё остальное), `Invalid Host header: <host>` (лог сервера). Исправление: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`.

View file

@ -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](client/index.md)** знакомит с ним, **[Транспорты клиента](client/transports.md)** описывает три формы подключения, **[Колбэки клиента](client/callbacks.md)** — сами колбэки, а **[Тестирование](get-started/testing.md)** показывает шаблон работы в памяти, который заменяет вспомогательную функцию `create_connected_server_and_client_session()` из v1.
Страница **[Объект Client](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}
@ -125,7 +125,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB
### Настройка транспорта переехала в `run()` {#transport-configuration-moved-to-run}
`MCPServer(...)` описывает, чем ваш сервер *является*: имя, инструкции, жизненный цикл (lifespan), авторизацию. То, как он *обслуживается*, теперь относится к `run()` и сборщикам приложений — туда ушли `host`, `port`, `stateless_http`, `json_response`, пути эндпоинтов и `transport_security` (`MCPServer("x", port=9000)` — это `TypeError`). Перегрузки типизированы по транспортам, так что редактор подскажет, какие параметры принимает `stdio`, а какие — `streamable-http`. Одно удаление стоит знать: `mount_path` больше нет; поддерживаемый способ обслуживать под префиксом — монтировать ASGI-приложение.
`MCPServer(...)` описывает, *что такое* ваш сервер: его имя, инструкции, жизненный цикл (lifespan), авторизация. То, как он *обслуживается*, теперь относится к `run()` и сборщикам приложений — туда ушли `host`, `port`, `stateless_http`, `json_response`, пути эндпоинтов и `transport_security` (`MCPServer("x", port=9000)` — это `TypeError`). Перегрузки типизированы по транспортам, так что редактор подскажет, какие параметры принимает `stdio`, а какие — `streamable-http`. Одно удаление стоит знать: `mount_path` больше нет; поддерживаемый способ обслуживать под префиксом — монтировать ASGI-приложение.
Параметры описаны на странице **[Запуск сервера](run/index.md)**; монтирование — на странице **[Добавление в существующее приложение](run/asgi.md)**.
@ -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)**.
@ -148,7 +148,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB
* **Транспорт WebSocket** с обеих сторон и дополнение `mcp[ws]`. Он никогда не входил в спецификацию MCP.
* API **экспериментальных Tasks** (`mcp.*.experimental`). 2026-07-28 выносит задачи из ядра протокола в официальное расширение ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)), которое этот SDK пока не реализует.
* `mcp.shared.version`, `mcp.shared.progress` и `mcp.shared.session` (с заглушкой `RequestResponder`, которую импортировали аннотации `message_handler` в v1) как пути импорта. (`mcp.types` **не** удалён: он остаётся постоянным псевдонимом отдельного пакета `mcp_types`.)
* `mcp.shared.version`, `mcp.shared.progress` и `mcp.shared.session` (с заглушкой `RequestResponder`, которую импортировали аннотации `message_handler` в v1) как пути импорта. (`mcp.types` *не* удалён: он остаётся постоянным псевдонимом отдельного пакета `mcp_types`.)
* Устаревшее написание `streamablehttp_client` и колбэк `get_session_id` у `streamable_http_client` (который теперь отдаёт ровно два потока).
* `McpError`, переименованный в **`MCPError`** с прямым конструктором `(code, message, data)`.
* `MCPServer.get_context()`, `mount_path=`, а также методы-декораторы, ContextVar и словари обработчиков низкоуровневого `Server`.