Completions
Autocomplete prompt arguments and template parameters.
completion/complete lets a client suggest values while the user is filling in a prompt argument or a resource-template parameter. You supply a complete callback; the method itself is wired for you on both eras.
Prompt arguments and resource templates share one callback signature:
type CompleteCallback = (
ctx: {
argument: { name: string; value: string };
arguments?: Record<string, string>;
},
event: H3Event,
) => MaybePromise<CompleteResult>;#Prompt Arguments
Each argument can have its own callback, receiving the partial value typed so far as ctx.argument.value:
import { definePrompt } from "h3-mcp";
const deployPrompt = definePrompt({
name: "deploy",
description: "Deploy to an environment",
arguments: [
{
name: "environment",
required: true,
complete: async ({ argument }, event) => ({
values: ["production", "staging", "development"].filter((e) =>
e.startsWith(argument.value),
),
}),
},
{
name: "region",
complete: async ({ argument }) => ({
values: ["us-east-1", "eu-west-1", "ap-south-1"].filter((r) =>
r.startsWith(argument.value),
),
total: 3,
hasMore: false,
}),
},
],
handler: async (args) => ({
messages: [
{
role: "user",
content: { type: "text", text: `Deploy to ${args.environment} in ${args.region}` },
},
],
}),
});values is required; total and hasMore are optional and let a client show "3 of 240 matches" instead of a truncated list.
#Narrowing With Already-Resolved Arguments
ctx.arguments carries the values the client has already filled in (params.context.arguments), so a suggestion list can depend on an earlier answer:
arguments: [
{ name: "environment", required: true, complete: completeEnvironment },
{
name: "region",
complete: async ({ argument, arguments: resolved }) => ({
// Only the regions this environment actually runs in.
values: (await listRegions(resolved?.environment)).filter((r) =>
r.startsWith(argument.value),
),
}),
},
],The map is validated at the boundary — an object of string values, capped at 64 entries and 1 KB per value — but the values themselves are whatever the client sent. Treat them like any other request input.
#Resource Template Parameters
A template gets one callback for all of its variables, so it switches on ctx.argument.name:
import { defineResourceTemplate } from "h3-mcp";
const fileTemplate = defineResourceTemplate({
name: "project-file",
uriTemplate: "app://files/{path}",
description: "Project files",
complete: async ({ argument }, event) => {
if (argument.name === "path") {
return { values: await listFiles(argument.value) };
}
return { values: [] };
},
handler: async (uri, variables) => ({
contents: [{ uri: uri.toString(), text: await readFile(variables.path) }],
}),
});#Capability
The server advertises completions: {} as soon as any prompt argument or resource template has a complete callback — there is nothing to enable. A prompt that declares its arguments as a schema has no per-argument callbacks, so it contributes nothing here.
Tip
Completion callbacks run on every keystroke a client sends. Keep them cheap: filter an in-memory list, or cache the expensive lookup outside the callback.
Important
ctx.argument.value is untrusted input. Completing against a database or filesystem means an attacker can probe it one prefix at a time — scope the lookup to what that request is allowed to see, and cap the number of values you return.