docs: refresh translations, and translate pages in parallel (#3458)
This commit is contained in:
parent
08a3bc8eaf
commit
fd66270926
6 changed files with 4821 additions and 2277 deletions
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9e7b9a1710e5aeba, b74ca4c1d2ddddee, fa8714e61bf90c5a, 04db67a886b7271c, 857690fb8f876800]
|
||||
sections: [9e7b9a1710e5aeba, 66a2e9acc9101d54, d3d25aa802f5145a, 04db67a886b7271c, 857690fb8f876800]
|
||||
tool: 1
|
||||
---
|
||||
# Підказки щодо кешування {#caching-hints}
|
||||
|
|
@ -30,7 +30,7 @@ translation:
|
|||
|
||||
На низькорівневому `Server` обробники будують результати вручну, а `ttl_ms` / `cache_scope` — звичайні поля моделей результатів. Обробник, який задає їх явно, завжди перемагає мапу конструктора, поле за полем:
|
||||
|
||||
```python title="server.py" hl_lines="10 16"
|
||||
```python title="server.py" hl_lines="11 17"
|
||||
--8<-- "docs_src/caching/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -44,10 +44,24 @@ translation:
|
|||
|
||||
У сесії 2026-07-28 `Client` дотримується підказок за вас: він має вбудований кеш відповідей, увімкнений за замовчуванням. Результат, що прийшов із `ttlMs`, зберігається, і ідентичний виклик у межах цього TTL обслуговується з кешу без звернення до сервера. Результат *без* підказки не кешується: результати без підказок отримують `CacheConfig.default_ttl_ms`, типове значення якого `0` (одразу застарілий), тож сервер, який нічого не оголошує, бачить рівно той самий трафік виклик-за-викликом, що й завжди.
|
||||
|
||||
```python title="client.py" hl_lines="33 35 38"
|
||||
Щоб побачити це на власні очі, запустіть `server.py` з попереднього розділу через uvicorn (його останній рядок будує ASGI-застосунок). Обробник друкує рядок щоразу, коли справді виконується:
|
||||
|
||||
```console
|
||||
uvicorn server:app --port 8000
|
||||
```
|
||||
|
||||
```python title="client.py" hl_lines="20 23 28"
|
||||
--8<-- "docs_src/caching/tutorial003.py"
|
||||
```
|
||||
|
||||
Запустіть `python client.py` у другому терміналі. Він надрукує підказки, які містив перший результат: `ttlMs` обробника поруч із `cacheScope` з мапи:
|
||||
|
||||
```text
|
||||
1000 public
|
||||
```
|
||||
|
||||
Решту розповідає термінал сервера: між логами запитів uvicorn рядок `tools/list served` з'являється тричі.
|
||||
|
||||
Чотири виклики, три отримання. Другий виклик знайшов свіжий запис і не дійшов до сервера; переведення (впровадженого) годинника за межу TTL змусило третій знову звернутися до сервера; четвертий сказав `cache_mode="refresh"`. Цей іменований аргумент є в п'яти методів із кешуванням (`list_tools`, `list_prompts`, `list_resources`, `list_resource_templates`, `read_resource`):
|
||||
|
||||
* `"use"` (за замовчуванням) віддає свіжий запис, якщо він є, а якщо ні — звертається до сервера і зберігає результат.
|
||||
|
|
@ -56,7 +70,7 @@ translation:
|
|||
|
||||
Одне правило стоїть над `"use"`: **виклики з `meta` завжди доходять до сервера.** Запит із заданим `meta` (токен перебігу виконання, поля трасування) очікує запиту мережею, тому за `cache_mode="use"` його обробляють як `"refresh"`: читання з кешу пропускається, а отриманий результат усе одно замінює закешований запис. `"bypass"` і явний `"refresh"` поводяться як завжди.
|
||||
|
||||
Щоб повністю вимкнути кешування, створіть клієнт як `Client(server, cache=None)`: кожен виклик знову стає зверненням до сервера, а `cache_mode`, хоч і приймається далі, нічого не робить.
|
||||
Щоб повністю вимкнути кешування, передайте `cache=None` під час створення `Client`: кожен виклик знову стає зверненням до сервера, а `cache_mode`, хоч і приймається далі, нічого не робить.
|
||||
|
||||
Область також дотримується автоматично: записи `"private"` прив'язані до *розділу* (partition) кешу (нижче), тоді як записи `"public"` можуть за бажанням ділитися ширше. А **сповіщення перемагають TTL** для конкретних записів, які вони називають: сповіщення `list_changed` витісняє відповідний закешований список, а `resources/updated` витісняє закешоване читання, збережене рівно під його URI, хоч би якими свіжими вони були. На з'єднанні 2026-07-28 ці сповіщення надходять потоком `subscriptions/listen`, який відкривають через `client.listen(...)`, і витіснення завершується до того, як ваш спостерігач побачить подію; докладніше — на сторінці **[Підписки](subscriptions.md)**.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [adf3c545b5be46b6, 916cd3ab1c03f461, e9be7a8d0eb0a456, 565890a636288ecf, 6af7e49db9129ec3, 06b0238c174186af, 90c6043be435fcb0]
|
||||
sections: [adf3c545b5be46b6, 916cd3ab1c03f461, 32ef568335dd95a7, 565890a636288ecf, 6af7e49db9129ec3, 06b0238c174186af, 0abc5ea5cb7ff6b3]
|
||||
tool: 1
|
||||
---
|
||||
# Колбеки клієнта {#client-callbacks}
|
||||
|
|
@ -63,7 +63,7 @@ result.content # [TextContent(type='text', text='Card issued to Ada Lovelace.')
|
|||
`mode="legacy"` у виклику `Client(...)` стоїть не просто так. За замовчуванням `Client(...)` узгоджує сучасний
|
||||
шлях протоколу, а на ньому немає зворотного каналу (back-channel) для запитів від сервера до клієнта: `ctx.elicit`
|
||||
завершується помилкою ще до того, як запуститься колбек. Вирішує це не транспорт, а узгоджений
|
||||
протокол — однаково і в пам'яті, і за URL. Фіксуйте `mode="legacy"` щоразу, коли клієнт має
|
||||
протокол. Фіксуйте `mode="legacy"` щоразу, коли клієнт має
|
||||
відповідати на такий запит; так робить кожен тест за цією сторінкою. Докладніше — на сторінці **[Версії протоколу](../protocol-versions.md)**.
|
||||
|
||||
У сесії 2026-07-28 колбек не зникає, він просто отримує дані інакше: коли інструмент повертає
|
||||
|
|
@ -151,4 +151,4 @@ result.structured_content # {'result': ['elicitation']}
|
|||
* `sampling_callback` і `list_roots_callback` працюють так само, але обслуговують застарілі можливості; сучасні сервери натомість використовують багатораундові запити.
|
||||
* `logging_callback` і `message_handler` отримують сповіщення. Вони нічого не оголошують.
|
||||
|
||||
Перший аргумент `Client(...)` — об'єкт транспорту. Усі його різновиди описано на сторінці **[Транспорти клієнта](transports.md)**.
|
||||
Перший аргумент `Client(...)` визначає транспорт. Усі його різновиди описано на сторінці **[Транспорти клієнта](transports.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||||
sections: [ebef1e7a0df854f4, 7e16449f66e7dfd6, eeb0682f7d2a1079, 5713f0196a34e6e7, 0e844597859e4248, 3a97d9195ddcc92e, 1da08c483e59c141, 84702cc6e0a1fd42, 8dee7a31c86ffc5c, 83a5bce168ef23d7]
|
||||
tool: 1
|
||||
---
|
||||
# Клієнт {#the-client}
|
||||
|
|
@ -11,13 +11,23 @@ translation:
|
|||
|
||||
## Перший клієнт {#your-first-client}
|
||||
|
||||
```python title="client.py" hl_lines="14-18"
|
||||
Клієнтові потрібен сервер, з яким говорити. До цього Bookshop під'єднується кожен приклад на цій сторінці. Збережіть його як `server.py` і залиште працювати через HTTP:
|
||||
|
||||
```python title="server.py"
|
||||
--8<-- "docs_src/client/tutorial001.py"
|
||||
```
|
||||
|
||||
Сервер угорі потрібен лише для того, щоб було до чого під'єднатися. Клієнт — це п'ять виділених рядків.
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
* `Client(mcp)` отримує **сам об'єкт сервера**. Це транспорт у пам'яті: без підпроцесу, без порту, без HTTP. Саме так під'єднується кожен приклад на цій сторінці й кожен тест, який ви напишете.
|
||||
Тепер сервер доступний за адресою `http://localhost:8000/mcp`. Клієнт — окрема програма. Збережіть його як `client.py` і запустіть `python client.py` у другому терміналі:
|
||||
|
||||
```python title="client.py" hl_lines="7-11"
|
||||
--8<-- "docs_src/client/tutorial001_client.py"
|
||||
```
|
||||
|
||||
* `Client("http://localhost:8000/mcp")` отримує **URL**, тож під'єднується через Streamable HTTP до сервера, який ви щойно запустили.
|
||||
* `async with` — це **життєвий цикл**. Вхід у блок під'єднує й узгоджує параметри; вихід — від'єднує. Пари `connect()` / `close()` немає, а `Client` не можна використати повторно після завершення блоку.
|
||||
* Усередині блоку відомості про з'єднання вже доступні як звичайні властивості.
|
||||
|
||||
|
|
@ -25,10 +35,10 @@ translation:
|
|||
|
||||
`Client` приймає один позиційний аргумент і визначає транспорт за його типом:
|
||||
|
||||
* Екземпляр `MCPServer` (або низькорівневого `Server`): під'єднання **в межах процесу**.
|
||||
* Рядок з URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, шлях для робочого розгортання.
|
||||
* `StdioServerParameters`: команда, яку буде запущено як **підпроцес**; спілкування з ним іде через його stdin і stdout.
|
||||
* Рядок з URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, транспорт для робочого розгортання.
|
||||
* `StdioServerParameters`: команда, яку буде запущено як локальний **підпроцес**; спілкування з ним іде через його stdin і stdout.
|
||||
* **Транспорт**: будь-що, що можна використати як `async with ... as (read, write)`, наприклад `streamable_http_client(url, http_client=...)` навколо вашого власного HTTP-клієнта.
|
||||
* Екземпляр `MCPServer` (або низькорівневого `Server`): під'єднання **в межах процесу**, без підпроцесу й без порту. Це для тестів, і на ньому побудована сторінка **[Тестування](../get-started/testing.md)**.
|
||||
|
||||
Усе інше на цій сторінці однакове для всіх чотирьох. Заголовки, підпроцеси, тайм-аути та протокол `Transport` мають власну сторінку: **[Транспорти клієнта](transports.md)**.
|
||||
|
||||
|
|
@ -49,11 +59,11 @@ translation:
|
|||
|
||||
## Перелік інструментів {#listing-tools}
|
||||
|
||||
```python title="client.py" hl_lines="15-20"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client/tutorial002.py"
|
||||
```
|
||||
|
||||
`list_tools()` повертає `ListToolsResult`; інструменти лежать у `.tools`. Кожен із них — повне означення, яке хост передав би моделі:
|
||||
`list_tools()` повертає `ListToolsResult`; інструменти лежать у `.tools`. Кожен із них — повне означення, яке хост передав би моделі. Ось перший:
|
||||
|
||||
```python
|
||||
tool.name # 'search_books'
|
||||
|
|
@ -77,6 +87,8 @@ tool.description # 'Search the catalog by title or author.'
|
|||
|
||||
Ця схема — усе, що потрібно UI, щоб показати форму аргументів, і все, що потрібно моделі, щоб сформувати коректні аргументи.
|
||||
|
||||
Другий інструмент, `lookup_book`, зареєстровано без `title=`, тож його `tool.title` дорівнює `None`.
|
||||
|
||||
!!! tip
|
||||
`title` необов'язковий, тож UI, що показує інструменти людині, має обирати: `title`, якщо він є,
|
||||
і `name`, якщо немає. `from mcp.shared.metadata_utils import get_display_name` робить саме це —
|
||||
|
|
@ -86,7 +98,7 @@ tool.description # 'Search the catalog by title or author.'
|
|||
|
||||
`call_tool(name, arguments)` запускає інструмент і повертає `CallToolResult`.
|
||||
|
||||
```python title="client.py" hl_lines="27-34"
|
||||
```python title="client.py" hl_lines="9-16"
|
||||
--8<-- "docs_src/client/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -142,7 +154,7 @@ result.is_error # False
|
|||
|
||||
Дії з ресурсами йдуть парами: два способи перелічити, один спосіб прочитати.
|
||||
|
||||
```python title="client.py" hl_lines="22-31"
|
||||
```python title="client.py" hl_lines="9-18"
|
||||
--8<-- "docs_src/client/tutorial004.py"
|
||||
```
|
||||
|
||||
|
|
@ -156,7 +168,7 @@ result.is_error # False
|
|||
|
||||
## Промпти {#prompts}
|
||||
|
||||
```python title="client.py" hl_lines="15-20"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client/tutorial005.py"
|
||||
```
|
||||
|
||||
|
|
@ -181,7 +193,7 @@ message.content # TextContent(type='text', text='Recommend one poetry book from
|
|||
|
||||
Сервер з обробником автодоповнення може доповнювати аргументи промптів і шаблонів ресурсів, поки користувач друкує.
|
||||
|
||||
```python title="client.py" hl_lines="27-31"
|
||||
```python title="client.py" hl_lines="9-13"
|
||||
--8<-- "docs_src/client/tutorial006.py"
|
||||
```
|
||||
|
||||
|
|
@ -194,21 +206,21 @@ message.content # TextContent(type='text', text='Recommend one poetry book from
|
|||
|
||||
Кожен метод `list_*` приймає іменований аргумент `cursor=`, а кожен результат містить `next_cursor`. Коли `next_cursor` дорівнює `None`, у вас є все.
|
||||
|
||||
```python title="client.py" hl_lines="22-30"
|
||||
```python title="client.py" hl_lines="7-15"
|
||||
--8<-- "docs_src/client/tutorial007.py"
|
||||
```
|
||||
|
||||
Цей цикл коректний для будь-якого сервера. `MCPServer` повертає все однією сторінкою, тож `next_cursor` дорівнює `None` і цикл виконується один раз — саме тому більшість коду його ніколи не пише. Сервери, що справді розбивають результати на сторінки, і правила, яким підкоряються курсори, — на сторінці **[Пагінація](../advanced/pagination.md)**.
|
||||
`list_all_tools` коректна для будь-якого сервера. `MCPServer` повертає все однією сторінкою, тож `next_cursor` дорівнює `None` і цикл виконується один раз — саме тому більшість коду його ніколи не пише. Сервери, що справді розбивають результати на сторінки, і правила, яким підкоряються курсори, — на сторінці **[Пагінація](../advanced/pagination.md)**.
|
||||
|
||||
## У тестах {#in-tests}
|
||||
|
||||
`Client(mcp)` без процесу й без порту — це вже тестова обв'язка для вашого сервера.
|
||||
Кожен `client.py` на цій сторінці звертався до `server.py` через HTTP. У тесті мережу оминають і передають у `Client` сам об'єкт сервера: `from server import mcp`, потім `Client(mcp)`. Без процесу, без порту, а всі методи вище працюють так само.
|
||||
|
||||
Саме для цього є один прапорець конструктора: `Client(mcp, raise_exceptions=True)`. Він діє лише на з'єднаннях у пам'яті, а пояснює його й будує навколо нього весь підхід сторінка **[Тестування](../get-started/testing.md)**.
|
||||
Саме для цього є один прапорець конструктора: `Client(mcp, raise_exceptions=True)`. Він діє лише на з'єднаннях у межах процесу, а пояснює його й будує навколо нього весь підхід сторінка **[Тестування](../get-started/testing.md)**.
|
||||
|
||||
## Підсумки {#recap}
|
||||
|
||||
* `Client(x)` під'єднується в пам'яті до об'єкта сервера, через Streamable HTTP — до рядка з URL і через транспорт — до всього іншого.
|
||||
* `Client(x)` під'єднується через Streamable HTTP до рядка з URL, запускає підпроцес для `StdioServerParameters`, входить у транспорт напряму, а в тестах приймає сам об'єкт сервера.
|
||||
* `async with` — це весь життєвий цикл. Усередині нього `server_capabilities` і `protocol_version` уже заповнені; `server_info` та `instructions` — теж, якщо сервер їх надає.
|
||||
* `list_tools()` дає `name`, `title`, `description` та `input_schema` кожного інструмента.
|
||||
* `call_tool()` повертає `content` для моделі, `structured_content` для вашого коду та `is_error`. Інструмент, що викидає виняток, — це результат, а не виняток.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [c6899d3892bd9fa0, 79372cff3cc48a88, 63878d29e87c3e73, 13175843d3588af4, e7e2b9fd516f77de, 758f06399b513c1f, a05d7278487d610b]
|
||||
sections: [c6899d3892bd9fa0, 79372cff3cc48a88, c2dae1ebe2ebd543, 13175843d3588af4, df06056fb16b3846, 758f06399b513c1f, a05d7278487d610b]
|
||||
tool: 1
|
||||
---
|
||||
# OAuth-клієнти {#oauth-clients}
|
||||
|
|
@ -81,18 +81,20 @@ translation:
|
|||
|
||||
Коли `Client` уперше надсилає запит, сервер відповідає `401`. Далі справу бере на себе провайдер:
|
||||
|
||||
1. **Виявлення.** Він читає заголовок `WWW-Authenticate`, завантажує Protected Resource Metadata сервера з `/.well-known/oauth-protected-resource`, дізнається, який сервер авторизації захищає цей ресурс, і завантажує метадані *того* сервера.
|
||||
1. **Виявлення.** Він читає заголовок `WWW-Authenticate`, завантажує Protected Resource Metadata сервера з `/.well-known/oauth-protected-resource`, дізнається, який сервер авторизації захищає цей ресурс, і завантажує метадані *того* сервера. (Старіший сервер, що не публікує метаданих ресурсу, натомість запитують про метадані сервера авторизації за його власним origin.) У будь-якому разі метадані мають називати своїм `issuer` той сервер, для якого їх завантажено; усе інше відхиляється.
|
||||
2. **Реєстрація.** У сховищі порожньо? Він динамічно реєструє вас із вашими `OAuthClientMetadata` і зберігає результат.
|
||||
3. **Авторизація.** Він генерує пару PKCE і `state`, будує URL авторизації, очікує ваш `redirect_handler`, а потім — ваш `callback_handler`, щоб отримати код.
|
||||
4. **Обмін.** Він міняє код на `OAuthToken`, зберігає його й повторює ваш початковий запит із `Authorization: Bearer ...`.
|
||||
|
||||
Після цього все тихо. Токени беруться зі сховища, прострочений токен доступу оновлюється за допомогою токена оновлення, і лише коли ніщо з цього не спрацьовує, провайдер запускає потік знову.
|
||||
|
||||
Для всіх цих запитів діє одне транспортне правило: як і MCP-запит, усередині якого вони виконуються, вони переходять за перенаправленням лише тоді, коли воно лишається на тому самому origin і зберігає метод (скажімо, 307/308 на кінцеву скісну риску), а будь-яке інше перенаправлення трактують так, ніби цей URL не відповідає.
|
||||
|
||||
Нічого з цього ви не писали. Лишаються ще два іменовані аргументи (`client_metadata_url` і `validate_resource_url`), і цьому файлу не потрібен жоден. Знати варто про `client_metadata_url`; йому присвячено окремий розділ нижче.
|
||||
|
||||
### Спробуйте самі {#try-it}
|
||||
|
||||
Більшість прикладів у цій документації можна перевірити за допомогою `Client(server)` у пам'яті. Цей — ні: уся суть потоку в HTTP-відповіді `401`, а між клієнтом у пам'яті та його сервером HTTP немає.
|
||||
`Client(server)` у пам'яті, яким користуються ваші тести, тут не допоможе: уся суть потоку в HTTP-відповіді `401`, а між клієнтом у пам'яті та його сервером HTTP немає.
|
||||
|
||||
У репозиторії є жива версія. `examples/servers/simple-auth/` запускає окремий сервер авторизації та захищений MCP-сервер; `examples/clients/simple-auth-client/` — це клієнт із цієї сторінки, що виріс у невеликий CLI. У його README є дві команди: запустіть сервери, запустіть клієнт проти них — і спостерігайте, як проходять усі чотири кроки.
|
||||
|
||||
|
|
@ -110,13 +112,14 @@ URL має бути HTTPS із некореневим шляхом; усе ін
|
|||
|
||||
`ClientCredentialsOAuthProvider` — це той самий `httpx2.Auth`, тільки без людини:
|
||||
|
||||
```python title="client.py" hl_lines="4 27-33"
|
||||
```python title="client.py" hl_lines="4 27-34"
|
||||
--8<-- "docs_src/oauth_clients/tutorial002.py"
|
||||
```
|
||||
|
||||
Що змінилося:
|
||||
|
||||
* Немає `OAuthClientMetadata`, немає обробників. Ви передаєте `client_id` і `client_secret`; провайдер будує навколо них мінімальну реєстрацію `client_credentials` і повністю пропускає динамічну реєстрацію.
|
||||
* `issuer` називає сервер авторизації, що видав ці облікові дані; використовуйте значення `issuer`, яке повертає його документ `/.well-known/oauth-authorization-server`. Виявлення й далі виконується, як описано вище, але запити на токен будуються лише з метаданих *цього* видавця; якщо MCP-сервер указує кудись інде, потік натомість зупиняється з `OAuthFlowError`. Пропускати його — застаріла практика, і у версії 3.0 він стане обов'язковим (див. **[Застарілі можливості](../deprecated.md#deprecated-sdk-helpers)**); доти провайдер попереджає й використовує той сервер авторизації, який знайде виявлення.
|
||||
* `scope` — це рядок, розділений пробілами: такий формат OAuth використовує в переданих даних.
|
||||
* Усе далі ідентичне: той самий `TokenStorage`, той самий `httpx2.AsyncClient(auth=...)`, той самий `streamable_http_client`.
|
||||
|
||||
|
|
@ -129,7 +132,7 @@ URL має бути HTTPS із некореневим шляхом; усе ін
|
|||
Ще один провайдер живе в `mcp.client.auth.extensions.client_credentials`:
|
||||
**`PrivateKeyJWTOAuthProvider`** — для клієнтів, які автентифікуються за допомогою JWT замість
|
||||
спільного секрету (`private_key_jwt`, варіант із парою ключів та workload identity). Він дотримується
|
||||
тієї ж схеми: створіть екземпляр і передайте в `auth=`. У тому ж модулі є
|
||||
тієї ж схеми: створіть екземпляр (він приймає той самий необов'язковий `issuer`) і передайте в `auth=`. У тому ж модулі є
|
||||
`SignedJWTParameters` і `static_assertion_provider` — два допоміжні засоби, що будують для нього assertion.
|
||||
|
||||
Є ще одна ситуація без людини: клієнт належить підприємству, де провайдер ідентичності, а не користувач, вирішує, до яких MCP-серверів він може звертатися. Це інший грант із власною моделлю довіри та власною сторінкою — **[Твердження про ідентичність](identity-assertion.md)**.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [09c857a25a9dc37a, 43bc6a76a243a50e, 0a716022a88768df, 4b7f78042bfcfff7, c112662e61b03315, 58974ba1f489a8b4, d18adbdbb835ea73]
|
||||
sections: [09c857a25a9dc37a, 43bc6a76a243a50e, 0a716022a88768df, 4b7f78042bfcfff7, c112662e61b03315, 58974ba1f489a8b4, ed4d17e894864056]
|
||||
tool: 1
|
||||
---
|
||||
# Групи сесій {#session-groups}
|
||||
|
|
@ -78,7 +78,7 @@ translation:
|
|||
## Підсумки {#recap}
|
||||
|
||||
* `ClientSessionGroup` тримає багато з'єднань із серверами й зводить їхні інструменти, ресурси й промпти в один `dict` кожного виду.
|
||||
* `connect_to_server(params)` для кожного сервера. Він приймає параметри транспорту й ніколи — об'єкт сервера чи URL, як `Client`.
|
||||
* `connect_to_server(params)` для кожного сервера. Він приймає параметри транспорту й ніколи — URL чи `Transport`, які приймає `Client`.
|
||||
* `group.call_tool(name, arguments)` сам спрямовує виклик на сервер-власник.
|
||||
* Імена мають бути унікальними в межах усієї групи; два сервери з інструментом `search` самі по собі співіснувати не можуть.
|
||||
* `component_name_hook=` переписує кожне зареєстроване ім'я. Змінюється ключ словника, а не ім'я в переданих даних.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
|
||||
sections: [9cac816674181eb0, 7c157764133fea1f, 40b4916d82eaf1d4, 10d151f2cc75317f, 3d0832f39b0d7059, 92742ba36533633d, 0aeca6145e7bd302]
|
||||
tool: 1
|
||||
---
|
||||
# Транспорти клієнта {#client-transports}
|
||||
|
|
@ -11,30 +11,15 @@ translation:
|
|||
|
||||
*Серверний* бік кожного з них (що робить `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**, транспорт, за яким розгортають сервер:
|
||||
Передайте рядок з 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-секундний тайм-аут на читання, бо сервер може тримати потік відповіді відкритим.
|
||||
Оце й увесь продакшен-клієнт. `Client` сам загортає URL у `streamable_http_client(...)` поверх `httpx2.AsyncClient`, налаштованого так, як потрібно MCP: 30-секундний тайм-аут на connect/write/pool і 300-секундний тайм-аут на читання, бо сервер може тримати потік відповіді відкритим.
|
||||
|
||||
!!! check
|
||||
Створений `Client` ще **не** під'єднаний. Конструктор лише обирає транспорт;
|
||||
|
|
@ -50,7 +35,7 @@ translation:
|
|||
|
||||
Щойно знадобиться заголовок `Authorization`, cookie, проксі, mTLS чи інший тайм-аут — створіть `httpx2.AsyncClient` самостійно й передайте його в `streamable_http_client`:
|
||||
|
||||
```python title="client.py" hl_lines="8-14"
|
||||
```python title="client.py" hl_lines="8-13"
|
||||
--8<-- "docs_src/client_transports/tutorial003.py"
|
||||
```
|
||||
|
||||
|
|
@ -80,9 +65,30 @@ translation:
|
|||
!!! info
|
||||
`httpx2` зберігає знайомий API `httpx`, тож якщо ви знаєте `httpx`, то вже вмієте робити тут автентифікацію,
|
||||
проксі, хуки подій, повторні спроби й обмеження з'єднань. SDK нічого не додає зверху й нічого не
|
||||
забирає. Саме сюди під'єднується й OAuth:
|
||||
забирає, окрім [обробки перенаправлень](#redirects). Саме сюди під'єднується й OAuth:
|
||||
`httpx2.AsyncClient(auth=OAuthClientProvider(...))`. Увесь цей процес описано на сторінці **[OAuth-клієнти](oauth-clients.md)**.
|
||||
|
||||
### Перенаправлення {#redirects}
|
||||
|
||||
Транспорт під'єднується до URL, який ви йому передали, і лише до цього origin.
|
||||
|
||||
* Перенаправлення `307`/`308`, що лишається в межах тієї самої схеми, хоста й порту, виконується; так само й `http://` → `https://` на тому самому хості. Це покриває звичне перенаправлення з кінцевою скісною рискою `/mcp` → `/mcp/`.
|
||||
* Перенаправлення будь-куди інде **не** виконується. Виклик завершується помилкою:
|
||||
|
||||
```text
|
||||
MCPError: Redirect to https://other.example.com/mcp not followed; use that URL as the endpoint if it is the intended server
|
||||
```
|
||||
|
||||
Якщо цей URL — сервер, який ви мали на увазі, впишіть його в конфігурацію. Якщо ні — сервер або проксі перед ним налаштовано неправильно.
|
||||
|
||||
Це стосується будь-якого `httpx2.AsyncClient`, який ви передаєте: його налаштування `follow_redirects` для MCP-запитів не враховується — в жоден бік. OAuth-провайдери SDK застосовують те саме правило до власних запитів.
|
||||
|
||||
!!! tip
|
||||
`Redirect to http://… not followed: it would downgrade this HTTPS endpoint to plain HTTP` означає, що
|
||||
сервер стоїть за проксі з термінацією TLS, про який не знає, і видає перенаправлення на `http://`.
|
||||
Це виправляють на сервері (**[Розгортання та масштабування](../run/deploy.md#behind-a-tls-terminating-proxy)**)
|
||||
або використанням точного URL `https://…/`, який підказує повідомлення.
|
||||
|
||||
## stdio {#stdio}
|
||||
|
||||
Сервер **stdio** — це підпроцес. Клієнт запускає його, пише JSON-RPC в його stdin і читає JSON-RPC з його stdout. Саме так десктопний хост запускає сервер на вашій машині: хост і *є* цим кодом плюс UI, а сторінка **[Під'єднання до справжнього хоста](../get-started/real-host.md)** показує ті самі стосунки з боку хоста — як конфігураційний файл.
|
||||
|
|
@ -105,6 +111,18 @@ stderr дочірнього процесу йде у ваш. Щоб спряму
|
|||
Сервер, якому потрібен ключ API, там його не знайде. Передайте його явно через `env=`; ці
|
||||
змінні накладаються поверх списку дозволених. Саме це й робить `BOOKSHOP_API_KEY` вище.
|
||||
|
||||
## У пам'яті {#in-memory}
|
||||
|
||||
У тесті нема чого розгортати й нема чого запускати. Передайте сам об'єкт сервера:
|
||||
|
||||
```python hl_lines="14"
|
||||
--8<-- "docs_src/client_transports/tutorial001.py"
|
||||
```
|
||||
|
||||
Жодного підпроцесу, жодного порту, жодних байтів у мережі. Клієнт і сервер — це два об'єкти в одному процесі, а виклик усе одно проходить через справжній протокольний рівень: `search_books` перелічується, валідується й викликається точнісінько так само, як це було б через HTTP. Сторінка **[Тестування](../get-started/testing.md)** будує довкола цього весь підхід.
|
||||
|
||||
Ця сама форма водночас слугує API для вбудовування: застосунок, який сам створює сервер, може викликати його інструменти без мережевого переходу.
|
||||
|
||||
## SSE {#sse}
|
||||
|
||||
`sse_client(url)` з `mcp.client.sse` — це HTTP-транспорт, на зміну якому прийшов Streamable HTTP. Загортайте його так само, `Client(sse_client("http://localhost:8000/sse"))`, щоб говорити із сервером, який досі ним користується, — і не будуйте на ньому нічого нового.
|
||||
|
|
@ -113,15 +131,16 @@ stderr дочірнього процесу йде у ваш. Щоб спряму
|
|||
|
||||
Для `Client` усе перелічене вище — одне й те саме.
|
||||
|
||||
**Транспорт** — це будь-який асинхронний контекстний менеджер, що повертає пару потоків повідомлень `(read, write)`: формально — протокол `Transport` у `mcp.client`. `Client` розв'язує свій аргумент за типом: об'єкт сервера під'єднується в межах процесу, `str` стає `streamable_http_client(url)`, `StdioServerParameters` стає `stdio_client(params)`, а в будь-що інше він входить безпосередньо як у транспорт. Саме завдяки останньому правилу `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=` немає.
|
||||
* Перенаправлення виконуються лише в межах власного origin цього URL (`307`/`308` із кінцевою скісною рискою), плюс `http`→`https` на тому самому хості. Усе інше завершується помилкою `Redirect to … not followed`; пропишіть у конфігурації кінцевий URL.
|
||||
* stdio — це `Client(StdioServerParameters(...))`. Загортайте його в `stdio_client(...)` самостійно лише для того, щоб перенаправити stderr дочірнього процесу.
|
||||
* Підпроцес отримує середовище зі списку дозволених, а не ваше; `env=` його доповнює.
|
||||
* `Client(mcp)` (об'єкт сервера) під'єднується в пам'яті. Використовуйте в тестах або щоб вбудувати сервер у застосунок, який його створив.
|
||||
* Транспорт — це будь-що, з чим можна зробити `async with x as (read, write)`. Усе, що не є об'єктом сервера, URL чи `StdioServerParameters`, `Client` передає прямо цьому протоколу.
|
||||
* Створення `Client` обирає транспорт. `async with` його відкриває.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue