# How to define prompt templates in an MCP server

- Tool: MCP
- Difficulty: Intermediate
- Time required: 15-25 min
- Compatibility: MCP TypeScript SDK 1.x, MCP Python SDK 1.x
- Last updated: March 2026

## TL;DR

Prompts in MCP servers are reusable message templates with optional arguments that AI clients can invoke to start structured interactions. Register prompts with server.registerPrompt() in TypeScript or @mcp.prompt() in Python, define argument schemas, and return pre-formatted message arrays that guide the AI's behavior.

## Defining Prompts in Your MCP Server

Prompts are the third core primitive in MCP, alongside tools and resources. While tools let the AI perform actions and resources provide data, prompts are server-defined message templates that guide how the AI approaches specific tasks. They are user-controlled — the AI client presents available prompts to the user, who selects one and fills in arguments.

Prompts are ideal for code review workflows, debugging templates, report generators, and any interaction pattern you want to standardize. This tutorial covers prompt registration in both TypeScript and Python, including argument schemas, multi-message sequences, and embedded resource references.

## Before you start

- MCP TypeScript SDK or Python SDK installed and configured
- A running MCP server with transport set up
- Familiarity with MCP tools and resources (prompts build on these concepts)
- Basic understanding of Zod schema validation (for TypeScript)

## Step-by-step guide

### 1. Register a basic prompt with arguments

Use server.registerPrompt() to define a prompt template. The first argument is the prompt name, the second is a config object with a description and argsSchema using Zod, and the third is a handler that receives the argument values and returns a messages array. Each message has a role (user or assistant) and content.

```
// TypeScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({ name: "prompt-server", version: "1.0.0" });

server.registerPrompt("review-code", {
  description: "Generate a code review for the given code snippet",
  argsSchema: {
    code: z.string().describe("The code to review"),
    language: z.string().default("typescript").describe("Programming language"),
  },
}, ({ code, language }) => ({
  messages: [{
    role: "user",
    content: {
      type: "text",
      text: `Review this ${language} code for bugs, security issues, and style improvements:\n\n\`\`\`${language}\n${code}\n\`\`\`\n\nProvide specific line-by-line feedback.`,
    },
  }],
}));
```

**Expected result:** The prompt appears in the prompts/list response and can be invoked by MCP clients.

### 2. Create multi-message prompt sequences

Prompts can return multiple messages to set up a conversation flow. Use a system-like setup by having an initial assistant message followed by a user message. This is useful for establishing context before the user's actual request.

```
// TypeScript
server.registerPrompt("debug-error", {
  description: "Help debug an error with structured analysis",
  argsSchema: {
    error: z.string().describe("The error message or stack trace"),
    context: z.string().optional().describe("Additional context about when the error occurs"),
  },
}, ({ error, context }) => ({
  messages: [
    {
      role: "user",
      content: {
        type: "text",
        text: `I need help debugging this error:\n\n${error}${context ? `\n\nContext: ${context}` : ""}\n\nPlease analyze:\n1. What is the root cause?\n2. What are the likely fixes?\n3. How can I prevent this in the future?`,
      },
    },
  ],
}));
```

**Expected result:** The client receives a structured conversation starter that guides the AI to provide systematic debug analysis.

### 3. Embed resource references in prompts

Prompts can include embedded resource content, combining the prompt primitive with the resource primitive. Use type: 'resource' in the message content to reference a resource URI. The client resolves the resource and includes its content in the conversation, giving the AI additional context alongside the prompt template.

```
// TypeScript
server.registerPrompt("analyze-with-schema", {
  description: "Analyze code against the project schema",
  argsSchema: {
    code: z.string().describe("Code to analyze"),
  },
}, ({ code }) => ({
  messages: [
    {
      role: "user",
      content: {
        type: "resource",
        resource: {
          uri: "db://schema",
          text: "(Schema will be loaded from resource)",
          mimeType: "text/plain",
        },
      },
    },
    {
      role: "user",
      content: {
        type: "text",
        text: `Given the database schema above, review this code for correctness:\n\n${code}`,
      },
    },
  ],
}));
```

**Expected result:** The AI receives both the database schema and the code to analyze in a single structured conversation.

### 4. Define prompts in Python with FastMCP

In Python, use the @mcp.prompt() decorator. The function's docstring becomes the prompt description. Arguments are defined as function parameters with type hints. Return a string for a simple single-message prompt, or return a list of message dictionaries for multi-message sequences.

```
# Python
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("prompt-server")

@mcp.prompt()
async def review_code(code: str, language: str = "python") -> str:
    """Generate a code review for the given code snippet."""
    return f"""Review this {language} code for bugs, security issues, and style:

```{language}
{code}
```

Provide specific, actionable feedback."""

@mcp.prompt()
async def generate_tests(code: str, framework: str = "pytest") -> str:
    """Generate unit tests for the given code."""
    return f"""Write comprehensive {framework} tests for this code:

```python
{code}
```

Cover edge cases, error conditions, and happy paths."""
```

**Expected result:** Both prompts are registered and available to clients connecting to the Python MCP server.

### 5. Test prompts with the MCP Inspector

Use the MCP Inspector to verify your prompts are registered correctly and return the expected messages. Start your server, connect the Inspector, navigate to the Prompts tab, and invoke each prompt with test arguments. Verify the returned messages contain the correct structure and interpolated values. For teams building production MCP servers, RapidDev can help establish testing pipelines that cover all prompt variations.

```
# Start your server for testing
npx tsc && node dist/index.js

# In another terminal, use the Inspector
npx @modelcontextprotocol/inspector
```

**Expected result:** The Inspector shows all registered prompts, lets you fill in arguments, and displays the generated message array.

## Complete code example

File: `src/index.ts`

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "prompts-demo-server",
  version: "1.0.0",
});

// Code review prompt
server.registerPrompt("review-code", {
  description: "Generate a structured code review",
  argsSchema: {
    code: z.string().describe("The code to review"),
    language: z.string().default("typescript").describe("Programming language"),
    focus: z.enum(["bugs", "security", "performance", "all"]).default("all")
      .describe("Review focus area"),
  },
}, ({ code, language, focus }) => ({
  messages: [{
    role: "user",
    content: {
      type: "text",
      text: `Review this ${language} code with focus on ${focus}:\n\n\`\`\`${language}\n${code}\n\`\`\`\n\nFor each issue found, provide:\n- Line number\n- Severity (critical/warning/info)\n- Description\n- Suggested fix`,
    },
  }],
}));

// Error debugging prompt
server.registerPrompt("debug-error", {
  description: "Structured error debugging assistant",
  argsSchema: {
    error: z.string().describe("Error message or stack trace"),
    context: z.string().optional().describe("When/where the error occurs"),
  },
}, ({ error, context }) => ({
  messages: [{
    role: "user",
    content: {
      type: "text",
      text: `Debug this error:\n\n${error}${context ? `\n\nContext: ${context}` : ""}\n\nAnalyze:\n1. Root cause\n2. Step-by-step fix\n3. Prevention strategy`,
    },
  }],
}));

// Test generation prompt
server.registerPrompt("generate-tests", {
  description: "Generate unit tests for code",
  argsSchema: {
    code: z.string().describe("Code to test"),
    framework: z.enum(["jest", "vitest", "mocha"]).default("jest")
      .describe("Test framework"),
  },
}, ({ code, framework }) => ({
  messages: [{
    role: "user",
    content: {
      type: "text",
      text: `Write comprehensive ${framework} unit tests for:\n\n\`\`\`typescript\n${code}\n\`\`\`\n\nInclude:\n- Happy path tests\n- Edge cases\n- Error handling tests\n- Mock setup for external dependencies`,
    },
  }],
}));

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Prompts demo server running on stdio");
```

## Common mistakes

- **Confusing prompts with tools** — undefined Fix: Prompts are message templates invoked by users through the client UI. Tools are functions invoked by the AI model. Use prompts for structured interaction patterns, tools for actions.
- **Referencing non-existent resource URIs in prompts** — undefined Fix: If your prompt embeds a resource reference, make sure that resource is registered on the same server. The client resolves resource URIs before sending to the AI.
- **Making prompts too generic** — undefined Fix: Specific prompts with clear structure produce better AI responses. Instead of 'help with code', create 'review-code', 'debug-error', and 'generate-tests' separately.
- **Forgetting to describe prompt arguments** — undefined Fix: Add .describe() to every Zod parameter. Clients display these descriptions to users who need to fill in the arguments.

## Best practices

- Name prompts as action verbs: review-code, debug-error, generate-tests, explain-architecture
- Write clear descriptions for both the prompt and each argument — users see these in the client UI
- Use structured output instructions in your prompt text (numbered lists, specific sections to fill)
- Embed resource references when the prompt needs project context like schemas or configuration
- Keep prompts focused on one task — create multiple prompts instead of one mega-prompt
- Test all prompts with the MCP Inspector using various argument combinations
- Use default values for optional arguments to reduce friction for users
- Version your prompts by updating the prompt text when you improve the template

## Frequently asked questions

### What is the difference between prompts and tools in MCP?

Prompts are user-initiated message templates — the user picks a prompt from a menu and fills in arguments. Tools are AI-initiated function calls — the AI decides to call a tool based on the conversation. Prompts guide the conversation structure; tools perform actions.

### Can prompts call tools or read resources?

Prompts themselves do not call tools or read resources. However, prompts can embed resource references (type: 'resource') that the client resolves before sending. The AI response to a prompt may then decide to use tools based on the conversation.

### How does the user select which prompt to use?

The MCP client (like Claude Desktop) calls prompts/list to get available prompts and displays them to the user, often as a menu or slash-command list. The user selects a prompt, fills in required arguments, and the client calls prompts/get to retrieve the messages.

### Can I update prompts without restarting the server?

You can register new prompts at runtime and send a notifications/prompts/list_changed notification. However, existing prompt handlers cannot be modified without re-registering them with the same name.

### Should I use prompts or just tell users to type specific messages?

Prompts are better because they provide a structured interface with validated arguments, consistent formatting, and discoverability through the client UI. They reduce errors compared to asking users to remember exact message formats.

### Can prompts return assistant messages?

Yes. The messages array can include both user and assistant role messages. This is useful for few-shot prompting where you provide example assistant responses to guide the AI's output format. For complex prompt engineering strategies, the RapidDev team can help design optimal prompt structures.

---

Source: https://www.rapidevelopers.com/mcp-tutorial/how-to-define-prompts-in-mcp-server
© RapidDev — https://www.rapidevelopers.com/mcp-tutorial/how-to-define-prompts-in-mcp-server
