docs/tutorial/completions.md
A client building a UI on top of your server wants to autocomplete argument values as the user types: language names, repository names, file paths.
Completions are how your server supplies those suggestions.
Completions apply to exactly two things: the arguments of a prompt and the parameters of a resource template. So start with a server that has one of each:
--8<-- "docs_src/completions/tutorial001.py"
Nothing here is about completions yet.
review_code takes a language. A user shouldn't have to guess which spellings you accept.github_repo takes an owner and a repo. Free-text boxes for both make a bad form.Add one function decorated with @mcp.completion():
--8<-- "docs_src/completions/tutorial002.py"
async def: the SDK awaits it.ref: which prompt or resource template, as a PromptReference or a ResourceTemplateReference. isinstance is how you tell them apart.argument: argument.name is the argument being completed, argument.value is what the user has typed so far.context: the arguments already resolved. Ignore it for now.Completion(values=[...]), or None when you have nothing to offer.!!! tip
argument.value is the prefix the user has typed. The SDK does not filter for you: whatever
you put in values is what the UI shows. The startswith is yours to write.
Drive it with the in-memory Client, the same one you use in Testing. Call
client.complete() with ref=PromptReference(name="review_code") and
argument={"name": "language", "value": "py"}:
result.completion.values # ['python']
ref is the same reference type your handler receives.argument is a plain dict with exactly two keys, name and value.Send an empty value and you get the whole list back. lang.startswith("") is true for every language:
result.completion.values # ['go', 'javascript', 'python', 'rust', 'typescript']
Ask about code (an argument your handler doesn't recognise) and it returns None, which the SDK turns into an empty list:
result.completion.values # []
None means "no suggestions", never an error. A UI falls back to a plain text box.
Registering the handler is the declaration. Connect a client and look:
client.server_capabilities.completions # CompletionsCapability()
You didn't list completions anywhere. The SDK saw the handler and advertised it during the handshake. Every optional capability works this way: the handler is the declaration. (The three primitives are not optional: MCPServer always declares those, handlers or not.)
!!! check
Go back to the first server.py (the one with no handler) and ask it anyway. The call fails
with a JSON-RPC error:
```text
Method not found
```
And `client.server_capabilities.completions` is `None`. That's the point of the capability: a
well-behaved client checks it and never sends the request you can't answer.
github://repos/{owner}/{repo} has two parameters, and the useful values for repo depend on which owner was picked first.
That's what context is for. It carries the arguments the user has already resolved:
--8<-- "docs_src/completions/tutorial003.py"
repo parameter.context.arguments is a dict[str, str] | None of the values picked so far (here, owner).owner yet means no sensible suggestions, so the handler returns None.The client sends those resolved values with context_arguments=. This time ref is a
ResourceTemplateReference(uri="github://repos/{owner}/{repo}"). Ask for repo with an
empty value and pass context_arguments={"owner": "modelcontextprotocol"}:
result.completion.values # ['python-sdk', 'typescript-sdk', 'inspector']
Drop context_arguments= and the same call returns []. The handler can't know which repos to offer until it knows the owner.
!!! info
Completion also takes total= and has_more=. Set them when values is a slice of a longer
list, so a UI can show "and 200 more". Most handlers never need them.
@mcp.completion() registers the one handler. It's async def (ref, argument, context) -> Completion | None.isinstance(ref, ...) and on argument.name. Filter by argument.value yourself.None becomes an empty list. It is never an error.context.arguments holds the already-resolved values; the client supplies them as context_arguments=.completions capability appears the moment you register the handler. Without it, the request is Method not found.Suggestions help before a tool runs. To ask the user a question in the middle of one, you want Elicitation.