Zum Inhalt

Vervollständigungen

Maschinelle Übersetzung

Diese Seite wurde automatisch aus der englischen Dokumentation übersetzt, und die englische Seite ist die maßgebliche Fassung. Wenn sich etwas falsch liest, erklärt Übersetzungen, wie du es melden kannst.

Ein Client, der eine UI auf deinem Server aufbaut, möchte Argumentwerte automatisch vervollständigen, während die Person tippt: Sprachnamen, Repository-Namen, Dateipfade.

Mit Vervollständigungen liefert dein Server diese Vorschläge.

Etwas zum Vervollständigen

Vervollständigungen gibt es für genau zwei Dinge: die Argumente eines Prompts und die Parameter eines Ressourcen-Templates. Beginne also mit einem Server, der von beidem eines hat:

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}"

Noch hat hier nichts mit Vervollständigungen zu tun.

  • review_code nimmt eine language entgegen. Niemand sollte raten müssen, welche Schreibweisen du akzeptierst.
  • github_repo nimmt einen owner und ein repo entgegen. Freitextfelder für beide ergeben ein schlechtes Formular.

Der Vervollständigungs-Handler

Füge eine mit @mcp.completion() dekorierte Funktion hinzu:

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
  • Es gibt einen Handler pro Server. Jeder Vervollständigungs-Request landet hier, und du verzweigst danach, was gerade vervollständigt wird.
  • Er muss mit async def definiert sein: Das SDK wartet per await auf ihn.
  • Er erhält drei Argumente:
  • ref: um welchen Prompt oder welches Ressourcen-Template es geht, als PromptReference oder ResourceTemplateReference. Mit isinstance unterscheidest du die beiden.
  • argument: argument.name ist das Argument, das vervollständigt wird, argument.value das, was die Person bisher getippt hat.
  • context: die bereits aufgelösten Argumente. Ignoriere es vorerst.
  • Du gibst eine Completion(values=[...]) zurück, oder None, wenn du nichts anzubieten hast.

Tip

argument.value ist das Präfix, das die Person getippt hat. Das SDK filtert nicht für dich: Was immer du in values packst, zeigt die UI an. Das startswith schreibst du selbst.

Ausprobieren

Steuere ihn mit dem In-Memory-Client aus Testen an. Rufe client.complete() mit ref=PromptReference(name="review_code") und argument={"name": "language", "value": "py"} auf:

result.completion.values  # ['python']
  • ref ist derselbe Referenztyp, den dein Handler erhält.
  • argument ist ein einfaches dict mit genau zwei Schlüsseln, name und value.

Schickst du ein leeres value, bekommst du die ganze Liste zurück. lang.startswith("") ist für jede Sprache wahr:

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

Fragst du nach code (einem Argument, das dein Handler nicht kennt), gibt er None zurück, was das SDK in eine leere Liste verwandelt:

result.completion.values  # []

None bedeutet „keine Vorschläge“, nie einen Fehler. Eine UI fällt auf ein einfaches Textfeld zurück.

Eine Capability, die du nie deklariert hast

Den Handler zu registrieren ist die Deklaration. Verbinde einen Client und sieh nach:

client.server_capabilities.completions  # CompletionsCapability()

Du hast completions nirgends aufgeführt. Das SDK hat den Handler gesehen und die Capability für dich deklariert. Jede optionale Capability funktioniert so: Der Handler ist die Deklaration. (Die drei Primitive sind nicht optional: MCPServer deklariert sie immer, mit oder ohne Handler.)

Check

Geh zurück zur ersten server.py (der ohne Handler) und frage trotzdem. Der Aufruf schlägt mit einem JSON-RPC-Fehler fehl:

Method not found

Und client.server_capabilities.completions ist None. Genau dafür ist die Capability da: Ein Client, der sich korrekt verhält, prüft sie und schickt den Request, den du nicht beantworten kannst, gar nicht erst.

Abhängige Argumente

github://repos/{owner}/{repo} hat zwei Parameter, und die sinnvollen Werte für repo hängen davon ab, welcher owner zuerst gewählt wurde.

Dafür ist context da. Es trägt die Argumente, die die Person bereits aufgelöst hat:

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
  • Der neue Zweig greift beim Parameter repo des Templates.
  • context.arguments ist ein dict[str, str] | None mit den bisher gewählten Werten (hier owner).
  • Noch kein owner bedeutet keine sinnvollen Vorschläge, also gibt der Handler None zurück.

Der Client schickt diese aufgelösten Werte mit context_arguments=. Diesmal ist ref eine ResourceTemplateReference(uri="github://repos/{owner}/{repo}"). Frage mit leerem value nach repo und übergib context_arguments={"owner": "modelcontextprotocol"}:

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

Lässt du context_arguments= weg, gibt derselbe Aufruf [] zurück. Der Handler kann nicht wissen, welche Repos er anbieten soll, solange er den Owner nicht kennt.

Info

Completion nimmt außerdem total= und has_more= entgegen. Setze sie, wenn values ein Ausschnitt einer längeren Liste ist, damit eine UI „und 200 weitere“ anzeigen kann. Die meisten Handler brauchen sie nie.

Zusammenfassung

  • Vervollständigungen sind Vorschläge für Prompt-Argumente und Parameter von Ressourcen-Templates. Sonst nichts.
  • @mcp.completion() registriert den einen Handler. Er ist async def (ref, argument, context) -> Completion | None.
  • Verzweige nach isinstance(ref, ...) und nach argument.name. Filtere selbst nach argument.value.
  • None wird zu einer leeren Liste. Es ist nie ein Fehler.
  • context.arguments enthält die bereits aufgelösten Werte; der Client liefert sie als context_arguments=.
  • Die Capability completions erscheint, sobald du den Handler registrierst. Ohne ihn endet der Request mit Method not found.

Vorschläge helfen, solange die Person einen Prompt oder ein Template noch ausfüllt; um ihr mitten in einem Tool-Aufruf eine Frage zu stellen, brauchst du Elicitation (Rückfrage bei der Person am Host). Alles, was ein Tool außer Text zurückgeben kann, steht in Bilder, Audio und Icons.