Tools, Resources, and Prompts
Prompt Templates in MCP
Prompts are the least-used of the three capabilities, and the one that most often should have been used. They are conversation starters your server hands to the user, not to the model — which makes them the right home for the instructions your team keeps retyping slightly differently every time.
Why Prompts?
The honest case for them: somewhere in your organisation there is a paragraph of instructions that one person writes well and everyone else writes badly. A prompt template is where that paragraph goes to stop being tribal knowledge.
Prompts are useful for:
- Standardizing interactions that are currently copy-pasted from a wiki
- Giving domain-specific starting points to people who don't know how to ask
- Encapsulating instructions too long for anyone to retype accurately
Anatomy of a prompt definition
Four parts, and the split between definition and generation is the part people get wrong first:
What a Prompt definition is made of
- 01 · name
The stable identifier the host calls. Treat it as an API surface — renaming it breaks saved workflows
code_review - 02 · description
What the user sees in the host's prompt picker. Written for a human choosing from a menu, not for the model
Review code for best practices and issues - 03 · arguments
The blanks the user fills in. Mark only what you genuinely cannot default as required
language (required) · focus (optional) - 04 · messages
Generated on demand by get_prompt, with the arguments interpolated. This is the part the model actually reads
role: user → "Review the following {language} code…"
Click any slot to reveal an example.
The first three are declared once in list_prompts — they are a menu entry. The fourth is built fresh in get_prompt every time someone picks it. Keeping that separation clear is why a prompt can be cheap to list and expensive to build.
Defining Prompts
from mcp.types import Prompt, PromptArgument, PromptMessage, TextContent
@server.list_prompts()
async def list_prompts():
return [
Prompt(
name="code_review",
description="Review code for best practices and issues",
arguments=[
PromptArgument(
name="language",
description="Programming language",
required=True
),
PromptArgument(
name="focus",
description="What to focus on (security, performance, style)",
required=False
)
]
)
]
Generating Prompt Content
When a prompt is requested, return the actual messages:
@server.get_prompt()
async def get_prompt(name: str, arguments: dict):
if name == "code_review":
language = arguments["language"]
focus = arguments.get("focus", "general best practices")
return [
PromptMessage(
role="user",
content=TextContent(
type="text",
text=f"""Please review the following {language} code.
Focus on: {focus}
Provide specific suggestions with line numbers where applicable.
Rate the code from 1-10 and explain your rating."""
)
)
]
Multi-Turn Prompts
Prompts can include multiple messages for complex workflows:
@server.get_prompt()
async def get_prompt(name: str, arguments: dict):
if name == "debug_session":
return [
PromptMessage(
role="user",
content=TextContent(
type="text",
text="I'm going to share an error. Help me debug it step by step."
)
),
PromptMessage(
role="assistant",
content=TextContent(
type="text",
text="I'll help you debug. Please share the error message and relevant code."
)
),
PromptMessage(
role="user",
content=TextContent(
type="text",
text=f"Error: {arguments['error']}\nCode: {arguments['code']}"
)
)
]
Dynamic Prompt Arguments
Fetch argument options dynamically:
Prompt(
name="query_database",
description="Query a specific table",
arguments=[
PromptArgument(
name="table",
description="Table name",
required=True,
# Could be validated against actual schema
)
]
)
Next: one server that offers all three capabilities without them tripping over each other. :::
Sign in to rate