--- translation: sections: [5315262fe26b33e1, 9d8e98840f1b78f0, 0284b215e85366c4, 8534d8dbb4053a70, 2966fac6fe697007] tool: 1 --- # Ход выполнения {#progress} Инструмент, который работает тридцать секунд и все тридцать секунд молчит, выглядит сломанным. **Уведомления о ходе выполнения** решают эту проблему. Инструмент сообщает, насколько он продвинулся, а клиент решает, что из этого нарисовать: полосу, спиннер, строку в логе. ## Отчёт о ходе из инструмента {#report-it-from-the-tool} Примите параметр **`Context`** и вызовите `report_progress`: ```python title="server.py" hl_lines="8 11" --8<-- "docs_src/progress/tutorial001.py" ``` Три аргумента, и их смысл определяете вы: * `progress`: насколько вы продвинулись. Спецификация требует, чтобы значение **росло** с каждым отчётом: никогда не повторяйте значение и не уменьшайте его. * `total`: сколько всего, если это известно. Необязательный. * `message`: одна понятная человеку строка об *этом* шаге. Необязательный. `ctx` внедряется по аннотации типов, и модель его не видит: во входной схеме `import_catalog` единственное свойство — `urls`. Этому объекту целиком посвящена страница **[Объект Context](context.md)**; ход выполнения — одна из возможностей, которые он даёт. ## Приём на стороне клиента {#listen-for-it-from-the-client} Клиент подписывается **на каждый вызов отдельно**, передавая `progress_callback=` в `call_tool`: ```python title="client.py" hl_lines="7 16" import anyio from mcp import Client from server import mcp async def show(progress: float, total: float | None, message: str | None) -> None: print(f"{message} ({progress}/{total})") async def main() -> None: async with Client(mcp) as client: result = await client.call_tool( "import_catalog", {"urls": ["https://example.com/a.json", "https://example.com/b.json"]}, progress_callback=show, ) print(result.structured_content) anyio.run(main) ``` Колбэк — это `async`-функция, принимающая ровно то, что сообщил сервер: `progress`, `total`, `message`. !!! info `Client(mcp)` подключается напрямую к объекту сервера, в памяти, — это тот же клиент, на котором построена страница **[Тестирование](../get-started/testing.md)**. Параметр `progress_callback` один и тот же, какой бы транспорт ни использовал `Client`; а вот *временны́е характеристики*, которые вы сейчас увидите, относятся к подключению в памяти. Оно выполняет колбэк прямо на месте, поэтому каждый отчёт приходит до того, как `call_tool` вернёт управление. По настоящему транспорту уведомления соревнуются с результатом, и медленный колбэк может всё ещё работать после того, как `call_tool` уже вернул управление. ### Попробуйте сами {#try-it} Положите `client.py` рядом с `server.py` и запустите: ```console python client.py ``` ```text Imported https://example.com/a.json (1/2) Imported https://example.com/b.json (2/2) {'result': 'Imported 2 records.'} ``` Каждый `await ctx.report_progress(...)` на сервере превратился в один вызов `show` на клиенте, в том же порядке, и обе строки напечатались **до** того, как `call_tool` вернул управление. Ход выполнения не упаковывается в результат: он передаётся потоком, пока инструмент ещё работает. !!! warning `progress_callback` относится к **вызову**, а не к `Client`. Аргумента конструктора для него нет, потому что разным вызовам нужны разные колбэки: один управляет полосой загрузки, следующий — строкой в логе. !!! check Теперь удалите `progress_callback=show` и запустите снова: ```text {'result': 'Imported 2 records.'} ``` Ни ошибки, ни предупреждения, тот же результат. `report_progress` **ничего не делает, если вызывающая сторона не запросила ход выполнения**, поэтому сообщайте о нём безусловно и никогда не гадайте, слушает ли кто-нибудь. ## Когда общий объём неизвестен {#when-you-dont-know-the-total} `total` нужен, когда известен знаменатель. Часто это не так: вы вычитываете ленту, идёте по курсору, скачиваете что-то без заголовка длины. Просто не указывайте его: ```python title="server.py" hl_lines="20" --8<-- "docs_src/progress/tutorial002.py" ``` Колбэк получает `total=None`. Клиент по-прежнему может показывать *активность* («пока импортировано 3...»), но не процент. Не выдумывайте общий объём ради полосы покрасивее. !!! tip `progress` не обязан считать что-то конкретное. Байты, строки, страницы: выберите единицу, понятную пользователю, и обещайте `total`, только если сможете это обещание сдержать. ## Итоги {#recap} * `await ctx.report_progress(progress, total=None, message=None)` из любого инструмента, принимающего `Context`. * Клиент передаёт `progress_callback=` в `call_tool`: на каждый вызов, никогда не в `Client`. * Колбэк имеет вид `async (progress, total, message) -> None` и срабатывает, пока инструмент ещё работает. * Нет колбэка у вызова — `report_progress` ничего не делает. Сообщайте безусловно. * Опускайте `total`, когда он неизвестен; колбэк получит `None`. Ход выполнения — это то, что работающий инструмент показывает *пользователю*. Строки, которые он пишет в лог для *вас*, человека, обслуживающего сервер, — это другой канал: **[Логирование](logging.md)**.