python-sdk/i18n/ru/pages/client/transports.md

128 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
translation:
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
tool: 1
---
# Клиентские транспорты {#client-transports}
Каждый `Client` общается со своим сервером через **транспорт** — то, что на самом деле переносит сообщения.
Настраивать его отдельно не нужно. `Client` принимает один позиционный аргумент и определяет транспорт по его типу.
*Серверная* сторона каждого из них (то, что делает `mcp.run()` и что вы развёртываете) описана на странице **[Запуск сервера](../run/index.md)**.
## В памяти {#in-memory}
Передайте сам объект сервера:
```python title="client.py" hl_lines="14"
--8<-- "docs_src/client_transports/tutorial001.py"
```
Ни подпроцесса, ни порта, ни байтов в передаваемых данных. Клиент и сервер — два объекта в одном процессе, и вызов всё равно проходит через настоящий протокольный уровень: `search_books` перечисляется, валидируется и вызывается ровно так же, как это было бы по HTTP.
Поэтому это сразу две вещи:
* **Тестовый стенд.** Каждый пример в этой документации проверяется именно так, а страница **[Тестирование](../get-started/testing.md)** строит вокруг этого весь подход.
* **API для встраивания.** Приложению, которое создаёт сервер, не нужен сетевой переход, чтобы вызывать его инструменты.
## Streamable HTTP {#streamable-http}
Передайте строку с URL — и получите **Streamable HTTP**, транспорт, за которым вы развёртываете сервер:
```python title="client.py" hl_lines="5"
--8<-- "docs_src/client_transports/tutorial002.py"
```
Это уже готовый клиент для продакшена. `Client` сам оборачивает URL в `streamable_http_client(...)` поверх `httpx2.AsyncClient`, настроенного так, как нужно MCP: `follow_redirects=True`, таймаут 30 секунд на connect/write/pool и таймаут чтения 300 секунд, потому что сервер может держать поток ответа открытым.
!!! check
Созданный `Client` **не** подключён. Конструктор только выбирает транспорт;
открывает его `async with`. Обратитесь к соединению до входа в блок — и SDK сообщит об этом:
```text
RuntimeError: Client must be used within an async context manager
```
Когда вы написали `Client("http://...")`, ничего не разрешалось, не загружалось и не запускалось. Эта строка ничего не стоит.
### Собственный `httpx2.AsyncClient` {#bring-your-own-httpx2asyncclient}
Как только понадобится заголовок `Authorization`, cookie, прокси, mTLS или другой таймаут, создайте `httpx2.AsyncClient` сами и передайте его в `streamable_http_client`:
```python title="client.py" hl_lines="8-14"
--8<-- "docs_src/client_transports/tutorial003.py"
```
Обратите внимание на две вещи:
* `httpx2.AsyncClient` принадлежит вам, поэтому входите в него и выходите из него **вы**. SDK никогда не закрывает клиент, который он не создавал.
* `streamable_http_client(url, http_client=...)` возвращает транспорт, а `Client(transport)` принимает его, как и всё остальное.
Одно замечание о TLS: `httpx2` проверяет сертификаты по хранилищу доверия операционной системы (через
[`truststore`](https://pypi.org/project/truststore/)), а не по встроенному списку CA. В среде без
пригодного системного хранилища CA (некоторые минимальные контейнеры) задайте стандартные
переменные окружения `SSL_CERT_FILE`/`SSL_CERT_DIR` или передайте явный `verify=ssl_context` в свой `httpx2.AsyncClient`
(подробности в разделе
[`httpx` и `httpx-sse` заменены на `httpx2`](../migration.md#httpx-and-httpx-sse-replaced-by-httpx2)).
!!! warning
Раньше `streamable_http_client` принимал `headers=` и `timeout=` напрямую. Больше не принимает:
его единственные параметры — `url`, `http_client` и `terminate_on_close`. Напишите по привычке `headers=` —
и получите:
```text
TypeError: streamable_http_client() got an unexpected keyword argument 'headers'
```
Всё, что относится к HTTP, теперь живёт в одном `httpx2.AsyncClient`, который вы передаёте.
!!! info
`httpx2` сохраняет привычный API `httpx`, так что, если вы знаете `httpx`, вы уже умеете делать здесь
аутентификацию, прокси, хуки событий, повторные попытки и ограничения соединений. SDK ничего не добавляет сверху
и ничего не убирает. Здесь же подключается OAuth:
`httpx2.AsyncClient(auth=OAuthClientProvider(...))`. Весь этот сценарий — на странице **[OAuth-клиенты](oauth-clients.md)**.
## stdio {#stdio}
Сервер **stdio** — это подпроцесс. Клиент запускает его, пишет JSON-RPC в его stdin и читает JSON-RPC из его stdout. Именно так десктопный хост запускает сервер на вашей машине: хост — это *и есть* этот код плюс UI, а страница **[Подключение к реальному хосту](../get-started/real-host.md)** показывает те же отношения со стороны хоста, в виде файла конфигурации.
Опишите процесс с помощью `StdioServerParameters` и передайте его в `Client`:
```python title="client.py" hl_lines="3-7 11"
--8<-- "docs_src/client_transports/tutorial004.py"
```
Вход в блок запускает процесс. Выход из него завершает подпроцесс: закрывает stdin, ждёт, убивает, если тот задерживается. Убирать за ним самостоятельно не нужно.
stderr дочернего процесса идёт в ваш. Чтобы направить его куда-то ещё, соберите транспорт сами с помощью `stdio_client` (из `mcp`) и передайте вместо этого его: `Client(stdio_client(server, errlog=log_file))`.
!!! warning
Дочерний процесс **не** наследует ваше окружение. Он получает минимальный разрешённый список (`HOME`, `LOGNAME`,
`PATH`, `SHELL`, `TERM` и `USER` в POSIX), чтобы ничего чувствительного не утекло в процесс, который,
возможно, написали не вы.
Сервер, которому нужен API-ключ, там его не найдёт. Передайте его явно через `env=`; эти
переменные добавляются поверх разрешённого списка. Именно это делает `BOOKSHOP_API_KEY` выше.
## SSE {#sse}
`sse_client(url)` из `mcp.client.sse` — это HTTP-транспорт, который заменил Streamable HTTP. Оборачивайте его так же, `Client(sse_client("http://localhost:8000/sse"))`, чтобы общаться с сервером, который всё ещё на нём говорит, и не стройте на нём ничего нового.
## Протокол `Transport` {#the-transport-protocol}
Для `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(StdioServerParameters(...))`. Оборачивайте его в `stdio_client(...)` сами, только чтобы перенаправить stderr дочернего процесса.
* Подпроцесс получает окружение из разрешённого списка, а не ваше; `env=` добавляет к нему.
* Транспорт — это всё, с чем можно написать `async with x as (read, write)`. `Client` передаёт всё, что не объект сервера, не URL и не `StdioServerParameters`, прямо в этот протокол.
* Создание `Client` выбирает транспорт. `async with` его открывает.
Когда транспорт открыт, двум сторонам нужно договориться о версии протокола. Обычно думать об этом не приходится; когда всё-таки придётся, нужная страница — **[Версии протокола](../protocol-versions.md)**.