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}
|
||||
|
|
@ -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)**.
|
||||
|
|
|
|||
|
|
@ -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`. Запросите инструмент, которого у сервера вообще нет
|
||||
|
|
|
|||
|
|
@ -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)**.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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"`, прежде чем оно дойдёт до клиента. Детали
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
@ -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)**.
|
||||
|
|
|
|||
|
|
@ -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)**.
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
@ -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)**.
|
||||
|
|
|
|||
|
|
@ -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)**.
|
||||
|
|
|
|||
|
|
@ -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,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}
|
||||
|
||||
|
|
|
|||
|
|
@ -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=[...])`.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue