This is the documentation for the v2 beta — looking for the v1 documentation?
Skip to content

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.

ts
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.

ts
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.

ts
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 undefined

A 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.

ts
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.

ts
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.

ts
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 through prompts/list.
  • argsSchema is one Zod object: the advertised argument list, argument validation, and the callback's argument types.
  • Arguments that fail the schema reject prompts/get with a -32602 protocol error; the callback never runs.
  • The callback returns { messages }; each message names a role and one content block.
  • A message can embed a registered resource's contents with type: 'resource'.
  • completable() adds per-argument autocompletion.