MCP’s three server primitives are usually introduced as a list, which hides what actually distinguishes them: who decides to invoke them. Tools are model-invoked — the model chooses one mid-reasoning. Resources are application-controlled — the host decides what context to attach. Prompts are user-invoked: a human deliberately picks one from a menu, types a slash command, or clicks a button, and only then does anything happen. That single control-axis fact explains the whole design — why prompts are discoverable by name, why they declare arguments a person can fill in, why they return a sequence of messages rather than a blob of text, and why versioning them is a compatibility problem rather than a content problem. This article walks the protocol mechanics.

The control axis: who pulls the trigger

Every MCP server primitive answers a different question about control. A tool is exposed so the model can call it autonomously while working; the human may approve it, but the model decides it is needed. A resource is exposed so the host application can attach context — a file, a record, a page — on whatever policy it likes. A prompt is exposed so a person can deliberately start something.

This is not a cosmetic distinction. It determines where the primitive shows up in a client and what the invocation is allowed to assume. Because a prompt is triggered by a human who has already read its name and description, a prompt can legitimately be opinionated: it can encode a whole multi-turn workflow, prime the model with examples, and pull in context the user never has to name. Prompts are how a server ships expertise about how to ask, not just capability.

Advertisement

Two methods and one capability flag

The surface area is deliberately tiny. A server that offers prompts declares a prompts capability during the initialize handshake, and clients that see no such declaration simply never ask. From there the protocol is two JSON-RPC methods: prompts/list, which enumerates what is available, and prompts/get, which materialises one with arguments filled in.

The listing is paginated with the same opaque-cursor mechanism the rest of MCP uses, so a large prompt library need not come back in one response. The capability declaration can also advertise that the list is not static — a server whose prompt set changes (a new playbook is published, a workspace is switched) emits a list-changed notification so clients re-fetch instead of caching a stale menu. That pair — a paginated list plus a change notification — is what lets a client treat prompts as a live directory rather than configuration read once at startup.

What a listing entry actually declares

Each entry in prompts/list is metadata, not content. It carries a name — the stable identifier the client will pass back — a human-readable description, and a list of arguments. In current revisions each argument declares its own name, a description, and whether it is required. Some revisions also add a separate display title so the machine name and the label a user sees can differ.

Notice what is not there. Unlike a tool, which publishes a full JSON Schema inputSchema describing typed, nested parameters, a prompt’s argument list is intentionally flat, with values supplied as strings. That is a design choice, not an oversight: these fields get rendered as a small form for a human to fill in, so a deeply nested object graph would be the wrong shape. If your prompt needs structured typed input, that is a hint the operation belongs behind a tool instead.

prompts/get returns messages, not a string

This is the most important mechanical fact about MCP prompts, and the one most often gotten wrong when porting a homegrown template system. A prompts/get call takes the prompt name and a map of argument values, and the server responds with an optional description plus a list of messages, each with a role and a content block. It is a conversation fragment, not a rendered string.

The consequence is that a prompt can express things a string cannot. It can supply few-shot examples as alternating user and assistant turns. It can end on an assistant turn that half-completes an answer to constrain the format. It can interleave a question with the evidence needed to answer it, in a fixed order. Collapse all that into one string and the model loses the role boundaries that make the pattern work. Substitution also happens server-side: the client sends raw argument values and receives finished messages, so it never needs to know the template language.

Content blocks and embedded resources

A message’s content is a typed block, not plain text. Text is the common case, but current revisions also allow image and audio content and — most usefully — an embedded resource: an MCP resource’s contents inlined into the returned message, carrying its URI and MIME type.

Embedding is what makes a prompt self-sufficient. A ‘review this pull request’ prompt can return the instruction turn plus the actual diff, the contributing guide, and the relevant style rules, all inlined and all fetched by the server at the moment of invocation. The user supplied one argument; the server assembled the context. Because each embedded block keeps its URI, provenance survives the trip — the host can show which documents were pulled in and the model can cite them. The alternative, telling the model ‘go read resource X’ and hoping it does, costs a round trip and can silently not happen.

Arguments: validation and where errors go

Argument handling is a genuine contract, and the server is the enforcer. When a prompts/get arrives missing a required argument, naming an undeclared one, or referencing a prompt that does not exist, the correct response is a JSON-RPC error at the protocol layer.

That is worth stating explicitly because it differs from tools. A tool call that fails for a domain reason returns a successful JSON-RPC response carrying an isError flag, precisely so the model can see the failure and retry. Prompts have no such channel: there is no model in the loop to recover, because a human is still assembling the request. A bad prompts/get is a client bug or a user input problem, and it should surface as a protocol error the UI displays next to the offending field. Avoiding that failure altogether is the job of the completion capability, which lets a server suggest valid values for a prompt argument as the user types — covered in the MCP completion article.

Advertisement

Surfacing prompts in a client UI

Because prompts are user-invoked they need a place in the interface, and hosts have converged on a few patterns: a slash-command palette where each name becomes /name, an attachment menu, or buttons above the composer. Selecting one opens a small form built from the declared arguments; submitting it issues prompts/get and drops the returned messages into the conversation.

Two practical consequences follow. First, prompt names are user-facing strings, not internal identifiers — short, verb-first, and readable, because someone is going to type them. Second, when a host connects several servers at once names collide, so hosts typically namespace by server in the UI; servers should assume this and avoid generic names like summarize. The description field does real work too: it is the tooltip the user reads before deciding.

Versioning: the name is the contract

MCP does not give prompts a version field. What a client holds onto is the name and the argument list, so those two things are the compatibility surface, and the rules fall out from there. Rewriting the message text behind a prompt is safe — nobody depends on the wording. Adding a new optional argument is safe. Renaming an argument, making an optional one required, or removing one is a breaking change, because a saved invocation or a scripted client will start failing.

The workable discipline is additive-only evolution under a stable name, and a new name when you must break. Publish the successor alongside the original, mark the old one deprecated in its description, and retire it after a grace period. Version-suffixed names are a convention servers adopt, not a protocol feature — and the same is true of the localization, governance, and metrics stages in the map below. Only the top row is protocol; the rest is operational practice you build around it.

MCP prompts — templates + arguments + suggestions + versioningserver-authored prompt starters for hostsServer publishprompts listPrompt templatecontent + argsArguments schematyped inputsAuto-completeargument suggestionsClient UIuser picks + fillsRendered promptsent to modelVersion + compatsemverLocalizationi18n templatesGovernancereview + approvalMetricsusage + editsOps — deprecation + safety + auditrendercallevolvetranslatereviewmeasuremeasureoperateoperate
MCP prompts pipeline from server to model call.

Prompt, tool, or resource?

The decision is almost always answerable by asking who should initiate.

If…UseBecause
The model should decide to invoke it mid-taskToolAutonomous, typed, returns data the model reasons over
The host should attach it as background contextResourceURI-addressed, read on the application’s policy
A person should deliberately kick off a workflowPromptNamed, discoverable, returns a primed conversation

The classic mistake is exposing a prompt for something the model needs to call itself: it will never be reached, because nothing invokes prompts but a human. The mirror-image mistake is a tool whose real job is to say ‘here is how to approach this task’ — a prompt wearing the wrong hat. They compose well: a prompt’s returned messages often set up exactly the tool calls the model then makes on its own.

The trust boundary you still own

User-invoked does not mean trusted. A prompt is server-authored text that lands directly in the conversation, often carrying embedded resource content that was fetched from somewhere else entirely. That is a prompt-injection surface with two entrances: the server’s own template, and whatever the server inlined into it.

The mitigations are unglamorous. A host should treat prompt output as data to be shown, not instructions to be obeyed silently — render what is about to be inserted before it goes in, keep the returned messages attributed to their source, and never let a prompt result silently change host settings or auto-approve tool calls. A server should keep its templates auditable and resist smuggling standing instructions into a prompt the user believes is a convenience shortcut. The human deliberately pulled the trigger; make sure they can see what they fired.

MCP prompts are the user-invoked primitive, and that one fact drives the design. Two methods carry it: prompts/list publishes a paginated, change-notifying directory of named templates with declared arguments, and prompts/get materialises one. The payload is a sequence of messages, not a string — which is what lets a prompt ship few-shot turns, ordered context, and embedded resources with their URIs intact. Arguments are a flat, human-fillable contract the server validates, failing as JSON-RPC errors rather than a tool-style isError. With no version field, the name plus the argument list is the contract: evolve additively, take a new name to break. Choose a prompt when a person should start the work, a tool when the model should, a resource when the application should.