Home Blog Contact
Home/Blog/How to Add Reusable Prompt Templates to an MC…
How toLLM EngineeringMCPFastMCPPrompt Engineering

How to Add Reusable Prompt Templates to an MCP Server

9 min readBy Miloš Mitrović

Prompts are the Model Context Protocol primitive most servers skip, and it shows. If your server exposes tools but every client has to hand-write the wording that drives them, you have shipped half a product. A prompt is a named, argument-driven message template that the server owns and the client surfaces to the user, usually as a slash command. To add one you declare the prompts capability, answer two methods (prompts/list and prompts/get), and in the Python SDK that whole contract collapses into a single @mcp.prompt() decorator.

Key Takeaways

  • Short answer: decorate a function with @mcp.prompt(). Its parameters become the prompt's arguments, and its return value becomes the message or messages the host feeds to the model.
  • Prompts are user-controlled. The host surfaces them for a person to pick, which is the opposite of tools, where the model decides to call them.
  • A server advertises support with the prompts capability during initialization. Set listChanged: true only if you will actually emit notifications/prompts/list_changed.
  • The wire protocol is two methods: prompts/list for discovery (paginated) and prompts/get, which returns a description plus a messages array.
  • Message content can be text, image, audio, or an embedded resource, and each argument can be auto-completed through the completion API.

What You Need Before You Start

Keep the surface small. This walkthrough uses the official Python SDK, which bundles the FastMCP framework, so one dependency covers everything.

  • Python 3.10 or newer, which the MCP Python SDK on GitHub requires.
  • The SDK with its CLI extra: pip install "mcp[cli]".
  • An MCP host that renders prompts, such as Claude Desktop, or the MCP Inspector for local testing.

Add a Prompt to Your MCP Server in Five Steps

Work top to bottom. When you finish step five you have a server that lists two prompts and returns real messages for each.

  1. Install the SDK.
    pip install "mcp[cli]"
  2. Create the server. FastMCP declares the prompts capability for you the moment you register a prompt, so there is no capability boilerplate to write.
    from mcp.server.fastmcp import FastMCP
    from mcp.server.fastmcp.prompts import base
    
    mcp = FastMCP("prompt-demo")
  3. Add a single-message prompt with an argument. The function parameter code becomes a required prompt argument because it has no default. Returning a plain string produces one user message.
    @mcp.prompt()
    def review_code(code: str) -> str:
        """Ask the model to review a snippet and suggest fixes."""
        return f"Please review this code and suggest improvements:\n\n{code}"
  4. Add a multi-message prompt with explicit roles. Return a list of typed messages when you want to prime a short exchange rather than a single instruction. An optional argument gets a default value.
    @mcp.prompt()
    def debug_error(error: str, language: str = "python") -> list[base.Message]:
        """Seed a debugging conversation for a stack trace."""
        return [
            base.UserMessage(f"I hit this error in {language}:"),
            base.UserMessage(error),
            base.AssistantMessage("I'll help debug that. What did you expect to happen?"),
        ]
  5. Run it. Add an entry point and launch the server over stdio, or open it in the Inspector.
    if __name__ == "__main__":
        mcp.run()
    
    # quick manual test with the Inspector:
    # mcp dev server.py

That is the whole task. The reader who stops here has two working prompts. The rest of this guide explains what crossed the wire and how to fix it when a host refuses to show them.

What the Protocol Actually Sends

FastMCP hides the JSON-RPC, but knowing the shape helps when you debug a client or write your own. Discovery uses prompts/list, which returns each prompt's name, optional title and description, and an arguments array where every entry carries name, description, and required. The MCP prompts specification fixes those exact field names in the 2025-06-18 revision.

Retrieval uses prompts/get. The client sends the prompt name plus an arguments object, and the server returns a description and a messages array:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "description": "Code review prompt",
    "messages": [
      {
        "role": "user",
        "content": { "type": "text", "text": "Please review this Python code..." }
      }
    ]
  }
}

Each message's content is one of four types the spec allows: text, image and audio (both base64 with a mimeType), or an embedded resource with its own uri. That last type is the bridge to your other primitives, so a prompt can inline a file or record your server already exposes. If you are wiring those up, see the companion guide on how to expose resources from an MCP server.

How Do Prompt Arguments Get Auto-Completed?

A good host offers argument suggestions as the user types. That is not magic in the prompt itself. It runs through the separate completion API, and the client calls completion/complete against a prompt reference to fetch candidate values. The mechanics and request shape live in the completion utility specification. Wire it up when an argument has a bounded set of sensible values, such as a project name or a known enum.

When Should the Prompt List Change at Runtime?

Most servers register their prompts once at startup and never touch the list again. If yours adds or removes prompts while running, declare listChanged: true and emit notifications/prompts/list_changed when the set changes, so clients re-fetch. Declaring the flag without ever sending the notification is worse than not declaring it, because clients trust the capability and cache a stale list.

Troubleshooting the Failures You Will Actually Hit

  • The host shows tools but no prompts. Confirm the client supports the prompts primitive at all, since some surface only tools. Then restart the host after any config change, because most read the server list once at launch. If you build against the standalone FastMCP 2.x package instead of the bundled version, check its own prompts documentation, as the import path is from fastmcp import FastMCP rather than from mcp.server.fastmcp.
  • Error -32602, missing required arguments. A parameter with no default is required. Either give the client that argument or add a default value so the parameter becomes optional.
  • Error -32602, invalid prompt name. The requested name did not match a registered prompt. The name is the function name unless you passed name= to the decorator, so keep the two in sync.
  • The prompt loads but the model ignores it. Prompts inject context that a person chose to insert. They do not force behavior the way a tool call does. If you need the model to take an action, expose a tool, not a prompt.

What to Do Next

You have prompts flowing. Three moves make the server production-ready:

  • Round out the primitives. If you have not built the tools and resources yet, start from the full walkthrough on how to build an MCP server in Python with FastMCP.
  • Add argument completion using the completion API so slash-command arguments suggest real values.
  • Verify the list and get calls against a live client. The fastest path is the guide on how to debug an MCP server with the MCP Inspector, which drives prompts/list and prompts/get by hand.

Sources

M
Miloš Mitrović
Email Marketing for Ecommerce

Have a question or a project?

Whether it is about this post or a system you want built, I'm happy to talk.

Get in touch

404

Post not found. It may have been moved or the link is incorrect.

← Back to the blog
Summarize with AI
ChatGPT, Perplexity, and Grok open with the prompt ready to run. Claude, Gemini, and Copilot open a chat with the prompt copied; press Ctrl+V (Cmd+V on Mac) to paste. The full text is included, so it works even without web access.