Module 5: Prompts — Reusable Templates

What an MCP prompt is

Description

Module 1 already previewed the definition in one sentence: a prompt is "a reusable template, user-controlled." This lesson stops on every word of that sentence, the same way Module 4 did with the definition of resource. "Reusable template" rules out the confusion of thinking a prompt is fixed data like a resource. "User-controlled" rules out the most common confusion of all: thinking a prompt is just a tool with a different name, because in both cases "something happens when you invoke it."

By the end of this lesson you'll be able to distinguish a prompt from a tool and from a resource using a single criterion — who decides to activate it — without needing to see a single JSON-RPC message yet.

Connection to the module

This lesson is purely conceptual, the same way lesson 02 of Module 4 was for resources — it doesn't execute anything yet. It's the ground lessons 03 (prompts/list) and 04 (prompts/get) stand on: if the definition is clear here, those two lessons are just "what the JSON for this concept looks like."


The definition, word by word

An MCP prompt is a named message template, optionally parameterized by arguments, that a server exposes for the user to activate explicitly — unlike a tool (which the model decides to invoke) or a resource (which the application decides to show).

"Named message template"

A prompt has a name (like "plan_booking") that identifies it, just as a tool has its name and a resource its uri. But what sits behind that name is different: it's not a function that produces an effect (like a tool) nor a document that already exists, ready to be read (like a resource) — it's a message recipe, a text (or a sequence of turns) the server builds at the moment it's requested, typically combining a fixed template with the concrete values of its arguments.

"Optionally parameterized by arguments"

A prompt can declare zero or more arguments, each with name, description, and required (True/False). In Reservo, plan_booking declares a single argument, room, and it's optional (required: False) — the prompt remains valid and useful even if no one specifies a room. Compare it to a tool's inputSchema (Module 3): there, every property carries a full type, possibly an enum, and can be validated with type precision. A prompt's arguments are deliberately simpler — there's no type or enum in the PromptArgument specification, because its purpose isn't to validate a function call: it's to identify which blank in the template can be filled in.

"User-controlled"

This is the word that sets a prompt apart from everything else. The MCP specification categorizes the three primitives by who decides to use them — the same table you already saw, partially, in Module 4:

tools       -> model-controlled     the MODEL decides to invoke it, based on the conversation
resources   -> application-driven   the APPLICATION decides what to show as available context
prompts     -> user-controlled      the USER decides to activate it explicitly

"User-controlled" means activating a prompt is a decision made by the person on the other side of the application — not the model reasoning about the conversation, not the application deciding on its own logic — typically choosing it from a catalog the application shows them (prompts/list, lesson 03 of this module). The model doesn't "decide" to use plan_booking the same way it decides to call get_quote; by the time the prompt is activated, the model hasn't even taken part in that decision yet — the user already made it, and what the model receives is the result: an already-assembled message, asking it to do something specific.


Prompt vs. tool: the contrast table

                    TOOL                          PROMPT
--------------------------------------------------------------------------
What it is          A function that produces       A message template
                     an effect or a calculation     filled in with data

Identified by       name + inputSchema              name + arguments (flat
                     (full JSON Schema)              list, no types)

Used with            tools/call, with arguments      prompts/get, with
                     that vary each time             arguments that vary

Who decides          The MODEL, reading the          The USER, choosing
to use it            description                     from a catalog

What it returns      content: the result of          messages: already-
                     running something               assembled conversation turns

Reservo example       get_quote(room, tier, hours)    plan_booking(room?)
                     -> {"price_cents": 6000}         -> instruction to review
                                                        policy and get a quote

Prompt vs. resource: "user-controlled" is not the same as "application-driven"

Another possible confusion: thinking that, since neither tools nor resources are user-controlled, prompts and resources are basically the same thing from the user's point of view. They aren't. The difference lies in who makes the decision, in each case:

  • A resource is chosen for display by the application (the host) — according to its own logic, without the user necessarily having asked for anything at that specific moment. It could, for example, automatically inject a resource's content when a new conversation opens, without the user having clicked on anything.
  • A prompt is chosen for activation by the user — an explicit act, typically choosing from a menu or typing a recognizable command. There's no way for a prompt to "activate itself": there's always a human choice involved.

In Reservo, this difference is clear: the two policies (M4) could appear automatically in the context of any conversation about Reservo, without anyone explicitly requesting them — that's consistent with being application-driven. plan_booking, on the other hand, only appears when someone, explicitly, decides "I want to use the plan-a-booking template" — never by the application deciding on their behalf.


Worked example: classifying five pieces of Reservo

Before touching wire protocol, a mental exercise — the same in spirit as lesson 02 of Module 4, now with all three categories complete — to cement the "who decides" criterion:

# No wire protocol in this lesson yet -- this is just for reasoning
# about the classification, with Python as clear notation.

candidates = [
    {"name": "get_quote",             "decides": "model"},
    {"name": "cancellation-policy",   "decides": "application"},
    {"name": "plan_booking",          "decides": "user"},
    {"name": "book_room",             "decides": "model"},
    {"name": "membership-tiers",      "decides": "application"},
]

kind_by_decider = {"model": "tool", "application": "resource", "user": "prompt"}

for item in candidates:
    kind = kind_by_decider[item["decides"]]
    print(f"{item['name']:20s} decide={item['decides']:12s} -> {kind}")

What to expect:

get_quote            decide=model        -> tool
cancellation-policy   decide=application  -> resource
plan_booking          decide=user         -> prompt
book_room              decide=model        -> tool
membership-tiers       decide=application  -> resource

The criterion is the same one across all three categories, and it never changes: it's not "how complex it is," nor "whether it has arguments," nor "whether it produces text" — it's, exclusively, who decides to activate it. Lesson 07 revisits this same table, now with the exact wire protocol shapes of each primitive next to it.


Common mistakes

  1. Thinking that "optional" via required: false is a property exclusive to prompts. No — tools can also have optional fields in their inputSchema (outside required). What's distinctive about a prompt isn't that its arguments are optional, but that the decision to activate it in the first place belongs to the user, whether it has arguments or not.

  2. Assuming a prompt needs at least one argument to make sense. No. A prompt with no arguments at all is still a legitimate template — a fixed instruction, with no blanks to fill — plan_booking with its single optional argument already shows you that, even with zero arguments provided, the prompt remains useful.

  3. Confusing "the user decides to activate it" with "the user writes the prompt's text by hand." No. The user chooses which template to activate (and, optionally, with what arguments) — the content of the generated message is built by the server, following the logic it declared (lesson 06 of this module shows it in code).

  4. Believing a prompt replaces writing instructions directly in a conversation. It doesn't replace it — it standardizes it. The same text plan_booking generates, you could, in principle, write yourself every time in a conversation; the prompt's advantage is not having to draft it from memory each time, and that any MCP-compatible host knows to show it to you as a recognizable option.


Exercises

Exercise 1: Tool, resource, or prompt (Easy)

For each of these four capabilities of a hypothetical MCP server for a technical support tool, say which of the three primitives it is, and in one sentence, who decides to activate it:

A) create_ticket(subject, body) -> creates a new ticket
B) sla_policy.md -> fixed document with the guaranteed response times
C) draft_incident_report(severity?) -> template that assembles an initial
   incident report, with severity as an optional argument
D) close_ticket(ticket_id) -> closes an existing ticket
See solution
  • A) Tool. The model decides to invoke it mid-conversation, when the user asks to create a ticket — the model reads the description and decides it's time to call it now.
  • B) Resource. A fixed document with a known URI; the application decides when to show it as context, and it's not an action that "gets executed."
  • C) Prompt. The user decides to activate the template explicitly (for example, choosing "draft incident report" from a menu), with severity as an optional argument that fills in the template — the exact same pattern as plan_booking with room.
  • D) Tool. It has an effect (closes the ticket) and the model invokes it when the conversation calls for it — the same pattern as cancel_booking in Reservo.

Exercise 2: Explain "user-controlled" without using that phrase (Medium)

A teammate tells you: "I don't get why prompts is a separate category — in the end, both a tool and a prompt end up being 'something activated with a name and some arguments,' right?" Write a 3-4 line explanation answering specifically with the concept of "who makes the decision to activate it," without using the phrase "user-controlled" (you have to explain it in your own words).

See solution

You're right that the shape of the message looks similar — a name, some arguments — but the real difference is in who decides that name gets invoked in the first place. With a tool, it's the model that, evaluating the ongoing conversation, decides "I need to call get_quote now" — a decision it makes without anyone explicitly asking for it at that instant. With a prompt, that decision is never made by the model: it's made by the person using the application, choosing from a catalog before the relevant conversation even starts. A prompt never "activates itself" mid-conversation the way a tool can.

Exercise 3: Design a prompt for a new case (Hard)

You're designing an MCP server for an internal code library. You want to expose a template that helps a developer prepare a pull request description, with two optional arguments: ticket_id (the ticket it resolves) and breaking_change (whether it introduces a breaking change). Declare the full Prompt object (following the name/description/arguments shape from this lesson, with no type in the arguments) and explain, in one sentence, why this is a prompt and not a tool, even though it ends up generating text to paste into a PR description — something a tool could, in principle, also do.

See solution
DRAFT_PR_DESCRIPTION_PROMPT = {
    "name": "draft_pr_description",
    "description": "Draft a pull request description following the team's template",
    "arguments": [
        {"name": "ticket_id", "description": "Ticket this PR resolves", "required": False},
        {"name": "breaking_change", "description": "Whether this PR introduces a breaking change", "required": False},
    ],
}

Why it's a prompt and not a tool: even though the final result is text (as a tool's result could also be), what sets the primitive apart isn't what it produces, but who decides to use it. A developer about to open a PR explicitly chooses "I want the PR description template" — it's not the model, evaluating the conversation, autonomously deciding "I'm going to draft a PR description now" without anyone having asked for it. If, instead, there were a draft_pr_description(...) tool the model could invoke on its own when it detects the user finished coding something, that would be a legitimate tool with the same name and the same result — the difference between the two versions isn't in the text they generate, but in whether activation depends on an explicit human choice (prompt) or on a model decision mid-conversation (tool).


Summary and next step

  • An MCP prompt is a message template with name and optional arguments, whose use is decided by the user (user-controlled), not by the model (like a tool) nor by the application (like a resource).
  • A prompt's arguments (name/description/required) are deliberately simpler than a tool's inputSchema: they carry no type or enum, because they don't validate a function call — they identify which blank in the template can be filled in.
  • The classification criterion is the same across all three primitives: who decides to activate it, not how complex it is or what it produces.
  • This guide's anchor prompt, plan_booking, has a single optional argument (room) and remains useful even without it — lesson 06 confirms this by running both cases.

Next lesson: 03 — prompts/list with arguments. The module's first real method: how a client discovers a server's prompt catalog, run against reservo-mcp-server extended with plan_booking.


Additional resources

  1. Model Context Protocol — Specification 2025-06-18: Prompts — The primitive's formal definition, including the user-controlled categorization and the shape of PromptArgument.
  2. Model Context Protocol — Specification 2025-06-18: Tools — The direct contrast: model-controlled, already covered in Module 3.
  3. Model Context Protocol — Specification 2025-06-18: Resources — The direct contrast: application-driven, already covered in Module 4.
  4. Model Context Protocol — Architecture overview — The overview of the three primitives and who controls each one.