MCP Prompts: Reusable Prompt Templates for Agents
MCP prompts are a primitive in the Model Context Protocol that let a server expose reusable, parameterized prompt templates a client can list, fetch, and drop straight into a conversation. Instead of every agent hardcoding its own copy of a "write a commit message" or "summarize this ticket" instruction, the prompt lives on the server once, takes arguments, and gets pulled in on demand. If you've been maintaining prompt text as scattered constants across five different codebases, this is the piece of MCP that fixes it.
Most people who set up MCP servers only reach for tools and resources. Prompts are the quieter third primitive, and they get skipped because the first tool you wire up (a database query, a search call) doesn't need one. But once you're running more than one agent against the same server, or you're trying to keep a prompt in sync across a CLI, an IDE extension, and a Slack bot, prompts stop being optional and start being the thing that saves you from copy-paste drift.
What an MCP prompt actually is
An MCP server can declare three kinds of capabilities: tools (functions the model can call), resources (data the model can read), and prompts (message templates the user or client can invoke). A prompt is not something the model decides to call on its own, the way it decides to call a tool. It's something a human, or the client application on the human's behalf, selects explicitly, usually from a menu like a slash command.
Structurally, a prompt definition has:
- a
namethe client uses to reference it - an optional
descriptionfor humans browsing available prompts - an
argumentsarray, each with aname, optionaldescription, and arequiredflag - a handler that returns one or more
messages, each with arole(userorassistant) andcontent
When a client calls prompts/get with a name and a set of argument values, the server renders the template and returns the finished messages. The client then inserts those messages into the conversation, exactly as if the user had typed them (or as if the assistant had said them, if the role is assistant).
This matters because it puts prompt authorship where it belongs: with whoever owns the domain knowledge, not with whoever is building the agent that day.
Prompts vs tools vs resources
It's worth being precise about the boundary, because teams routinely implement a "prompt" as a tool and end up with something worse.
Tools are actions with side effects or computed output: hit an API, run a query, write a file. The model chooses when to call them based on the conversation. Their return value goes back into context as data.
Resources are addressable, mostly static content: a file, a database row, a config value. They get attached to context so the model can read them, but they aren't templated instructions.
Prompts are reusable conversation starters or scaffolds. They shape how the model should approach a task, not what data it should look at. The key distinguishing feature is user-initiated selection: the human (or the calling UI) picks the prompt, fills in arguments, and it becomes the first move in a specific interaction.
A concrete test: if the thing you're building should run automatically whenever the model judges it relevant, it's a tool. If it should show up in a "/" menu for a person to pick deliberately, it's a prompt. Code review checklists, incident postmortem templates, "explain this codebase to a new hire" walkthroughs, these are prompts. "Fetch the latest deploy status" is a tool.
A minimal MCP prompt server
Here's a small server exposing two prompts: a code review template and a bug report generator, both parameterized.
from mcp.server import Server
from mcp.types import Prompt, PromptArgument, GetPromptResult, PromptMessage, TextContent
server = Server("team-prompts")
CODE_REVIEW_PROMPT = Prompt(
name="code-review",
description="Structured review of a diff against team standards",
arguments=[
PromptArgument(name="diff", description="The unified diff to review", required=True),
PromptArgument(name="severity", description="minimum severity to report (low|medium|high)", required=False),
],
)
BUG_REPORT_PROMPT = Prompt(
name="bug-report",
description="Turn a raw description into a structured bug report",
arguments=[
PromptArgument(name="summary", description="One-line summary of the bug", required=True),
PromptArgument(name="repro_steps", description="Steps to reproduce", required=True),
],
)
@server.list_prompts()
async def list_prompts():
return [CODE_REVIEW_PROMPT, BUG_REPORT_PROMPT]
@server.get_prompt()
async def get_prompt(name: str, arguments: dict) -> GetPromptResult:
if name == "code-review":
severity = arguments.get("severity", "medium")
text = (
f"Review the following diff. Report issues at {severity} severity "
f"or above. For each finding, give file:line, the problem, and a fix.\n\n"
f"```diff\n{arguments['diff']}\n```"
)
return GetPromptResult(
description="Code review request",
messages=[PromptMessage(role="user", content=TextContent(type="text", text=text))],
)
if name == "bug-report":
text = (
f"Write a bug report titled: {arguments['summary']}\n\n"
f"Reproduction steps:\n{arguments['repro_steps']}\n\n"
f"Format as: Summary, Steps to Reproduce, Expected, Actual, Severity."
)
return GetPromptResult(
description="Bug report drafting",
messages=[PromptMessage(role="user", content=TextContent(type="text", text=text))],
)
raise ValueError(f"Unknown prompt: {name}")Register this server in a client (Claude Code, an IDE extension, or your own MCP client) and both prompts show up as invokable templates, typically triggered as /code-review and /bug-report once the client discovers them via prompts/list.
Calling a prompt from the client side
On the client, the flow is: discover, then get, then splice into context. Using the Python SDK's client session:
from mcp import ClientSession
async def use_prompt(session: ClientSession):
prompts = await session.list_prompts()
for p in prompts.prompts:
print(p.name, p.description)
result = await session.get_prompt(
"code-review",
arguments={"diff": open("changes.diff").read(), "severity": "high"},
)
for message in result.messages:
print(message.role, message.content.text)The client is responsible for deciding what to do with the returned messages: append them to an existing conversation, start a fresh one, or show them to the user for editing before sending. MCP doesn't mandate UI behavior here, only the wire format.
Argument completion
Prompts support argument autocompletion through the completion/complete request. If a prompt argument has a bounded set of sensible values (say, severity should really only be low, medium, or high), the server can supply completions so the client's UI can offer a dropdown instead of a free-text field.
@server.complete()
async def complete(ref, argument: dict):
if ref.name == "code-review" and argument["name"] == "severity":
return ["low", "medium", "high"]
return []This is a small addition but it's what separates a prompt template that feels like a real product feature from one that feels like a raw text box. If you're building an internal tool for a non-technical team, wire this up, it's the difference between "type the exact string" and "pick from a list."
Prompts referencing resources
A prompt's messages aren't limited to plain text. A message's content can be an embedded resource, which means a prompt can pull in a file or a data blob as part of the conversation it constructs. This is useful for prompts like "review this document against our style guide," where the style guide itself is a resource the server manages centrally.
from mcp.types import EmbeddedResource, TextResourceContents
@server.get_prompt()
async def get_prompt(name: str, arguments: dict) -> GetPromptResult:
if name == "style-check":
guide = TextResourceContents(
uri="resource://style-guide/current",
mimeType="text/markdown",
text=load_style_guide(),
)
return GetPromptResult(
messages=[
PromptMessage(
role="user",
content=EmbeddedResource(type="resource", resource=guide),
),
PromptMessage(
role="user",
content=TextContent(
type="text",
text=f"Check this text against the style guide above:\n\n{arguments['text']}",
),
),
]
)Now the style guide lives in exactly one place. Update it once on the server, every client that calls style-check gets the current version, no stale copies pasted into five different agent configs.
Why this beats hardcoded prompt strings
The obvious question: why not just keep prompts in a shared Python module or a prompts.yaml and import them? For a single codebase, that's genuinely fine, don't reach for MCP just to organize strings you already control.
MCP prompts earn their keep once you cross a boundary that a shared import can't cross:
- Multiple clients, one server. If your team's prompts need to be usable from Claude Code, a web app, and a Slack bot, an MCP server is the one place to define them instead of three. Each client just needs an MCP connection, not a copy of your prompt library.
- Prompts change more often than code deploys. Because
prompts/listandprompts/getare runtime calls, updating a prompt on the server means every connected client sees the new version on its next call, no redeploy required on the client side. - Non-engineers own prompt content. If a support lead maintains the "how we write a customer escalation summary" template, they can own the MCP server's prompt definitions without touching agent code. Contrast that with a prompt buried as a string constant three files deep in an agent's source tree, which nobody outside engineering will ever find or safely edit.
- Prompts need arguments and completions. A YAML file can hold a template string, but it doesn't give you
requiredargument validation or UI-friendly completion hints for free. MCP's schema does.
If none of those apply, don't bother. A team of two engineers sharing one repo doesn't need a protocol server for prompt reuse, a prompts.py file is less moving parts and easier to debug.
Versioning and backward compatibility
Prompts drift. The severity argument you added last month might not exist in a client that cached the prompt list a week ago. A few practical rules:
- Treat
requiredarguments as a contract. Adding a new required argument to an existing prompt breaks every client that calls it with the old argument set. Add new arguments as optional with a sane default, or ship a new prompt name (code-review-v2) instead of mutating the old one. - Keep prompt names stable and put breaking changes behind a new name. Clients often let users bind a keyboard shortcut or slash command to a prompt name; renaming or repurposing that name out from under them is a bad surprise.
- Return a clear
descriptionon every prompt and every argument. Clients render these directly in their prompt picker UI, and an under-described prompt is an unused prompt. - If a prompt's behavior depends on server-side state (say, it embeds "today's on-call engineer"), document that explicitly in the description so callers understand the output isn't purely deterministic from the arguments they passed.
Debugging prompts
When a prompt renders wrong output, check these in order before assuming the model is at fault:
- Call
prompts/listdirectly and confirm the argument schema matches what you think you're sending. A silently-dropped argument (wrong key name) is the most common bug. - Call
prompts/getwith your exact arguments outside of any agent loop, and read the raw returnedmessages. Most "the agent is ignoring my instructions" bugs are actually "the template rendered blank because an argument was missing." - Check role assignment. A prompt message set as
role: assistantwhen it should berole: userwill change how the model treats it, since assistant-role content is read as something the model itself already said. - If a resource is embedded, confirm the resource fetch itself succeeds independently of the prompt. A broken resource read inside a prompt handler often surfaces as a confusing empty message rather than a clean error.
Most MCP inspector tools (the reference inspector shipped alongside the MCP SDKs) let you call prompts/list and prompts/get interactively without wiring up a full client, which is the fastest way to isolate whether the bug is in the server's template logic or in how your agent consumes the result.
FAQ
What's the difference between an MCP prompt and a system prompt? A system prompt is set once per conversation by the client or application and shapes the model's overall behavior for that session. An MCP prompt is a discrete, named, parameterized template that gets fetched and inserted on demand, usually as a user-role message mid-conversation, and can be one of many available from a server. They're complementary: your system prompt might say "you are a code review assistant," while an MCP prompt supplies the specific "review this diff" instruction for one turn.
Can a prompt call a tool? Not directly. A prompt returns static (or server-rendered) messages; it doesn't execute tool calls itself. But a prompt's rendered text can instruct the model to use a specific tool ("use the run_tests tool before responding"), and the model will act on that instruction in the normal tool-calling flow once the messages are in context.
Do I need a full MCP server just to reuse a few prompts? No. If you're not sharing across multiple clients or teams, a local module of template strings is simpler and has less operational overhead. Reach for MCP prompts when you need one server of truth serving multiple independent clients, or when non-engineers need to edit prompt content without touching agent code.
How do arguments get validated? The required flag on each PromptArgument is advisory in the protocol, the server itself is responsible for validating incoming arguments in its get_prompt handler and returning a clear error if something required is missing. Well-behaved clients also use the argument list to build their input forms, but don't rely on the client to enforce validation, always check server-side.
Can prompts return multiple messages, including assistant turns? Yes. A prompt can return a whole seeded mini-conversation, for example a user message followed by a pre-written assistant acknowledgment, followed by another user message. This is useful for few-shot style prompts where you want to show the model an example exchange before the real request.
Is there a limit on how many prompts a server can expose? The protocol doesn't impose one, but practically, keep a server's prompt list scoped to a coherent domain. A server with forty unrelated prompts is hard for a client's picker UI to present usefully and hard for a team to maintain. Split by domain (one server for code-review-style prompts, another for customer-support templates) rather than piling everything into one server.
BootcampA 30-day guided bootcamp: build, harden and ship a production autonomous agent from scratch.
AI AgentsUnderstand how AI agents really work: the loop, the tools, the memory, and why most agent projects fail.
Related reading