Автодополнение
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
Клиент, который строит пользовательский интерфейс поверх вашего сервера, хочет дополнять значения аргументов прямо по мере ввода: названия языков, имена репозиториев, пути к файлам.
Автодополнение (completions) — это способ, которым сервер выдаёт такие подсказки.
Что стоит дополнять
Автодополнение применяется ровно к двум вещам: к аргументам промпта и к параметрам шаблона ресурса. Поэтому начнём с сервера, в котором есть и то и другое:
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():
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. Он несёт аргументы, которые пользователь уже определил:
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). Всё, что инструмент может вернуть помимо текста, — на странице Изображения, аудио и значки.