Клієнт
Машинний переклад
Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.
Client — це те, через що програма на Python спілкується з MCP-сервером.
Це один об'єкт з одним життєвим циклом: створіть його, увійдіть в async with, викликайте методи. Кожна дія протоколу (перелічити інструменти, викликати один із них, прочитати ресурс, відрендерити промпт) — це його async-метод, що повертає типізований результат.
Перший клієнт
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop", instructions="Search the catalog before recommending a book.")
@mcp.tool()
def search_books(query: str) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r}."
async def main() -> None:
async with Client(mcp) as client:
print(client.server_info)
print(client.server_capabilities)
print(client.protocol_version)
print(client.instructions)
Сервер угорі потрібен лише для того, щоб було до чого під'єднатися. Клієнт — це п'ять виділених рядків.
Client(mcp)отримує сам об'єкт сервера. Це транспорт у пам'яті: без підпроцесу, без порту, без HTTP. Саме так під'єднується кожен приклад на цій сторінці й кожен тест, який ви напишете.async with— це життєвий цикл. Вхід у блок під'єднує й узгоджує параметри; вихід — від'єднує. Париconnect()/close()немає, аClientне можна використати повторно після завершення блоку.- Усередині блоку відомості про з'єднання вже доступні як звичайні властивості.
Що можна передати в Client
Client приймає один позиційний аргумент і визначає транспорт за його типом:
- Екземпляр
MCPServer(або низькорівневогоServer): під'єднання в межах процесу. - Рядок з URL (
Client("http://localhost:8000/mcp")): Streamable HTTP, шлях для робочого розгортання. - Транспорт: будь-що, що можна використати як
async with ... as (read, write), наприкладstdio_client(...), що обгортає підпроцес.
Усе інше на цій сторінці однакове для всіх трьох. Заголовки, підпроцеси, тайм-аути та протокол Transport мають власну сторінку: Транспорти клієнта.
Що є в під'єднаного клієнта
Чотири властивості лише для читання, заповнені в мить входу в блок:
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 зондує сервер і на старіших повертається до класичного рукостискання, тож один клієнт працює із сервером будь-якого покоління. Якщо потрібно цим керувати, докладніше — на сторінці Версії протоколу.
Tip
client.session — це базова ClientSession, низькорівневий запасний вихід.
Для жодної задачі на цій сторінці вона не знадобиться.
Перелік інструментів
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.tool(title="Search the catalog")
def search_books(query: str, limit: int = 10) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r} (showing up to {limit})."
async def main() -> None:
async with Client(mcp) as client:
result = await client.list_tools()
for tool in result.tools:
print(tool.name)
print(tool.title)
print(tool.description)
print(tool.input_schema)
list_tools() повертає ListToolsResult; інструменти лежать у .tools. Кожен із них — повне означення, яке хост передав би моделі:
tool.name # 'search_books'
tool.title # 'Search the catalog'
tool.description # 'Search the catalog by title or author.'
а tool.input_schema — це JSON Schema, яку сервер вивів з анотацій типів функції:
{
"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 робить саме це —
для інструментів, ресурсів, шаблонів ресурсів і промптів.
Виклик інструмента
call_tool(name, arguments) запускає інструмент і повертає CallToolResult.
from pydantic import BaseModel
from mcp import Client
from mcp.server import MCPServer
from mcp.types import TextContent
mcp = MCPServer("Bookshop")
class Book(BaseModel):
title: str
author: str
year: int
@mcp.tool()
def lookup_book(title: str) -> Book:
"""Look up a book by its exact title."""
if title != "Dune":
raise ValueError(f"No book titled {title!r} in the catalog.")
return Book(title="Dune", author="Frank Herbert", year=1965)
async def main() -> None:
async with Client(mcp) as client:
result = await client.call_tool("lookup_book", {"title": "Dune"})
for block in result.content:
if isinstance(block, TextContent):
print(block.text)
print(result.structured_content)
print(result.is_error)
Серверний lookup_book повертає Pydantic-модель Book. Ось що бачить клієнт:
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 — це list блоків вмісту, а блок вмісту — це об'єднання типів: TextContent, ImageContent, AudioContent, ResourceLink або EmbeddedResource. Інструмент може повернути кілька блоків, різних видів.
Саме тому main звужує тип через isinstance(block, TextContent), перш ніж звертатися до block.text. Зверніть увагу: поза isinstance немає жодного .text — перевірка типів цього не дозволить, бо ImageContent має .data, а не .text. Об'єднання чесно показує, що інструменту дозволено вам надіслати; ваш код має бути таким самим чесним.
structured_content: що читає ваш застосунок
structured_content — це повернене значення інструмента у вигляді JSON, що відповідає оголошеній output_schema інструмента. Жодного розбору рядків, жодних здогадок.
Коли є обидва, вони навмисно кажуть те саме двічі: content — для моделі, structured_content — для коду. Звідки береться структурована половина і як нею керувати — на сторінці Структурований вивід.
is_error: чи завершився інструмент помилкою
Інструмент, що викидає виняток, не викидає його у вашому клієнті. Він повертається як звичайний результат з is_error=True.
Check
Попросіть у lookup_book "Solaris" (назву, якої немає в каталозі) — і функція викине
ValueError. Виклик усе одно повернеться нормально:
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
Повідомлення винятку потрапило в content, де його може прочитати модель і спробувати ще раз. Це
навмисно: помилка інструмента — частина розмови, а не аварія. Завжди дивіться на 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 замість результату, а коли
сервер повертає що саме — описано на сторінці Обробка помилок.
Ресурси
Дії з ресурсами йдуть парами: два способи перелічити, один спосіб прочитати.
from mcp import Client
from mcp.server import MCPServer
from mcp.types import TextResourceContents
mcp = MCPServer("Bookshop")
@mcp.resource("catalog://genres")
def genres() -> list[str]:
"""The genres the catalog is organised by."""
return ["fiction", "non-fiction", "poetry"]
@mcp.resource("catalog://genres/{genre}")
def books_in_genre(genre: str) -> str:
"""Every title we stock in one genre."""
return f"3 books filed under {genre}."
async def main() -> None:
async with Client(mcp) as client:
listed = await client.list_resources()
print([resource.uri for resource in listed.resources])
templates = await client.list_resource_templates()
print([template.uri_template for template in templates.resource_templates])
result = await client.read_resource("catalog://genres/poetry")
for contents in result.contents:
if isinstance(contents, TextResourceContents):
print(contents.text)
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(...), описано на сторінці Підписки цього розділу.
Промпти
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.prompt(title="Recommend a book")
def recommend(genre: str) -> str:
"""Ask for a recommendation in a genre."""
return f"Recommend one {genre} book from the catalog and say why."
async def main() -> None:
async with Client(mcp) as client:
listed = await client.list_prompts()
print(listed.prompts)
result = await client.get_prompt("recommend", {"genre": "poetry"})
for message in result.messages:
print(message.role, message.content)
list_prompts() повідомляє, що пропонує сервер і що потрібно кожному промпту:
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:
message.role # 'user'
message.content # TextContent(type='text', text='Recommend one poetry book from the catalog and say why.')
Хост передає ці повідомлення прямо моделі. Оце й уся можливість.
Автодоповнення
Сервер з обробником автодоповнення може доповнювати аргументи промптів і шаблонів ресурсів, поки користувач друкує.
from mcp import Client
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference
mcp = MCPServer("Bookshop")
GENRES = ["fiction", "non-fiction", "poetry"]
@mcp.prompt()
def recommend(genre: str) -> str:
"""Ask for a recommendation in a genre."""
return f"Recommend one {genre} book from the catalog and say why."
@mcp.completion()
async def complete_genre(
ref: PromptReference | ResourceTemplateReference,
argument: CompletionArgument,
context: CompletionContext | None,
) -> Completion | None:
return Completion(values=[genre for genre in GENRES if genre.startswith(argument.value)])
async def main() -> None:
async with Client(mcp) as client:
result = await client.complete(
ref=PromptReference(type="ref/prompt", name="recommend"),
argument={"name": "genre", "value": "p"},
)
print(result.completion.values)
refвказує, який промпт чи шаблон ви заповнюєте:PromptReferenceабоResourceTemplateReference.argument— це{"name": ..., "value": ...}: аргумент і те, що користувач уже встиг набрати.
Відповідь — у result.completion.values. Наберіть "p" — і сервер поверне ['poetry']. Серверний бік, а також те, як обробник використовує інші, уже заповнені аргументи, щоб звузити свої пропозиції, — на сторінці Автодоповнення.
Пагінація
Кожен метод list_* приймає іменований аргумент cursor=, а кожен результат містить next_cursor. Коли next_cursor дорівнює None, у вас є все.
from mcp import Client
from mcp.server import MCPServer
from mcp.types import Tool
mcp = MCPServer("Bookshop")
@mcp.tool()
def search_books(query: str) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r}."
@mcp.tool()
def reserve_book(title: str) -> str:
"""Put a book on hold."""
return f"Reserved {title!r}."
async def main() -> None:
async with Client(mcp) as client:
tools: list[Tool] = []
cursor: str | None = None
while True:
page = await client.list_tools(cursor=cursor)
tools.extend(page.tools)
if page.next_cursor is None:
break
cursor = page.next_cursor
print([tool.name for tool in tools])
Цей цикл коректний для будь-якого сервера. MCPServer повертає все однією сторінкою, тож next_cursor дорівнює None і цикл виконується один раз — саме тому більшість коду його ніколи не пише. Сервери, що справді розбивають результати на сторінки, і правила, яким підкоряються курсори, — на сторінці Пагінація.
У тестах
Client(mcp) без процесу й без порту — це вже тестова обв'язка для вашого сервера.
Саме для цього є один прапорець конструктора: Client(mcp, raise_exceptions=True). Він діє лише на з'єднаннях у пам'яті, а пояснює його й будує навколо нього весь підхід сторінка Тестування.
Підсумки
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.
Про що сервер може попросити клієнта і як на це відповідати — на сторінці Колбеки клієнта.