Completions
Tradução automática
Esta página foi traduzida automaticamente a partir da documentação em inglês, e a página em inglês é a versão de referência. Se algo parecer errado, Traduções explica como avisar.
Um cliente que monta uma UI em cima do seu servidor quer autocompletar os valores dos argumentos enquanto o usuário digita: nomes de linguagens, nomes de repositórios, caminhos de arquivo.
É com as completions que o seu servidor fornece essas sugestões.
Algo que valha a pena completar
As completions se aplicam a exatamente duas coisas: os argumentos de um prompt e os parâmetros de um template de recurso. Então comece com um servidor que tenha um de cada:
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}"
Ainda não há nada de completions aqui.
review_coderecebe umlanguage. O usuário não deveria precisar adivinhar quais grafias você aceita.github_reporecebe umownere umrepo. Campos de texto livre para os dois resultam em um formulário ruim.
O handler de completion
Adicione uma função decorada com @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
- Existe um handler por servidor. Toda requisição de completion chega aqui, e você ramifica de acordo com o que está sendo completado.
- Ele precisa ser
async def: o SDK faz o await dele. - Ele recebe três argumentos:
ref: qual prompt ou template de recurso, como umPromptReferenceou umResourceTemplateReference. É comisinstanceque você distingue um do outro.argument:argument.nameé o argumento que está sendo completado,argument.valueé o que o usuário digitou até agora.context: os argumentos já resolvidos. Ignore-o por enquanto.- Você retorna um
Completion(values=[...]), ouNonequando não tem nada a oferecer.
Tip
argument.value é o prefixo que o usuário digitou. O SDK não filtra para você: o que
você colocar em values é o que a UI mostra. O startswith é você quem escreve.
Experimente
Use o Client em memória de Testes para exercitá-lo. Chame
client.complete() com ref=PromptReference(name="review_code") e
argument={"name": "language", "value": "py"}:
result.completion.values # ['python']
refé o mesmo tipo de referência que o seu handler recebe.argumenté um dict simples com exatamente duas chaves,nameevalue.
Envie um value vazio e você recebe a lista inteira de volta. lang.startswith("") é verdadeiro para toda linguagem:
result.completion.values # ['go', 'javascript', 'python', 'rust', 'typescript']
Pergunte sobre code (um argumento que o seu handler não reconhece) e ele retorna None, que o SDK transforma em uma lista vazia:
result.completion.values # []
None significa "sem sugestões", nunca um erro. A UI recorre a uma caixa de texto simples.
Uma capacidade que você nunca declarou
Registrar o handler é a declaração. Conecte um cliente e veja:
client.server_capabilities.completions # CompletionsCapability()
Você não listou completions em lugar nenhum. O SDK viu o handler e declarou a capacidade por você. Toda capacidade opcional funciona assim: o handler é a declaração. (As três primitivas não são opcionais: o MCPServer sempre as declara, com ou sem handlers.)
Check
Volte ao primeiro server.py (aquele sem handler) e pergunte mesmo assim. A chamada falha
com um erro JSON-RPC:
Method not found
E client.server_capabilities.completions é None. É para isso que a capacidade existe: um
cliente bem-comportado a confere e nunca envia uma requisição que você não tem como responder.
Argumentos dependentes
github://repos/{owner}/{repo} tem dois parâmetros, e os valores úteis para repo dependem de qual owner foi escolhido antes.
É para isso que serve o context. Ele carrega os argumentos que o usuário já resolveu:
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
- O novo ramo é acionado para o parâmetro
repodo template. context.argumentsé umdict[str, str] | Nonecom os valores escolhidos até agora (aqui,owner).- Sem
ownerainda, não há sugestões que façam sentido, então o handler retornaNone.
O cliente envia esses valores resolvidos com context_arguments=. Desta vez, ref é um
ResourceTemplateReference(uri="github://repos/{owner}/{repo}"). Peça repo com um
value vazio e passe context_arguments={"owner": "modelcontextprotocol"}:
result.completion.values # ['python-sdk', 'typescript-sdk', 'inspector']
Tire o context_arguments= e a mesma chamada retorna []. O handler não tem como saber quais repositórios oferecer antes de saber quem é o owner.
Info
Completion também aceita total= e has_more=. Defina-os quando values for uma fatia de uma
lista maior, para que a UI possa mostrar "e mais 200". A maioria dos handlers nunca precisa deles.
Recapitulando
- Completions são sugestões para argumentos de prompt e parâmetros de template de recurso. Nada mais.
@mcp.completion()registra o único handler. Ele éasync def (ref, argument, context) -> Completion | None.- Ramifique com base em
isinstance(ref, ...)e emargument.name. Filtre porargument.valuevocê mesmo. Nonevira uma lista vazia. Nunca é um erro.context.argumentsguarda os valores já resolvidos; o cliente os fornece comocontext_arguments=.- A capacidade
completionsaparece no momento em que você registra o handler. Sem ele, a requisição dáMethod not found.
As sugestões ajudam enquanto o usuário ainda está preenchendo um prompt ou template; para fazer uma pergunta a ele no meio de uma chamada de ferramenta, o que você quer é a Elicitação (elicitation). Tudo o que uma ferramenta pode retornar além de texto está em Imagens, áudio e ícones.