49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
126 lines
5.4 KiB
Markdown
126 lines
5.4 KiB
Markdown
# Completions
|
|
|
|
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.
|
|
|
|
## Something worth completing
|
|
|
|
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:
|
|
|
|
```python title="server.py" hl_lines="6 12"
|
|
--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.
|
|
|
|
## The completion handler
|
|
|
|
Add **one** function decorated with `@mcp.completion()`:
|
|
|
|
```python title="server.py" hl_lines="22-30"
|
|
--8<-- "docs_src/completions/tutorial002.py"
|
|
```
|
|
|
|
* There is one handler per server. Every completion request lands here, and you branch on what's being completed.
|
|
* It must be `async def`: the SDK awaits it.
|
|
* It receives three arguments:
|
|
* `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.
|
|
* You return a `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.
|
|
|
|
### Try it
|
|
|
|
Drive it with the in-memory `Client` from **[Testing](../get-started/testing.md)**. Call
|
|
`client.complete()` with `ref=PromptReference(name="review_code")` and
|
|
`argument={"name": "language", "value": "py"}`:
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
result.completion.values # []
|
|
```
|
|
|
|
`None` means *"no suggestions"*, never an error. A UI falls back to a plain text box.
|
|
|
|
## A capability you never declared
|
|
|
|
Registering the handler is the declaration. Connect a client and look:
|
|
|
|
```python
|
|
client.server_capabilities.completions # CompletionsCapability()
|
|
```
|
|
|
|
You didn't list `completions` anywhere. The SDK saw the handler and declared the capability for you. 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.
|
|
|
|
## Dependent arguments
|
|
|
|
`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**:
|
|
|
|
```python title="server.py" hl_lines="9-12 35-39"
|
|
--8<-- "docs_src/completions/tutorial003.py"
|
|
```
|
|
|
|
* The new branch fires for the template's `repo` parameter.
|
|
* `context.arguments` is a `dict[str, str] | None` of the values picked so far (here, `owner`).
|
|
* No `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"}`:
|
|
|
|
```python
|
|
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.
|
|
|
|
## Recap
|
|
|
|
* Completions are suggestions for **prompt arguments** and **resource template parameters**. Nothing else.
|
|
* `@mcp.completion()` registers the one handler. It's `async def (ref, argument, context) -> Completion | None`.
|
|
* Branch on `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=`.
|
|
* The `completions` capability appears the moment you register the handler. Without it, the request is `Method not found`.
|
|
|
|
Suggestions help while the user is still *filling in* a prompt or template; to ask them a question in the *middle* of a tool call, you want **[Elicitation](../handlers/elicitation.md)**. Everything a tool can return besides text is **[Images, audio & icons](media.md)**.
|