MCP prompts: slash commands your server ships

Of the three primitives a Model Context Protocol (MCP) server offers (tools, resources and prompts), prompts are the one the user picks. I like that split: your server writes them, hosts usually show them as slash commands or menu entries, and the user decides when to run one. So a prompt is a good way to package a workflow you know well, such as reviewing a pull request against your team’s standards or drafting an incident postmortem.
This is part 11 of my series on what an MCP server does under the 2026-07-28 specification. Part 10 covered resources, which the application controls. The facts are as I read them in October 2026.
In brief
- A prompt is a message template your server writes and the user invokes. “User-controlled” means the user decides when it’s used, not who writes it.
prompts/listreturns the templates;prompts/getrenders one into messages in theuserorassistantrole, for the host to put into the conversation.completion/completesuggests argument values as the user types, andprompts/getmay answerinput_requiredto ask for a missing detail before it renders.- Implementations MUST validate prompt inputs, which land in text the model treats as instructions. My advice goes further: keep free text between clear markers and design arguments as identifiers.
- In the Python software development kit (SDK), one decorator gives you the prompt and its arguments, but
MCPServeranswers a missing required argument or an unknown prompt with -32603 where the spec asks for -32602.
Who decides when a prompt runs
The prompts specification doesn’t require any particular interface, but slash commands are the common pattern. Here is how hosts invoke a prompt, as of October 2026:
| Host | How a prompt is invoked |
|---|---|
| Claude (web, Desktop, mobile) | Chosen from the connector’s menu in the chat |
| Claude Code | /server:prompt (MCP) in the / menu, or typed as /mcp__server__prompt arg1 arg2 |
| VS Code (GitHub Copilot) | /<server>.<prompt> in the chat input |
| Gemini CLI | Slash commands, with named (--arg="value") or positional arguments |
| Cursor | The / prompt picker in the chat |
| ChatGPT | Not documented: OpenAI’s docs don’t describe MCP prompt support, and a gateway vendor’s docs say ChatGPT doesn’t currently consume arbitrary prompts from a remote server |
That sets a prompt apart from server instructions: a host that supports them gives them to the model when it connects, and nobody picks them. A prompt arrives only when the user runs it.
How a prompt travels: list, complete, get
A server that offers prompts declares the capability, with listChanged if it will announce changes:
{ "capabilities": { "prompts": { "listChanged": true } } }
prompts/list is paginated and cacheable, like the other lists (part 6 covers both). Each prompt has a name and may have a title, a description, icons and arguments, each argument with a name, a description and required:
{
"name": "code_review",
"title": "Request code review",
"description": "Review code quality and suggest improvements",
"arguments": [{ "name": "code", "description": "The code to review", "required": true }]
}
prompts/get takes a name and arguments, and returns the rendered messages:
{
"resultType": "complete",
"description": "Code review prompt",
"messages": [
{ "role": "user", "content": { "type": "text", "text": "Please review this Python code:\ndef hello():\n print('world')" } }
]
}
Here is the whole path in a host with slash commands, from the listing to the large language model (LLM):
Three things deserve a word:
- Completion.
completion/completelets a server suggest values for prompt arguments, and for resource-template arguments, as the user types. Suggested repository names, environments, customer IDs and date ranges make slash commands much easier to use. Declare thecompletionscapability to support it. - Asking for input.
prompts/getis one of the three methods allowed to returninput_required, withtools/callandresources/read. A prompt can ask for a missing detail before it renders, through the Multi Round-Trip Requests (MRTR) flow from part 2: thealtblock above. - Change notifications. Servers that declared
listChangedSHOULD sendnotifications/prompts/list_changedto clients listening withpromptsListChanged: trueonsubscriptions/listen. The client then callsprompts/listagain.
What a message can hold
Prompt messages take the user or assistant role, so a prompt can carry few-shot examples or start an assistant turn. The content can be:
| Type | Notes |
|---|---|
text | The common case |
image, audio | Base64 data with a valid Multipurpose Internet Mail Extensions (MIME) type |
resource_link | A Uniform Resource Identifier (URI) the client fetches, with resource annotations, as in part 10 |
resource (embedded) | Server-managed content inlined into the message (URI, MIME type, text or blob) |
All content types support annotations (audience, priority, lastModified).
Rules, and arguments the model can’t misread
The spec’s rules for prompts:
- The prompt list MUST NOT vary per connection, but MAY vary by authorization, so you can show only the prompts the caller’s scopes allow.
- Errors: -32602 for an unknown prompt or a missing required argument, -32603 for internal errors.
- Servers SHOULD validate arguments, and clients SHOULD handle pagination.
- Implementations MUST validate prompt inputs and outputs to prevent injection attacks or unauthorized access to resources.
The spec only asks you to validate. My advice goes further, because the injection risk is concrete: user-supplied arguments are interpolated into text the model treats as instructions.
- Put arguments in clearly delimited blocks (“the code to review is between the markers below”), and cap their length. Never splice them into the instruction sentences themselves.
- Design arguments as identifiers, such as a repository name or a ticket ID, not free text. Claude Code splits slash-command arguments on whitespace, so each argument must be a single token there.
A code review prompt in Python
With the official Python SDK, mcp 2.3.0, a prompt is a decorated function. This is the review prompt from my guide in Python, with the title from its listing and one addition: an optional repo argument for the diagram’s completion step.
from typing import Annotated
from pydantic import Field
from mcp import MCPError
from mcp.server import MCPServer
from mcp.types import INVALID_PARAMS
mcp = MCPServer("review-server")
REPOS = ["web-app", "web-api"]
@mcp.prompt(name="code_review", title="Request code review")
def code_review(
code: Annotated[str, Field(description="The code to review")],
repo: Annotated[str | None, Field(description="Repository name")] = None,
) -> str:
"""Review code against team standards"""
if repo is not None and repo not in REPOS:
raise MCPError(INVALID_PARAMS, f"Unknown repository: {repo}")
header = f"Repository: {repo}\n" if repo else ""
return f"""\
Review the code between the markers for correctness, security and style.
Report findings as a table with severity, line, issue and fix.
{header}<<<CODE
{code}
CODE>>>"""
The parameters become the prompt’s arguments, with their descriptions and required flags; the docstring becomes its description, and the returned string becomes one user message. repo is the shape that travels well: one token, checked against a list, with completion to help the user type it. code is free text, so it sits between the markers, and Claude Code would split it on whitespace. To stay short, the example leaves out a length cap.
Completion is one handler for the whole server; registering it makes the SDK declare the completions capability:
from mcp.types import Completion, PromptReference
@mcp.completion()
async def complete(ref, argument, context) -> Completion | None:
if isinstance(ref, PromptReference) and argument.name == "repo":
return Completion(values=[r for r in REPOS if r.startswith(argument.value)])
return None
The in-memory client, in the same file, lists the prompt, completes “web”, gets it and tries three bad requests:
import anyio
from mcp import Client
async def main() -> None:
async with Client(mcp) as client: # 2026-07-28 by default
listed = await client.list_prompts()
print(listed.prompts[0].model_dump_json(by_alias=True, exclude_none=True, indent=2))
ref = PromptReference(name="code_review")
done = await client.complete(ref, {"name": "repo", "value": "web"})
print(done.completion.values)
code = "def hello():\n print('world')"
got = await client.get_prompt("code_review", {"code": code, "repo": "web-app"})
print(got.messages[0].content.text)
for name, args in [("code_review", {}), ("no_such_prompt", {"code": code}),
("code_review", {"code": code, "repo": "other"})]:
try:
await client.get_prompt(name, args)
except MCPError as e:
print(e.error.code, e.error.message)
anyio.run(main)
{
"name": "code_review",
"title": "Request code review",
"description": "Review code against team standards",
"arguments": [
{
"name": "code",
"description": "The code to review",
"required": true
},
{
"name": "repo",
"description": "Repository name",
"required": false
}
]
}
['web-app', 'web-api']
Review the code between the markers for correctness, security and style.
Report findings as a table with severity, line, issue and fix.
Repository: web-app
<<<CODE
def hello():
print('world')
CODE>>>
-32603 Internal server error
-32603 Internal server error
-32602 Unknown repository: other
The last three lines are the errors. For the first two the spec asks for -32602 (invalid params), but MCPServer in 2.3.0 answers both with -32603 (internal error). A fix for the unknown-prompt case, pull request #1872, was still open on 9 October 2026. For a missing argument, you can make the parameter optional and raise MCPError with INVALID_PARAMS yourself, as the repo check does. The client then gets -32602, but prompts/list shows the argument as required: false. An unknown prompt still gets -32603 in 2.3.0, because none of your code runs.
The full prompts/list result carried the SDK’s defaults, ttlMs: 0 and cacheScope: "private", and the prompts/get result carried resultType: "complete".
Method and caveats
- Built from my guide to MCP servers and written against the 2026-07-28 specification.
- How each host invokes prompts is as of October 2026.
- The Python samples ran against
mcp2.3.0 on 9 October 2026, with the SDK’s in-memory client, and printed the output shown. Therepoargument, its check and its completion handler are my additions to the sample in my guide. - Where OpenAI’s documentation and a gateway vendor’s are more precise than my notes (on prompts in ChatGPT), this article follows them and names the source.
Series What an MCP server actually does in 2026 Part 11 of 35
- What an MCP server does in 2026: much more than a list of tools
- MCP goes stateless: what changed in the 2026-07-28 specification
- server/discover: the one method every MCP server must implement
- Server instructions: the paragraph every model reads first
- Stateless MCP requests: _meta, resultType and explicit handles
- Caching hints and pagination in MCP
- MCP transports in 2026: stdio and Streamable HTTP
- Designing an MCP tool: the definition and the result
- MCP tool errors, and tools that models use well
- MCP resources: context the application decides to use
- MCP prompts: slash commands your server ships
- Tool, resource or prompt? Choosing the right MCP primitive