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

Автодополнение

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

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

Клиент, который строит пользовательский интерфейс поверх вашего сервера, хочет дополнять значения аргументов прямо по мере ввода: названия языков, имена репозиториев, пути к файлам.

Автодополнение (completions) — это способ, которым сервер выдаёт такие подсказки.

Что стоит дополнять

Автодополнение применяется ровно к двум вещам: к аргументам промпта и к параметрам шаблона ресурса. Поэтому начнём с сервера, в котором есть и то и другое:

server.py
from mcp.server import MCPServer

mcp = MCPServer("GitHub Explorer")


@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
    """A GitHub repository."""
    return f"Repository: {owner}/{repo}"


@mcp.prompt()
def review_code(language: str, code: str) -> str:
    """Review a snippet of code."""
    return f"Review this {language} code:\n{code}"

Пока здесь нет ничего про автодополнение.

  • review_code принимает language. Пользователь не должен угадывать, какие варианты написания вы принимаете.
  • github_repo принимает owner и repo. Два поля для свободного ввода — плохая форма.

Обработчик автодополнения

Добавьте одну функцию с декоратором @mcp.completion():

server.py
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference

mcp = MCPServer("GitHub Explorer")

LANGUAGES = ["go", "javascript", "python", "rust", "typescript"]


@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
    """A GitHub repository."""
    return f"Repository: {owner}/{repo}"


@mcp.prompt()
def review_code(language: str, code: str) -> str:
    """Review a snippet of code."""
    return f"Review this {language} code:\n{code}"


@mcp.completion()
async def handle_completion(
    ref: PromptReference | ResourceTemplateReference,
    argument: CompletionArgument,
    context: CompletionContext | None,
) -> Completion | None:
    if isinstance(ref, PromptReference) and argument.name == "language":
        return Completion(values=[lang for lang in LANGUAGES if lang.startswith(argument.value)])
    return None
  • Обработчик на сервер один. Все запросы автодополнения попадают сюда, а дальше вы ветвитесь в зависимости от того, что именно дополняется.
  • Он должен быть async def: SDK вызывает его через await.
  • Он получает три аргумента:
  • ref: какой промпт или шаблон ресурса — в виде PromptReference или ResourceTemplateReference. Различить их можно через isinstance.
  • argument: argument.name — дополняемый аргумент, argument.value — то, что пользователь уже успел набрать.
  • context: уже определённые аргументы. Пока не обращайте на него внимания.
  • Верните Completion(values=[...]) или None, если предложить нечего.

Tip

argument.value — это префикс, который набрал пользователь. SDK не фильтрует за вас: что положите в values, то интерфейс и покажет. startswith пишете вы сами.

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

Проверьте его с помощью Client в памяти со страницы Тестирование. Вызовите client.complete() с ref=PromptReference(name="review_code") и argument={"name": "language", "value": "py"}:

result.completion.values  # ['python']
  • ref — тот же ссылочный тип, что получает обработчик.
  • argument — обычный словарь ровно с двумя ключами: name и value.

Отправьте пустое value — и вернётся весь список. lang.startswith("") истинно для любого языка:

result.completion.values  # ['go', 'javascript', 'python', 'rust', 'typescript']

Спросите про code (аргумент, который обработчик не знает) — он вернёт None, а SDK превратит его в пустой список:

result.completion.values  # []

None означает «подсказок нет» и никогда не ошибку. Интерфейс откатывается к обычному текстовому полю.

Возможность, которую вы не объявляли

Регистрация обработчика и есть объявление. Подключите клиент и посмотрите:

client.server_capabilities.completions  # CompletionsCapability()

Вы нигде не указывали completions. SDK увидел обработчик и объявил возможность за вас. Так работает каждая необязательная возможность: обработчик — это и есть объявление. (Три примитива к необязательным не относятся: MCPServer объявляет их всегда, есть обработчики или нет.)

Check

Вернитесь к первому server.py (тому, где обработчика нет) и всё равно спросите. Вызов завершится ошибкой JSON-RPC:

Method not found

А client.server_capabilities.completions равно None. В этом и смысл возможности: корректный клиент проверяет её и никогда не отправляет запрос, на который вы не можете ответить.

Зависимые аргументы

У github://repos/{owner}/{repo} два параметра, и полезные значения для repo зависят от того, какой owner выбрали первым.

Для этого и нужен context. Он несёт аргументы, которые пользователь уже определил:

server.py
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference

mcp = MCPServer("GitHub Explorer")

LANGUAGES = ["go", "javascript", "python", "rust", "typescript"]

REPOS_BY_OWNER = {
    "modelcontextprotocol": ["python-sdk", "typescript-sdk", "inspector"],
    "pydantic": ["pydantic", "pydantic-ai", "logfire"],
}


@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
    """A GitHub repository."""
    return f"Repository: {owner}/{repo}"


@mcp.prompt()
def review_code(language: str, code: str) -> str:
    """Review a snippet of code."""
    return f"Review this {language} code:\n{code}"


@mcp.completion()
async def handle_completion(
    ref: PromptReference | ResourceTemplateReference,
    argument: CompletionArgument,
    context: CompletionContext | None,
) -> Completion | None:
    if isinstance(ref, PromptReference) and argument.name == "language":
        return Completion(values=[lang for lang in LANGUAGES if lang.startswith(argument.value)])
    if isinstance(ref, ResourceTemplateReference) and argument.name == "repo":
        if context is None or context.arguments is None:
            return None
        repos = REPOS_BY_OWNER.get(context.arguments.get("owner", ""), [])
        return Completion(values=[repo for repo in repos if repo.startswith(argument.value)])
    return None
  • Новая ветка срабатывает для параметра repo шаблона.
  • context.arguments — это dict[str, str] | None со значениями, выбранными к этому моменту (здесь owner).
  • Нет owner — нет и осмысленных подсказок, поэтому обработчик возвращает None.

Клиент отправляет эти определённые значения через context_arguments=. На этот раз ref — это ResourceTemplateReference(uri="github://repos/{owner}/{repo}"). Запросите repo с пустым value и передайте context_arguments={"owner": "modelcontextprotocol"}:

result.completion.values  # ['python-sdk', 'typescript-sdk', 'inspector']

Уберите context_arguments= — и тот же вызов вернёт []. Обработчик не может знать, какие репозитории предложить, пока не знает владельца.

Info

Completion принимает также total= и has_more=. Задавайте их, когда values — лишь часть более длинного списка, чтобы интерфейс мог показать «и ещё 200». Большинству обработчиков они не нужны.

Итоги

  • Автодополнение — это подсказки для аргументов промптов и параметров шаблонов ресурсов. И ничего больше.
  • @mcp.completion() регистрирует единственный обработчик. Это async def (ref, argument, context) -> Completion | None.
  • Ветвитесь по isinstance(ref, ...) и по argument.name. Фильтруйте по argument.value сами.
  • None превращается в пустой список. Это никогда не ошибка.
  • В context.arguments лежат уже определённые значения; клиент передаёт их как context_arguments=.
  • Возможность completions появляется, как только вы регистрируете обработчик. Без него ответ на запрос — Method not found.

Подсказки помогают, пока пользователь ещё заполняет промпт или шаблон; чтобы задать ему вопрос посреди вызова инструмента, нужна элицитация (elicitation). Всё, что инструмент может вернуть помимо текста, — на странице Изображения, аудио и значки.