128 lines
11 KiB
Markdown
128 lines
11 KiB
Markdown
---
|
||
translation:
|
||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
|
||
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`, перетворіть його на транспорт через `stdio_client` і передайте *це* в `Client`:
|
||
|
||
```python title="client.py" hl_lines="4-8 12"
|
||
--8<-- "docs_src/client_transports/tutorial004.py"
|
||
```
|
||
|
||
`Client` не приймає сам об'єкт параметрів. `StdioServerParameters` — це конфігурація; `stdio_client(server)` — транспорт, який уміє запустити з неї процес. Завжди загортайте.
|
||
|
||
Вихід із блоку `async with` також завершує підпроцес: закриває stdin, чекає, вбиває, якщо той затримується. Прибирати за ним самостійно ніколи не доведеться.
|
||
|
||
!!! 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)`, а в будь-що інше він входить безпосередньо як у транспорт. Саме завдяки останньому правилу `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(...)))`, ніколи не сам об'єкт параметрів.
|
||
* Підпроцес отримує середовище зі списку дозволених, а не ваше; `env=` його доповнює.
|
||
* Транспорт — це будь-що, з чим можна зробити `async with x as (read, write)`. Усе, що не є об'єктом сервера чи URL, `Client` передає прямо цьому протоколу.
|
||
* Створення `Client` обирає транспорт. `async with` його відкриває.
|
||
|
||
Щойно транспорт відкрито, обидві сторони мають домовитися про версію протоколу. Зазвичай про це не думаєш; а коли доводиться — є сторінка **[Версії протоколу](../protocol-versions.md)**.
|