Колбеки клієнта
Машинний переклад
Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.
Майже кожен запит у MCP іде в один бік: від клієнта до сервера.
Сервер теж може дещо попросити в клієнта: поставити запитання користувачеві, скористатися моделлю користувача для семплювання (sampling), отримати список папок його робочого простору. На ці запити відповідають колбеки, які передаються в Client(...).
Сервер, який запитує
Ось сервер, інструмент якого не може завершитися самотужки:
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Library")
class CardHolder(BaseModel):
name: str
@mcp.tool()
async def issue_card(ctx: Context) -> str:
"""Issue a new library card."""
answer = await ctx.elicit("What name should go on the card?", schema=CardHolder)
if answer.action == "accept":
return f"Card issued to {answer.data.name}."
return "No card issued."
ctx.elicit(...)надсилає запитelicitation/createклієнтові й чекає.- Інструмент не повертає результат, доки хтось (людина у формі або ваш код) не надасть
name.
Це серверна половина, і вона належить сторінці Еліцитація. Ця сторінка — про інший кінець з'єднання.
Колбек еліцитації
from mcp import Client
from mcp.client import ClientRequestContext
from mcp.types import ElicitRequestParams, ElicitResult
async def handle_elicitation(
context: ClientRequestContext,
params: ElicitRequestParams,
) -> ElicitResult:
return ElicitResult(action="accept", content={"name": "Ada Lovelace"})
async def main() -> None:
async with Client(
"http://127.0.0.1:8000/mcp",
mode="legacy",
elicitation_callback=handle_elicitation,
) as client:
result = await client.call_tool("issue_card")
print(result.content)
- Колбек еліцитації (elicitation) — це
async (context, params) -> ElicitResult. params.message— це запитання.params.requested_schema— JSON Schema відповіді, яку хоче отримати сервер. Справжній клієнт будує з неї форму; цей заповнює її автоматично.- Повертається
ElicitResult(action="accept", content={...}), абоaction="decline", абоaction="cancel". Єдиний інший варіант —ErrorData(...): він відхиляє запит, і весь виклик завершується помилкою. context— цеClientRequestContext: активнаsession,request_idсервера та будь-якіmeta, які він додав.
Tip
params — об'єднання двох режимів еліцитації. Тут params.mode дорівнює "form"; запит "url"
замість схеми несе params.url. Обидва обробляє один колбек; розгалужуйтеся за params.mode.
Повний шаблон показано на сторінці Еліцитація.
Спробуйте самі
Викличте issue_card і простежте за обома кінцями.
Колбек отримує запитання сервера, уже розібране:
params.mode # 'form'
params.message # 'What name should go on the card?'
params.requested_schema # {'properties': {'name': {'title': 'Name', 'type': 'string'}},
# 'required': ['name'], 'title': 'CardHolder', 'type': 'object'}
Він відповідає, ctx.elicit(...) усередині інструмента відновлює роботу, й інструмент завершується:
result.content # [TextContent(type='text', text='Card issued to Ada Lovelace.')]
Один tools/call від вас, один elicitation/create у відповідь від сервера, на який відповіла ваша функція, — і все це всередині одного виклику інструмента.
Info
mode="legacy" у виклику Client(...) стоїть не просто так. За замовчуванням Client(...) узгоджує сучасний
шлях протоколу, а на ньому немає зворотного каналу (back-channel) для запитів від сервера до клієнта: ctx.elicit
завершується помилкою ще до того, як запуститься колбек. Вирішує це не транспорт, а узгоджений
протокол — однаково і в пам'яті, і за URL. Фіксуйте mode="legacy" щоразу, коли клієнт має
відповідати на такий запит; так робить кожен тест за цією сторінкою. Докладніше — на сторінці Версії протоколу.
У сесії 2026-07-28 колбек не зникає, він просто отримує дані інакше: коли інструмент повертає
InputRequiredResult з ElicitRequest усередині, Client передає цей запис тому самому
elicitation_callback і повторює виклик за вас. Цей сценарій описано на сторінці Багатораундові запити (multi-round-trip).
Колбек — це можливість
Ви ніде не повідомляли серверу, що ваш клієнт уміє відповідати на запити еліцитації. Це зробив SDK.
Під'єднуючись, клієнт оголошує свої capabilities — дзеркальне відображення серверних. Цей об'єкт ви не пишете. Реєстрація колбека і є оголошенням.
| що передається | що оголошує клієнт |
|---|---|
elicitation_callback= |
"elicitation": {"form": {}, "url": {}} |
sampling_callback= |
"sampling": {} |
list_roots_callback= |
"roots": {"listChanged": true} |
| жодного з них | {} |
Єдине уточнення — підможливості семплювання: передавайте sampling_capabilities=SamplingCapability(tools=SamplingToolsCapability()) разом із sampling_callback, якщо ваш семплер обробляє параметри tools / tool_choice. Сервери мають побачити оголошену sampling.tools, перш ніж надсилати їх.
logging_callback і message_handler у таблиці немає. Вони обробляють сповіщення, а сповіщенням можливість не потрібна.
Сервер зчитує оголошення методом ctx.session.check_client_capability(...). Додайте інструмент, який це робить:
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
from mcp.types import ClientCapabilities, ElicitationCapability, RootsCapability, SamplingCapability
mcp = MCPServer("Library")
class CardHolder(BaseModel):
name: str
@mcp.tool()
async def issue_card(ctx: Context) -> str:
"""Issue a new library card."""
answer = await ctx.elicit("What name should go on the card?", schema=CardHolder)
if answer.action == "accept":
return f"Card issued to {answer.data.name}."
return "No card issued."
@mcp.tool()
def client_features(ctx: Context) -> list[str]:
"""Which optional features the connected client declared."""
declared = {
"elicitation": ClientCapabilities(elicitation=ElicitationCapability()),
"sampling": ClientCapabilities(sampling=SamplingCapability()),
"roots": ClientCapabilities(roots=RootsCapability()),
}
return [name for name, capability in declared.items() if ctx.session.check_client_capability(capability)]
Під'єднайтеся лише з elicitation_callback і викличте його:
result.structured_content # {'result': ['elicitation']}
Передайте всі три колбеки — отримаєте ['elicitation', 'sampling', 'roots']. Не передайте жодного — отримаєте [].
Check
Тепер зробіть неправильно: під'єднайтеся без elicitation_callback і все одно викличте issue_card.
Запит сервера elicitation/create все одно доходить до клієнта, і SDK відповідає на нього за
вас — помилкою, бо ви ніде не сказали, що можете його обробити. Ця помилка топить весь виклик.
call_tool не повертає результат із is_error; він викидає виняток:
MCPError: Elicitation not supported
Це помилка протоколу (-32600, invalid request), а не помилка інструмента: моделі тут нічого
прочитати й повторити. Саме тому client_features варто мати: чемний сервер
перевіряє, перш ніж питати.
Застаріла пара
sampling_callback відповідає на sampling/createMessage: сервер просить вашу модель щось доповнити. list_roots_callback відповідає на roots/list: сервер питає, у яких каталогах йому можна працювати.
Обидва працюють. Обидва дотримуються правила вище. І обидва обслуговують RPC, які специфікація 2026-07-28 вилучає: сучасний сервер не звертається до клієнта посеред запиту, а повертає запит вам як частину результату інструмента (Багатораундові запити). Самі колбеки нікуди не зникають. Коли InputRequiredResult несе CreateMessageRequest або ListRootsRequest, автоматичний цикл Client передає його тому самому sampling_callback чи list_roots_callback, який ви зареєстрували тут. Повний список — на сторінці Застарілі можливості.
Колбеки досі потрібні, щоб спілкуватися із серверами, які ще не перейшли. Сигнатури:
from pydantic import FileUrl
from mcp.client import ClientRequestContext
from mcp.types import CreateMessageRequestParams, CreateMessageResult, ListRootsResult, Root, TextContent
async def handle_sampling(
context: ClientRequestContext,
params: CreateMessageRequestParams,
) -> CreateMessageResult:
return CreateMessageResult(
role="assistant",
content=TextContent(type="text", text="The answer is 42."),
model="my-llm",
)
async def handle_list_roots(context: ClientRequestContext) -> ListRootsResult:
return ListRootsResult(roots=[Root(uri=FileUrl("file:///home/ada/notebooks"), name="notebooks")])
- Колбек семплювання отримує повний
CreateMessageRequestParams(messages,model_preferences,max_tokens) і повертаєCreateMessageResult. Модель запускаєте ви, як завгодно; SDK лише переносить запит. - Колбек кореневих каталогів (roots) не приймає жодних параметрів і повертає
ListRootsResult. - Кожен із них натомість може повернути
ErrorData(...), щоб відмовити.
Передавайте їх у Client(...) так само, як elicitation_callback.
Колбеки сповіщень
Ще два. Жоден нічого не оголошує.
logging_callback отримує notifications/message, які надсилає сервер, у вигляді LoggingMessageNotificationParams (level, logger, data). Протокольне логування саме оголошене застарілим у специфікації 2026-07-28 (що робити натомість — на сторінці Логування), тож цей колбек існує для серверів, які досі його надсилають. На з'єднанні покоління 2026 самого колбека недостатньо, бо сервери 2026 надсилають лог-повідомлення лише у відповідь на запити, які на це погодилися: передайте log_level="info" (або інший рівень) у Client(...), щоб проставляти цю згоду в кожному запиті й отримувати повідомлення цього рівня та вище. Сервери до 2026 ігнорують її й зберігають свою поведінку logging/setLevel.
message_handler — універсальний приймач: до нього доходить кожне сповіщення сервера, яке сесія передає назовні (на додачу до свого спеціального колбека), а на транспорті на основі потоку — ще й кожен Exception транспортного рівня. Два ніколи не доходять: notifications/cancelled SDK застосовує сам, а не передає назовні, а підтвердження підписки для активного потоку listen() споживає сам цей потік. Анотуйте параметр типом IncomingMessage (ServerNotification | Exception, експортується з mcp.client). Єдиний шаблон, який варто знати, — if isinstance(message, Exception): raise message, щоб розірване з'єднання падало гучно, а не зникало безслідно.
Підсумки
- Сервер може надсилати запити клієнтові. Відповідають на них колбеки, передані в
Client(...). - Колбек еліцитації — актуальний:
async (context, params) -> ElicitResult, одна функція і для режиму форми, і для режиму URL. - Реєстрація колбека — це оголошення можливості. Без нього SDK відхиляє запит сервера від вашого імені, і весь виклик завершується з
MCPError. - Сервер дізнається про це ще до запиту за допомогою
ctx.session.check_client_capability(...). sampling_callbackіlist_roots_callbackпрацюють так само, але обслуговують застарілі можливості; сучасні сервери натомість використовують багатораундові запити.logging_callbackіmessage_handlerотримують сповіщення. Вони нічого не оголошують.
Перший аргумент Client(...) — об'єкт транспорту. Усі його різновиди описано на сторінці Транспорти клієнта.