Completion
Completion is server-side autocomplete for prompt arguments and resource template variables: the client sends the partial value the user has typed so far, your callback returns the matching suggestions.
Wrap an argument with completable
completable wraps one field of a prompt's argsSchema — the schema validates exactly as before, and the second argument suggests values for the field.
import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
const languages = ['typescript', 'javascript', 'python', 'rust', 'go'];
const server = new McpServer({ name: 'review', version: '1.0.0' });
server.registerPrompt(
'review-code',
{
description: 'Review code for best practices',
argsSchema: z.object({
language: completable(z.string().describe('Programming language'), value =>
languages.filter(language => language.startsWith(value))
)
})
},
({ language }) => ({
messages: [
{
role: 'user' as const,
content: { type: 'text' as const, text: `Review this ${language} code for best practices.` }
}
]
})
);The first completable field also registers the server's completion/complete handler and advertises the completions capability — nothing to declare. A request for completions of language with the partial value ty now returns typescript.
Every result quoted on this page comes from client.complete() on an in-memory Client connected to this server — Try it from a client shows the call, and Test a server shows the wiring.
Return suggestions from the complete callback
The callback receives the value typed so far and returns every match — string[], or a promise of one when the lookup is async. Register a second prompt whose repo argument completes from an async list.
const branchesByRepo: Record<string, string[]> = {
'typescript-sdk': ['main', 'release/1.x', 'release/2.x'],
'python-sdk': ['main', 'release/1.x'],
inspector: ['main']
};
async function listRepos(): Promise<string[]> {
return Object.keys(branchesByRepo);
}
server.registerPrompt(
'review-pr',
{
description: 'Review the open pull requests on one branch',
argsSchema: z.object({
repo: completable(z.string().describe('Repository name'), async value => {
const repos = await listRepos();
return repos.filter(repo => repo.startsWith(value));
}),
branch: completable(z.string().describe('Target branch'), completeBranch)
})
},
({ repo, branch }) => ({
messages: [
{
role: 'user' as const,
content: { type: 'text' as const, text: `Review the open pull requests on ${repo}@${branch}.` }
}
]
})
);Return the full match list; the SDK truncates values to 100 entries and fills in total and hasMore. Completing repo with the value ty returns:
{ values: [ 'typescript-sdk' ], total: 1, hasMore: false }branch points at completeBranch, defined in the next section.
Use the other arguments for context
The callback's optional second parameter carries arguments: the values the client has already filled in for the prompt's other arguments. Use it to make one field's suggestions depend on another's.
async function completeBranch(value: string, context?: { arguments?: Record<string, string> }): Promise<string[]> {
const repo = context?.arguments?.repo;
if (!repo) return [];
return (branchesByRepo[repo] ?? []).filter(branch => branch.startsWith(value));
}With repo: 'typescript-sdk' already filled in, completing branch with the value rel returns:
{ values: [ 'release/1.x', 'release/2.x' ], total: 2, hasMore: false }Clients are not required to send context — return an empty list when it is missing, never throw.
Complete a resource template variable
Resource template variables complete through the template's complete map — one callback per URI variable, with the same (value, context?) signature. completable is for prompt arguments only.
server.registerResource(
'readme',
new ResourceTemplate('repo://{repo}/readme', {
list: undefined,
complete: {
repo: async value => {
const repos = await listRepos();
return repos.filter(repo => repo.startsWith(value));
}
}
}),
{ description: 'The README of one repository' },
async (uri, { repo }) => ({
contents: [{ uri: uri.href, text: `Repository: ${repo}` }]
})
);The completion request targets the template by its URI pattern: ref: { type: 'ref/resource', uri: 'repo://{repo}/readme' } with argument: { name: 'repo', value: 'py' } returns python-sdk.
Try it from a client
Client.complete() sends completion/complete. Complete language on review-code with an empty value to get the whole list.
const result = await client.complete({
ref: { type: 'ref/prompt', name: 'review-code' },
argument: { name: 'language', value: '' }
});
console.log(result.completion);The server's callback ran once and produced every value:
{
values: [ 'typescript', 'javascript', 'python', 'rust', 'go' ],
total: 5,
hasMore: false
}TIP
A host issues the same request as the end user types: MCP Inspector requests completions while you fill in a prompt's arguments and shows the returned values as suggestions.
Recap
completable(schema, callback)attaches autocompletion to one prompt argument; the schema validates exactly as before.- The callback receives the partial value and returns the full match list, synchronously or as a promise; the SDK caps
valuesat 100 and setstotalandhasMore. context.argumentscarries the prompt's already-filled arguments, so one field's suggestions can depend on another's.- Resource template variables complete through the template's
completemap, notcompletable. - The first completable registration advertises the
completionscapability;Client.complete()sends the request.