Introduction
Single-message prompts cover many use cases, but the real power of MCP prompts emerges with advanced patterns: multi-message prompts for few-shot learning, embedded resources for context injection, dynamic prompt generation, and autocompletion for argument values. These patterns let you build sophisticated, reusable workflows.
Key Concepts
- Multi-message prompts use alternating
userandassistantmessages to create few-shot examples - Embedded resources inject resource content directly into prompt messages
- Dynamic prompts adapt their content based on runtime conditions
completable()wraps Zod schemas to provide autocompletion suggestions for prompt arguments- Few-shot prompts teach the LLM by example, not just by instruction
Real World Context
Advanced prompt patterns solve real workflow challenges:
- Few-shot code generation: show the LLM example input/output pairs before presenting the actual task
- Context-aware analysis: embed a database schema resource into a SQL review prompt
- Guided debugging: multi-turn prompts that walk through a systematic diagnostic process
- Autocompleted arguments: suggest valid project names, file paths, or topic lists as the user types
Deep Dive
Multi-message prompts use alternating user and assistant roles to provide few-shot examples. The LLM learns the expected pattern from the examples before receiving the actual task.
typescriptimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; server.registerPrompt( 'write-test', { title: 'Write Unit Test', description: 'Generate a unit test following project conventions', argsSchema: z.object({ functionCode: z.string().describe('The function to test') }) }, ({ functionCode }): GetPromptResult => ({ messages: [ { role: 'user', content: { type: 'text', text: 'Write a test for this function:\nfunction add(a, b) { return a + b; }' } }, { role: 'assistant', content: { type: 'text', text: 'describe("add", () => {\n it("adds two numbers", () => {\n expect(add(2, 3)).toBe(5);\n });\n});' } }, { role: 'user', content: { type: 'text', text: `Write a test for this function:\n${functionCode}` } } ] }) );
The first two messages (user example + assistant response) form a few-shot example. The third message presents the actual task. The LLM follows the pattern established by the example.
Autocompletion enhances the user experience by suggesting valid argument values. The completable() function wraps a Zod schema and provides a callback that returns suggestions.
typescriptimport { McpServer, completable } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; server.registerPrompt('explain', { title: 'Explain Concept', argsSchema: z.object({ topic: completable( z.string().describe('Topic'), value => ['closures', 'promises', 'generics'].filter(t => t.startsWith(value)) ) }) }, ({ topic }): GetPromptResult => ({ messages: [{ role: 'user', content: { type: 'text', text: `Explain ${topic}` } }] }));
The completable() function takes two arguments: the Zod schema for the field and a callback that receives the current input value and returns an array of matching suggestions. As the user types, the client calls the completion endpoint and displays filtered suggestions.
Dynamic prompts adapt their content based on runtime conditions.
typescriptserver.registerPrompt( 'daily-review', { title: 'Daily Code Review', description: 'Review recent changes with context-appropriate instructions', argsSchema: z.object({ diff: z.string().describe('Git diff to review') }) }, ({ diff }): GetPromptResult => { const isLargeDiff = diff.split('\n').length > 200; const instructions = isLargeDiff ? 'This is a large diff. Focus on architectural changes, breaking changes, and security issues. Skip style nits.' : 'Review this diff thoroughly. Check for bugs, style issues, performance problems, and security concerns.'; return { messages: [ { role: 'user', content: { type: 'text', text: `${instructions}\n\n${diff}` } } ] }; } );
The handler checks the size of the diff and adjusts the review instructions accordingly. This kind of runtime adaptation makes prompts smarter than static templates.
Common Pitfalls
- Too many few-shot examples: more than 2-3 examples can consume excessive context window space without proportional benefit
- Inconsistent message roles: few-shot examples must alternate user/assistant; two consecutive messages with the same role break the pattern
- Overly broad completions: returning thousands of completion suggestions slows down the client UI; filter and limit results
- Ignoring prompt length: large embedded resources or many messages can exceed context limits; be mindful of total token count
Best Practices
- Use 1-2 few-shot examples for most tasks — enough to establish the pattern without wasting context
- Keep completion callbacks fast — they are called on every keystroke; avoid expensive database queries
- Test multi-message prompts with different LLMs to ensure the few-shot pattern is followed consistently
- Document the expected output format in the prompt text, not just through examples
- Use dynamic prompts to handle edge cases (large inputs, missing optional data) gracefully
Summary
Advanced prompt patterns extend basic prompts with multi-message few-shot learning, autocompletion via completable(), and dynamic content generation. Multi-message prompts teach by example using alternating user/assistant roles. Autocompletion improves discoverability by suggesting valid argument values. Dynamic prompts adapt their instructions based on runtime conditions.
Code Examples
server.registerPrompt(
'write-test',
{
title: 'Write Unit Test',
description: 'Generate a unit test following project conventions',
argsSchema: z.object({ functionCode: z.string().describe('The function to test') })
},
({ functionCode }): GetPromptResult => ({
messages: [
{ role: 'user', content: { type: 'text', text: 'Write a test for: function add(a, b) { return a + b; }' } },
{ role: 'assistant', content: { type: 'text', text: 'describe("add", () => { it("adds two numbers", () => { expect(add(2, 3)).toBe(5); }); });' } },
{ role: 'user', content: { type: 'text', text: `Write a test for:\n${functionCode}` } }
]
})
);import { McpServer, completable } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
server.registerPrompt('explain', {
title: 'Explain Concept',
argsSchema: z.object({
topic: completable(
z.string().describe('Topic'),
value => ['closures', 'promises', 'generics'].filter(t => t.startsWith(value))
)
})
}, ({ topic }): GetPromptResult => ({
messages: [{ role: 'user', content: { type: 'text', text: `Explain ${topic}` } }]
}));