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

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

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

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

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

Автодоповнення (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). Усе, що інструмент може повернути, крім тексту, — на сторінці Зображення, аудіо та значки.