The series has deferred this method three times. Resource templates have variables a user fills, and prompt arguments do too, and both times the note was that those values can be auto-completed. Completion is the method that does it. completion/complete suggests values for an argument as the user types, the way an IDE completes code. This post reads it on the wire, including the context field that makes one argument’s suggestions depend on another the user already answered.
This is part of MCP on the Wire, a series that takes the Model Context Protocol apart message by message, in Go. It comes out of building and running MCP servers in production, including the open-source
txn2/mcp-data-platform, an Apache-2.0 platform in Go that connects AI assistants to Trino, DataHub, and S3 through one MCP endpoint, enriching every result with semantic context (ownership, lineage, PII, data quality) behind OAuth 2.1 auth, personas, and an audit trail. Everything here is read straight off the wire against spec revision2025-11-25, with the official Go SDK atv1.6.1.
§Why a Primitive of Its Own
Completion could have been a field on prompts and resources, and it is not, for a reason worth stating. Suggestions are interactive and live: the user types a character, the client asks, the server answers, and it repeats on the next keystroke. That is a request-response loop, not static metadata, so it is its own method. It also serves two masters at once, the prompt arguments from the last post and the resource-template variables from the one before, which is why a completion request names what it is completing with a reference: ref/prompt for a prompt argument, ref/resource for a template variable. Both are slots a human fills, and completion is the help offered while they fill them.
That framing matters for the series’ running question. Completion is the one primitive so far that the model never sees. It is purely for the person typing into the client, an autocomplete dropdown, nothing that reaches the conversation. It belongs to the user-controlled world of prompts, not the model-controlled world of tools.
§The Request and the Result
A completion/complete request has two required parts and one optional one. The ref says what is being completed. The argument gives the argument’s name and its current partial value. Ask the server to complete the focus argument of a prompt, with c typed so far:
// completion/complete
{ "ref": { "type": "ref/prompt", "name": "code_review" },
"argument": { "name": "focus", "value": "c" } }
The server matches against its candidates and returns the suggestions:
{ "completion": { "total": 1, "values": ["concurrency"] } }
The result is a completion object with three fields. values is the array of suggestions, ranked by relevance, capped at 100 per response. total is the optional count of all matches, which can exceed the returned values. hasMore flags that more exist beyond this page. The spec asks servers to sort by relevance and fuzzy-match where it helps, and to rate-limit, since a keystroke-driven loop can fire fast. Completing a resource-template variable is the same request with a ref/resource and the template’s URI:
// completion/complete { "ref": { "type": "ref/resource", "uri": "db:///{table}/{column}" },
// "argument": { "name": "table", "value": "" } }
{ "completion": { "total": 3, "values": ["users", "orders", "products"] } }
§Context: Suggestions That Depend on Earlier Answers
A template with more than one variable is where completion earns its keep. The db:///{table}/{column} template has two slots, and the columns that make sense depend on the table already chosen. The context.arguments field carries the variables the user has already filled, so the server can scope its suggestions. The same column argument returns different values depending on what table holds:
column with value "". The context decides the answer.Both requests are captured from the wire, and they are identical except for one field. Without context, the server cannot know which table’s columns to offer, and a two-variable template would be unusable. With it, the completion narrows as the user goes, exactly like an IDE offering the methods of the object you just typed a dot after. The spec’s own example is the same pattern with a prompt: completing a framework argument with value: "fla" and context.arguments of { "language": "python" } returns ["flask"].
§The Go Server
One handler covers every completion the server offers. It is set in the server options, which is what turns the completions capability on, and it dispatches on the reference and the argument name:
func complete(ctx context.Context, req *mcp.CompleteRequest) (*mcp.CompleteResult, error) {
ref := req.Params.Ref
arg := req.Params.Argument
var values []string
switch {
case ref.Type == "ref/prompt" && arg.Name == "focus":
values = filterByPrefix([]string{"bugs", "concurrency", "security", "performance"}, arg.Value)
case ref.Type == "ref/resource" && arg.Name == "column":
table := ""
if req.Params.Context != nil {
table = req.Params.Context.Arguments["table"] // the already-filled variable
}
values = filterByPrefix(columnsFor(table), arg.Value)
}
return &mcp.CompleteResult{Completion: mcp.CompletionResultDetails{
Values: values, Total: len(values), HasMore: false,
}}, nil
}
The filterByPrefix here is a stand-in for whatever ranking the server wants, a prefix match in the demo, a fuzzy match or a database lookup in production. The shape is what matters: take the partial value and the context, return up to a hundred ranked strings. A request for a completion when the server has no handler is a -32601, the protocol’s way of saying the capability is not supported.
A platform like txn2/mcp-data-platform, whose resource templates stand in for every table in a warehouse and every term in a glossary, is exactly where argument completion earns its keep. The user types part of a table name and the server narrows the list, rather than the agent guessing at an identifier it half-remembers.
§What’s Next
The server primitives are done: tools, resources, prompts, and the completion that helps fill them. Everything so far has run client to server, the client asking and the server answering. The next post turns the protocol around. Sampling: When the Server Calls Your Model Back reads sampling/createMessage, the request a server sends to the client to borrow the host’s model, the inversion glimpsed back in the protocol-versus-API post, now taken apart message by message: model preferences, the tool loop inside it, and the consent gate that keeps the server from driving the model unchecked.
The production data platform behind this series is txn2/mcp-data-platform, available hosted as Plexara.