Complétions
Traduction automatique
Cette page a été traduite automatiquement à partir de la documentation en anglais, et la page en anglais fait foi. Si quelque chose vous semble incorrect, la page Traductions explique comment le signaler.
Un client qui construit une interface utilisateur au-dessus de votre serveur veut autocompléter les valeurs des arguments au fil de la saisie de l’utilisateur : noms de langages, noms de dépôts, chemins de fichiers.
Les complétions sont le moyen par lequel votre serveur fournit ces suggestions.
Quelque chose à compléter
Les complétions s’appliquent à exactement deux choses : les arguments d’un prompt et les paramètres d’un modèle de ressource. Commencez donc par un serveur qui en possède un de chaque :
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}"
Rien ici ne concerne encore les complétions.
review_codeprend unlanguage. Un utilisateur ne devrait pas avoir à deviner quelles orthographes vous acceptez.github_repoprend unowneret unrepo. Des champs de texte libre pour les deux font un mauvais formulaire.
Le gestionnaire de complétion
Ajoutez une seule fonction décorée avec @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
- Il y a un seul gestionnaire (handler) par serveur. Chaque requête de complétion arrive ici, et vous aiguillez selon ce qui est en cours de complétion.
- Il doit être
async def: le SDK l’attend avec await. - Il reçoit trois arguments :
ref: quel prompt ou modèle de ressource, sous la forme d’unePromptReferenceou d’uneResourceTemplateReference. C’estisinstancequi vous permet de les distinguer.argument:argument.nameest l’argument en cours de complétion,argument.valueest ce que l’utilisateur a saisi jusqu’ici.context: les arguments déjà résolus. Ignorez-le pour l’instant.- Vous renvoyez une
Completion(values=[...]), ouNonequand vous n’avez rien à proposer.
Tip
argument.value est le préfixe que l’utilisateur a saisi. Le SDK ne filtre pas pour vous : ce que
vous mettez dans values est ce que l’interface affiche. Le startswith, c’est à vous de l’écrire.
Essayer
Pilotez-le avec le Client en mémoire de Tests. Appelez
client.complete() avec ref=PromptReference(name="review_code") et
argument={"name": "language", "value": "py"} :
result.completion.values # ['python']
refest le même type de référence que celui que reçoit votre gestionnaire.argumentest un simple dict avec exactement deux clés,nameetvalue.
Envoyez une value vide et vous obtenez toute la liste en retour. lang.startswith("") est vrai pour chaque langage :
result.completion.values # ['go', 'javascript', 'python', 'rust', 'typescript']
Interrogez-le sur code (un argument que votre gestionnaire ne reconnaît pas) et il renvoie None, que le SDK transforme en liste vide :
result.completion.values # []
None signifie « aucune suggestion », jamais une erreur. Une interface se rabat sur un simple champ de texte.
Une capacité que vous n’avez jamais déclarée
Enregistrer le gestionnaire, c’est la déclarer. Connectez un client et regardez :
client.server_capabilities.completions # CompletionsCapability()
Vous n’avez listé completions nulle part. Le SDK a vu le gestionnaire et a déclaré la capacité pour vous. Toutes les capacités optionnelles fonctionnent ainsi : le gestionnaire est la déclaration. (Les trois primitives ne sont pas optionnelles : MCPServer les déclare toujours, gestionnaires ou non.)
Check
Revenez au premier server.py (celui sans gestionnaire) et interrogez-le quand même. L’appel échoue
avec une erreur JSON-RPC :
Method not found
Et client.server_capabilities.completions vaut None. C’est tout l’intérêt de la capacité : un
client bien conçu la vérifie et n’envoie jamais la requête à laquelle vous ne pouvez pas répondre.
Arguments dépendants
github://repos/{owner}/{repo} a deux paramètres, et les valeurs utiles pour repo dépendent du owner choisi en premier.
C’est à cela que sert context. Il transporte les arguments que l’utilisateur a déjà résolus :
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
- La nouvelle branche se déclenche pour le paramètre
repodu modèle. context.argumentsest undict[str, str] | Nonedes valeurs choisies jusqu’ici (ici,owner).- Pas encore de
ownersignifie pas de suggestion pertinente, donc le gestionnaire renvoieNone.
Le client envoie ces valeurs résolues avec context_arguments=. Cette fois, ref est une
ResourceTemplateReference(uri="github://repos/{owner}/{repo}"). Demandez repo avec une
value vide et passez context_arguments={"owner": "modelcontextprotocol"} :
result.completion.values # ['python-sdk', 'typescript-sdk', 'inspector']
Retirez context_arguments= et le même appel renvoie []. Le gestionnaire ne peut pas savoir quels dépôts proposer tant qu’il ne connaît pas le propriétaire.
Info
Completion accepte aussi total= et has_more=. Renseignez-les quand values est une tranche d’une liste
plus longue, pour qu’une interface puisse afficher « et 200 de plus ». La plupart des gestionnaires n’en ont jamais besoin.
Récapitulatif
- Les complétions sont des suggestions pour les arguments de prompt et les paramètres de modèle de ressource. Rien d’autre.
@mcp.completion()enregistre l’unique gestionnaire. Sa signature estasync def (ref, argument, context) -> Completion | None.- Aiguillez sur
isinstance(ref, ...)et surargument.name. Filtrez vous-même selonargument.value. Nonedevient une liste vide. Ce n’est jamais une erreur.context.argumentscontient les valeurs déjà résolues ; le client les fournit viacontext_arguments=.- La capacité
completionsapparaît dès que vous enregistrez le gestionnaire. Sans lui, la requête reçoitMethod not found.
Les suggestions aident pendant que l’utilisateur remplit encore un prompt ou un modèle ; pour lui poser une question au milieu d’un appel d’outil, c’est l’élicitation (elicitation) qu’il vous faut. Tout ce qu’un outil peut renvoyer en plus du texte se trouve dans Images, audio et icônes.