219 lines
18 KiB
Markdown
219 lines
18 KiB
Markdown
---
|
||
translation:
|
||
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
|
||
tool: 1
|
||
---
|
||
# Клієнт {#the-client}
|
||
|
||
**`Client`** — це те, через що програма на Python спілкується з MCP-сервером.
|
||
|
||
Це один об'єкт з одним життєвим циклом: створіть його, увійдіть в `async with`, викликайте методи. Кожна дія протоколу (перелічити інструменти, викликати один із них, прочитати ресурс, відрендерити промпт) — це його `async`-метод, що повертає типізований результат.
|
||
|
||
## Перший клієнт {#your-first-client}
|
||
|
||
```python title="client.py" hl_lines="14-18"
|
||
--8<-- "docs_src/client/tutorial001.py"
|
||
```
|
||
|
||
Сервер угорі потрібен лише для того, щоб було до чого під'єднатися. Клієнт — це п'ять виділених рядків.
|
||
|
||
* `Client(mcp)` отримує **сам об'єкт сервера**. Це транспорт у пам'яті: без підпроцесу, без порту, без HTTP. Саме так під'єднується кожен приклад на цій сторінці й кожен тест, який ви напишете.
|
||
* `async with` — це **життєвий цикл**. Вхід у блок під'єднує й узгоджує параметри; вихід — від'єднує. Пари `connect()` / `close()` немає, а `Client` не можна використати повторно після завершення блоку.
|
||
* Усередині блоку відомості про з'єднання вже доступні як звичайні властивості.
|
||
|
||
### Що можна передати в `Client` {#what-you-can-pass-to-client}
|
||
|
||
`Client` приймає один позиційний аргумент і визначає транспорт за його типом:
|
||
|
||
* Екземпляр `MCPServer` (або низькорівневого `Server`): під'єднання **в межах процесу**.
|
||
* Рядок з URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, шлях для робочого розгортання.
|
||
* `StdioServerParameters`: команда, яку буде запущено як **підпроцес**; спілкування з ним іде через його stdin і stdout.
|
||
* **Транспорт**: будь-що, що можна використати як `async with ... as (read, write)`, наприклад `streamable_http_client(url, http_client=...)` навколо вашого власного HTTP-клієнта.
|
||
|
||
Усе інше на цій сторінці однакове для всіх чотирьох. Заголовки, підпроцеси, тайм-аути та протокол `Transport` мають власну сторінку: **[Транспорти клієнта](transports.md)**.
|
||
|
||
### Що є в під'єднаного клієнта {#whats-on-a-connected-client}
|
||
|
||
Чотири властивості лише для читання, заповнені в мить входу в блок:
|
||
|
||
* `client.server_info`: ідентичність сервера або `None` для сервера покоління 2026, який її не повідомляє (сервери python-sdk за замовчуванням повідомляють). `server_info.name` тут — `"Bookshop"`, а `server_info.version` — те, що повідомить сервер.
|
||
* `client.server_capabilities`: що вміє сервер (`tools`, `resources`, `prompts`, `completions`, ...). Можливість, якої сервер не має, дорівнює `None`.
|
||
* `client.protocol_version`: версія протоколу, про яку домовилися обидві сторони. Тут це `"2026-07-28"`.
|
||
* `client.instructions`: рядок `instructions=` сервера або `None`, якщо сервер його не задав.
|
||
|
||
Версію протоколу ви не обирали. За замовчуванням `Client` зондує сервер і на старіших повертається до класичного рукостискання, тож один клієнт працює із сервером будь-якого покоління. Якщо потрібно цим керувати, докладніше — на сторінці **[Версії протоколу](../protocol-versions.md)**.
|
||
|
||
!!! tip
|
||
`client.session` — це базова `ClientSession`, низькорівневий запасний вихід.
|
||
Для жодної задачі на цій сторінці вона не знадобиться.
|
||
|
||
## Перелік інструментів {#listing-tools}
|
||
|
||
```python title="client.py" hl_lines="15-20"
|
||
--8<-- "docs_src/client/tutorial002.py"
|
||
```
|
||
|
||
`list_tools()` повертає `ListToolsResult`; інструменти лежать у `.tools`. Кожен із них — повне означення, яке хост передав би моделі:
|
||
|
||
```python
|
||
tool.name # 'search_books'
|
||
tool.title # 'Search the catalog'
|
||
tool.description # 'Search the catalog by title or author.'
|
||
```
|
||
|
||
а `tool.input_schema` — це JSON Schema, яку сервер вивів з анотацій типів функції:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"query": {"title": "Query", "type": "string"},
|
||
"limit": {"default": 10, "title": "Limit", "type": "integer"}
|
||
},
|
||
"required": ["query"],
|
||
"title": "search_booksArguments"
|
||
}
|
||
```
|
||
|
||
Ця схема — усе, що потрібно UI, щоб показати форму аргументів, і все, що потрібно моделі, щоб сформувати коректні аргументи.
|
||
|
||
!!! tip
|
||
`title` необов'язковий, тож UI, що показує інструменти людині, має обирати: `title`, якщо він є,
|
||
і `name`, якщо немає. `from mcp.shared.metadata_utils import get_display_name` робить саме це —
|
||
для інструментів, ресурсів, шаблонів ресурсів і промптів.
|
||
|
||
## Виклик інструмента {#calling-a-tool}
|
||
|
||
`call_tool(name, arguments)` запускає інструмент і повертає `CallToolResult`.
|
||
|
||
```python title="client.py" hl_lines="27-34"
|
||
--8<-- "docs_src/client/tutorial003.py"
|
||
```
|
||
|
||
Серверний `lookup_book` повертає Pydantic-модель `Book`. Ось що бачить клієнт:
|
||
|
||
```python
|
||
result.content # [TextContent(type='text', text='{\n "title": "Dune",\n "author": "Frank Herbert",\n "year": 1965\n}')]
|
||
result.structured_content # {'title': 'Dune', 'author': 'Frank Herbert', 'year': 1965}
|
||
result.is_error # False
|
||
```
|
||
|
||
Одне повернене значення, три речі для читання. У кожної свій споживач.
|
||
|
||
### `content`: що читає модель {#content-what-the-model-reads}
|
||
|
||
`content` — це `list` **блоків вмісту**, а блок вмісту — це об'єднання типів: `TextContent`, `ImageContent`, `AudioContent`, `ResourceLink` або `EmbeddedResource`. Інструмент може повернути кілька блоків, різних видів.
|
||
|
||
Саме тому `main` звужує тип через `isinstance(block, TextContent)`, перш ніж звертатися до `block.text`. Зверніть увагу: поза `isinstance` немає жодного `.text` — перевірка типів цього не дозволить, бо `ImageContent` має `.data`, а не `.text`. Об'єднання чесно показує, що інструменту дозволено вам надіслати; ваш код має бути таким самим чесним.
|
||
|
||
### `structured_content`: що читає ваш застосунок {#structured_content-what-your-application-reads}
|
||
|
||
`structured_content` — це повернене значення інструмента у вигляді JSON, що відповідає оголошеній `output_schema` інструмента. Жодного розбору рядків, жодних здогадок.
|
||
|
||
Коли є обидва, вони навмисно кажуть те саме двічі: `content` — для моделі, `structured_content` — для коду. Звідки береться структурована половина і як нею керувати — на сторінці **[Структурований вивід](../servers/structured-output.md)**.
|
||
|
||
### `is_error`: чи завершився інструмент помилкою {#is_error-whether-the-tool-failed}
|
||
|
||
Інструмент, що викидає виняток, **не** викидає його у вашому клієнті. Він повертається як звичайний результат з `is_error=True`.
|
||
|
||
!!! check
|
||
Попросіть у `lookup_book` `"Solaris"` (назву, якої немає в каталозі) — і функція викине
|
||
`ToolError`. Виклик усе одно повернеться нормально:
|
||
|
||
```python
|
||
result.is_error # True
|
||
result.content # [TextContent(type='text', text="Error executing tool lookup_book: No book titled 'Solaris' in the catalog.")]
|
||
result.structured_content # None
|
||
```
|
||
|
||
Повідомлення `ToolError` потрапило в `content`, де його може прочитати **модель** і спробувати ще раз. Це
|
||
навмисно: помилка інструмента — частина розмови, а не аварія. (Якби інструмент упав
|
||
з якимось іншим винятком, у `content` було б лише `Error executing tool lookup_book`.) Завжди дивіться на
|
||
`is_error`, перш ніж довіряти `structured_content`.
|
||
|
||
!!! warning
|
||
`is_error=True` охоплює більше, ніж ваш власний `raise`. Попросіть інструмент, якого в сервера
|
||
взагалі немає (`call_tool("does_not_exist", {})`), — і нічого не викидається. Повертається та сама форма:
|
||
`is_error=True` з `Unknown tool: does_not_exist` у `content`. Метод `Client` викидає
|
||
`MCPError` лише тоді, коли сервер відповідає **помилкою** JSON-RPC замість результату, а коли
|
||
сервер повертає що саме — описано на сторінці **[Обробка помилок](../servers/handling-errors.md)**.
|
||
|
||
## Ресурси {#resources}
|
||
|
||
Дії з ресурсами йдуть парами: два способи перелічити, один спосіб прочитати.
|
||
|
||
```python title="client.py" hl_lines="22-31"
|
||
--8<-- "docs_src/client/tutorial004.py"
|
||
```
|
||
|
||
* `list_resources()` повертає **конкретні** ресурси — ті, що мають фіксований URI. Тут: `['catalog://genres']`.
|
||
* `list_resource_templates()` повертає **параметризовані**. Тут: `['catalog://genres/{genre}']`. Це два різні списки, бо шаблон не можна прочитати, доки його не заповнено.
|
||
* `read_resource(uri)` приймає URI як звичайний `str` і працює з обома: передайте `"catalog://genres/poetry"` — і сервер зіставить його з шаблоном.
|
||
|
||
`read_resource` повертає `contents` — список `TextResourceContents` або `BlobResourceContents`. Та сама ідея, що й із вмістом інструментів: звузьте тип через `isinstance`, потім читайте `.text` (або `.blob`).
|
||
|
||
Клієнта також можна сповіщати про зміни ресурсу. На з'єднаннях покоління 2025 це `subscribe_resource(uri)` / `unsubscribe_resource(uri)` — пара методів, яку `MCPServer` не реалізує, тож у протоколі 2026-07-28 (де цих дій уже немає) запит повертає `-32601`, *Method not found*. Заміна у версії 2026 — потік `subscriptions/listen`, який `MCPServer` *таки* обслуговує — `server_capabilities.resources.subscribe` там дорівнює `True` — а як споживати його через `client.listen(...)`, описано на сторінці **[Підписки](subscriptions.md)** цього розділу.
|
||
|
||
## Промпти {#prompts}
|
||
|
||
```python title="client.py" hl_lines="15-20"
|
||
--8<-- "docs_src/client/tutorial005.py"
|
||
```
|
||
|
||
`list_prompts()` повідомляє, що пропонує сервер і що потрібно кожному промпту:
|
||
|
||
```python
|
||
prompt.name # 'recommend'
|
||
prompt.title # 'Recommend a book'
|
||
prompt.arguments # [PromptArgument(name='genre', required=True)]
|
||
```
|
||
|
||
`get_prompt(name, arguments)` рендерить його. Словник аргументів — `str -> str`: аргументи промпту завжди рядки. Результат — `messages`, список `PromptMessage`, кожне з `role` і блоком `content`:
|
||
|
||
```python
|
||
message.role # 'user'
|
||
message.content # TextContent(type='text', text='Recommend one poetry book from the catalog and say why.')
|
||
```
|
||
|
||
Хост передає ці повідомлення прямо моделі. Оце й уся можливість.
|
||
|
||
## Автодоповнення {#completions}
|
||
|
||
Сервер з обробником автодоповнення може доповнювати аргументи промптів і шаблонів ресурсів, поки користувач друкує.
|
||
|
||
```python title="client.py" hl_lines="27-31"
|
||
--8<-- "docs_src/client/tutorial006.py"
|
||
```
|
||
|
||
* `ref` вказує, *який* промпт чи шаблон ви заповнюєте: `PromptReference` або `ResourceTemplateReference`.
|
||
* `argument` — це `{"name": ..., "value": ...}`: аргумент і те, що користувач уже встиг набрати.
|
||
|
||
Відповідь — у `result.completion.values`. Наберіть `"p"` — і сервер поверне `['poetry']`. Серверний бік, а також те, як обробник використовує *інші*, уже заповнені аргументи, щоб звузити свої пропозиції, — на сторінці **[Автодоповнення](../servers/completions.md)**.
|
||
|
||
## Пагінація {#pagination}
|
||
|
||
Кожен метод `list_*` приймає іменований аргумент `cursor=`, а кожен результат містить `next_cursor`. Коли `next_cursor` дорівнює `None`, у вас є все.
|
||
|
||
```python title="client.py" hl_lines="22-30"
|
||
--8<-- "docs_src/client/tutorial007.py"
|
||
```
|
||
|
||
Цей цикл коректний для будь-якого сервера. `MCPServer` повертає все однією сторінкою, тож `next_cursor` дорівнює `None` і цикл виконується один раз — саме тому більшість коду його ніколи не пише. Сервери, що справді розбивають результати на сторінки, і правила, яким підкоряються курсори, — на сторінці **[Пагінація](../advanced/pagination.md)**.
|
||
|
||
## У тестах {#in-tests}
|
||
|
||
`Client(mcp)` без процесу й без порту — це вже тестова обв'язка для вашого сервера.
|
||
|
||
Саме для цього є один прапорець конструктора: `Client(mcp, raise_exceptions=True)`. Він діє лише на з'єднаннях у пам'яті, а пояснює його й будує навколо нього весь підхід сторінка **[Тестування](../get-started/testing.md)**.
|
||
|
||
## Підсумки {#recap}
|
||
|
||
* `Client(x)` під'єднується в пам'яті до об'єкта сервера, через Streamable HTTP — до рядка з URL і через транспорт — до всього іншого.
|
||
* `async with` — це весь життєвий цикл. Усередині нього `server_capabilities` і `protocol_version` уже заповнені; `server_info` та `instructions` — теж, якщо сервер їх надає.
|
||
* `list_tools()` дає `name`, `title`, `description` та `input_schema` кожного інструмента.
|
||
* `call_tool()` повертає `content` для моделі, `structured_content` для вашого коду та `is_error`. Інструмент, що викидає виняток, — це результат, а не виняток.
|
||
* `content` — об'єднання типів блоків; звужуйте тип через `isinstance`, перш ніж читати.
|
||
* `list_resources` / `list_resource_templates` / `read_resource`, `list_prompts` / `get_prompt` і `complete` доповнюють набір дій.
|
||
* Кожен `list_*` приймає `cursor=`; повторюйте цикл, доки `next_cursor` не стане `None`.
|
||
|
||
Про що сервер може попросити *клієнта* і як на це відповідати — на сторінці **[Колбеки клієнта](callbacks.md)**.
|