Перейти к содержанию

Объект Context

Машинный перевод

Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.

Аргументы инструмента приходят от модели. Всё остальное (запрос, который вы обслуживаете, сервер, внутри которого работаете, способ обратиться к клиенту) приходит из одного объекта: Context.

Его не нужно ни создавать, ни настраивать. Достаточно попросить.

Попросите его

Добавьте в любой инструмент параметр с аннотацией Context:

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bookshop")


@mcp.tool()
def search_books(query: str, ctx: Context) -> str:
    """Search the catalog by title or author."""
    return f"[request {ctx.request_id}] Found 3 books matching {query!r}."
  • SDK создаёт новый Context для каждого запроса и передаёт его в функцию.
  • Имя параметра не важно. ctx, context, c: SDK находит его по аннотации.
  • Ресурсы и промпты могут объявить такой параметр точно так же.
  • ctx.request_id — идентификатор запроса, который ваша функция обслуживает прямо сейчас.

Info

Если вы работали с FastAPI, этот приём вам знаком: объявляете параметр с типом самого фреймворка (там Request, здесь Context), и фреймворк его подставляет. Ничего регистрировать, ничего настраивать: весь механизм — это аннотация типа.

Невидим для модели

Вот что стоит усвоить. Так выглядит входная схема, которую tools/list сообщает для search_books:

{
  "type": "object",
  "properties": {
    "query": {"title": "Query", "type": "string"}
  },
  "required": ["query"],
  "title": "search_booksArguments"
}

Одно свойство. ctx — не аргумент: он никогда не появляется в схеме, модели о нём не сообщают, и ни один клиент не может его заполнить. Это договорённость между вами и SDK, невидимая в передаваемых данных.

Попробуйте сами

Запустите сервер через MCP Inspector:

uv run mcp dev server.py

В форме для search_books единственное поле — query. Вызовите инструмент со значением dune:

[request 3] Found 3 books matching 'dune'.

Число — номер того запроса, которым оказался этот вызов. Вызовите инструмент ещё раз, и оно изменится: каждый запрос получает свой Context.

Что он даёт

Внедряемый объект невелик. Помимо request_id:

  • await ctx.read_resource(uri): прочитать один из собственных ресурсов сервера изнутри инструмента. Об этом следующий раздел.
  • await ctx.report_progress(progress, total, message): передавать вызывающей стороне ход выполнения во время долгого вызова. Подробнее — на странице Прогресс.
  • await ctx.elicit(message, schema) и await ctx.elicit_url(...): приостановить инструмент и задать пользователю вопрос. Это элицитация (elicitation).
  • ctx.session: серверная сторона разговора с этим клиентом. Здесь живут уведомления, которые вы отправляете клиенту; последний раздел её использует.
  • ctx.headers: заголовки запроса, которые передал транспорт, или None на stdio. Прочитать нестандартный заголовок можно так: (ctx.headers or {}).get("x-..."). Заголовки — это данные от клиента: годятся для локали или флага возможности, но никогда для идентификации.
  • ctx.request_context: сырая запись о текущем запросе. Поле, к которому вы будете обращаться, — lifespan_context, объект, который вернул ваш код запуска (см. Жизненный цикл (lifespan)).

Логирования в этом списке нет намеренно. Сервер пишет логи через модуль Python logging, как любая другая программа на Python. Почему так — на короткой странице Логирование.

Tip

Внедрение происходит только для функции, которую вы зарегистрировали. Вспомогательная функция, которую вызывает ваш инструмент, не получает собственный Context; передавайте ей ctx как обычный аргумент. Никакого фонового «текущего контекста», который можно достать откуда-то ещё, не существует.

Чтение собственных ресурсов

Ресурсы сервера предназначены не только для клиентов. Инструмент тоже может их читать:

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bookshop")


@mcp.resource("catalog://genres")
def genres() -> str:
    """The genres the catalog is organised into."""
    return "fiction, non-fiction, poetry"


@mcp.tool()
async def describe_catalog(ctx: Context) -> str:
    """Describe how the catalog is organised."""
    [contents] = await ctx.read_resource("catalog://genres")
    return f"The catalog is organised into: {contents.content}"

ctx.read_resource разрешает URI через тот же реестр, что обслуживает resources/read, поэтому инструмент получает то же, что получил бы клиент: итерируемый набор ReadResourceContents, по одному на блок содержимого. Для этого URI он один:

contents.content    # 'fiction, non-fiction, poetry'
contents.mime_type  # 'text/plain'
  • content — ровно то, что вернула genres(). Один источник истины: клиент просматривает ресурс, ваши инструменты его потребляют, никто не копирует строку.
  • Единственный параметр describe_catalog — это Context, поэтому в его входной схеме вообще нет свойств. Модель вызывает его с {}.

Сообщите клиенту, что список изменился

То, что предлагает сервер, не зафиксировано на момент импорта. Зарегистрируйте инструмент во время выполнения, а затем сообщите об этом клиенту:

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bookshop")


def recommend_book(genre: str) -> str:
    """Recommend a book in the given genre."""
    return f"In {genre}, try 'Dune'."


@mcp.tool()
async def enable_recommendations(ctx: Context) -> str:
    """Switch on the recommendation tool."""
    mcp.add_tool(recommend_book)
    await ctx.session.send_tool_list_changed()
    return "Recommendations are now available."
  • mcp.add_tool(recommend_book) регистрирует обычную функцию как инструмент: имя, описание и схема выводятся точно так же, как это сделал бы @mcp.tool().
  • await ctx.session.send_tool_list_changed() отправляет notifications/tools/list_changed. Клиент, получивший его, снова вызывает tools/list и видит recommend_book.

Родственные методы — send_resource_list_changed(), send_prompt_list_changed() и send_resource_updated(uri) для изменения одного конкретного ресурса.

На подключении 2026-07-28 клиенты получают уведомления об изменениях только в потоке subscriptions/listen, который они открыли, поэтому перечисленные выше методы send_* до этих потоков не доходят. Методы публикации в Context доставляют уведомление сразу во все подписанные потоки: await ctx.notify_tools_changed(), await ctx.notify_prompts_changed(), await ctx.notify_resources_changed() и await ctx.notify_resource_updated(uri). Подробнее, включая масштабирование на несколько реплик, — на странице Подписки.

Check

Пока никто не запустил enable_recommendations, обещанного инструмента не существует. Вызовите его всё равно, и результатом будет ошибка, которую модель может прочитать:

Unknown tool: recommend_book

Запустите enable_recommendations, и тот же самый вызов проходит успешно. Список инструментов действительно динамический: tools/list отражает то, что зарегистрировано прямо сейчас.

Итоги

  • Аннотируйте параметр типом Context (в инструменте, ресурсе или промпте), и SDK его внедрит. Имя выбираете вы.
  • Для модели он невидим: входная схема всегда содержит только ваши настоящие аргументы.
  • ctx.request_id идентифицирует запрос; ctx.request_context.lifespan_context — то, что вернул ваш код запуска.
  • await ctx.read_resource(uri) позволяет инструменту читать собственные ресурсы сервера.
  • ctx.session — канал обратно к клиенту: send_tool_list_changed() и родственные методы велят ему заново запросить изменённый список.
  • Отчёты о ходе выполнения и элицитация тоже начинаются с Context; у каждой темы своя страница.

Параметры, которых модель никогда не видит и которые заполняют ваши собственные функции, — это Зависимости.