Prompts
A prompt is a message template a connected client invokes by name. Clients surface prompts directly to people — slash commands, menu entries — where tools are picked by the model.
Register a prompt
registerPrompt takes a name, a config, and a callback that returns the messages. argsSchema is a Zod object schema describing the arguments.
import { McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
const server = new McpServer({ name: 'review', version: '1.0.0' });
server.registerPrompt(
'review-code',
{
title: 'Code Review',
description: 'Review code for best practices and potential issues',
argsSchema: z.object({
code: z.string().describe('The code to review')
})
},
({ code }) => ({
messages: [
{
role: 'user' as const,
content: { type: 'text' as const, text: `Review this code:\n\n${code}` }
}
]
})
);prompts/list now advertises review-code with one required argument, code.
TIP
.describe() survives the conversion: prompts/list carries The code to review as the code argument's description — what a client shows next to the input field.
Coming from v1?
registerPrompt replaces prompt() — run the codemod, then see the upgrade guide.
Every call on this page comes from an in-memory Client connected to the server above — Test a server shows that wiring — and an MCP host does the same when someone picks the prompt. Fetch it with getPrompt.
const result = await client.getPrompt({ name: 'review-code', arguments: { code: 'let x = 1' } });
console.log(result.messages);The callback's messages come back with the argument filled in:
[
{
role: 'user',
content: { type: 'text', text: 'Review this code:\n\nlet x = 1' }
}
]Validate the arguments with the schema
Drop the required argument.
import type { ProtocolError } from '@modelcontextprotocol/client';
try {
await client.getPrompt({ name: 'review-code', arguments: {} });
} catch (error) {
const { code, message } = error as ProtocolError;
console.log(code, message);
}The SDK rejects the request before your callback runs:
-32602 Invalid arguments for prompt review-code: code: Invalid input: expected string, received undefinedA failed prompt validation is a protocol error — getPrompt rejects with a ProtocolError carrying code -32602 (Invalid params). A tool argument rejection comes back as an isError: true result instead.
From that one schema the SDK derives the argument list prompts/list advertises, validates prompts/get arguments before your callback runs, and infers the callback's argument types.
Build the messages
The callback returns { messages }. Each message names a role — 'user' or 'assistant' — and one content block; add an assistant message after the user message to seed how the reply starts.
server.registerPrompt(
'explain-error',
{
description: 'Explain a compiler error and suggest the smallest fix',
argsSchema: z.object({ error: z.string() })
},
({ error }) => ({
messages: [
{
role: 'user' as const,
content: { type: 'text' as const, text: `Explain this compiler error:\n\n${error}` }
},
{
role: 'assistant' as const,
content: { type: 'text' as const, text: 'The one-line cause:' }
}
]
})
);The host hands the messages to the model in order, so the trailing assistant message becomes the start of its reply. content accepts the same union a tool result does: text, image, audio, resource_link, and resource.
Embed a resource in a message
type: 'resource' puts a resource's contents inside a message. Register the resource as usual — see Resources — and embed the same uri, mimeType, and text in the prompt.
const styleGuide = '- Prefer const over let.\n- No single-letter identifiers.';
server.registerResource('style-guide', 'doc://style-guide', { mimeType: 'text/markdown' }, async uri => ({
contents: [{ uri: uri.href, mimeType: 'text/markdown', text: styleGuide }]
}));
server.registerPrompt(
'review-against-style',
{
description: 'Review code against the team style guide',
argsSchema: z.object({ code: z.string() })
},
({ code }) => ({
messages: [
{
role: 'user' as const,
content: {
type: 'resource' as const,
resource: { uri: 'doc://style-guide', mimeType: 'text/markdown', text: styleGuide }
}
},
{
role: 'user' as const,
content: { type: 'text' as const, text: `Review this code against the style guide:\n\n${code}` }
}
]
})
);prompts/get returns the style guide inline as the first message, so the client never makes a second resources/read round trip:
{
role: 'user',
content: {
type: 'resource',
resource: {
uri: 'doc://style-guide',
mimeType: 'text/markdown',
text: '- Prefer const over let.\n- No single-letter identifiers.'
}
}
}The uri tells the client which registered resource the embedded copy came from.
Offer argument autocompletion
Wrap an argument with completable() to suggest values while a client fills in the form.
import { completable } from '@modelcontextprotocol/server';
server.registerPrompt(
'translate',
{
description: 'Translate a snippet into another language',
argsSchema: z.object({
language: completable(z.string(), value =>
['typescript', 'python', 'rust', 'go'].filter(language => language.startsWith(value))
),
code: z.string()
})
},
({ language, code }) => ({
messages: [{ role: 'user' as const, content: { type: 'text' as const, text: `Translate to ${language}:\n\n${code}` } }]
})
);The client sends completion/complete with the characters typed so far; the SDK runs your function and returns the matching values. Completion covers the request flow and context-aware suggestions.
Recap
registerPrompt(name, config, callback)registers a prompt; clients discover it throughprompts/list.argsSchemais one Zod object: the advertised argument list, argument validation, and the callback's argument types.- Arguments that fail the schema reject
prompts/getwith a-32602protocol error; the callback never runs. - The callback returns
{ messages }; each message names aroleand onecontentblock. - A message can embed a registered resource's contents with
type: 'resource'. completable()adds per-argument autocompletion.