Перейти до змісту

Клієнт

Машинний переклад

Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.

Client — це те, через що програма на Python спілкується з MCP-сервером.

Це один об'єкт з одним життєвим циклом: створіть його, увійдіть в async with, викликайте методи. Кожна дія протоколу (перелічити інструменти, викликати один із них, прочитати ресурс, відрендерити промпт) — це його async-метод, що повертає типізований результат.

Перший клієнт

client.py
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, низькорівневий запасний вихід. Для жодної задачі на цій сторінці вона не знадобиться.

Перелік інструментів

client.py
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.

client.py
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 замість результату, а коли сервер повертає що саме — описано на сторінці Обробка помилок.

Ресурси

Дії з ресурсами йдуть парами: два способи перелічити, один спосіб прочитати.

client.py
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(...), описано на сторінці Підписки цього розділу.

Промпти

client.py
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.')

Хост передає ці повідомлення прямо моделі. Оце й уся можливість.

Автодоповнення

Сервер з обробником автодоповнення може доповнювати аргументи промптів і шаблонів ресурсів, поки користувач друкує.

client.py
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, у вас є все.

client.py
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.

Про що сервер може попросити клієнта і як на це відповідати — на сторінці Колбеки клієнта.