| title | Prompts |
|---|---|
| author | jeffhandley |
| description | How to implement and consume MCP prompts that return text, images, and embedded resources. |
| uid | prompts |
MCP prompts allow servers to expose reusable prompt templates to clients. Prompts provide a way for servers to define structured messages that can be parameterized and composed into conversations.
This document covers implementing prompts on the server, consuming them from the client, rich content types, and change notifications.
Prompts can be defined in several ways:
- Using the xref:ModelContextProtocol.Server.McpServerPromptAttribute attribute on methods within a class marked with xref:ModelContextProtocol.Server.McpServerPromptTypeAttribute
- Using xref:ModelContextProtocol.Server.McpServerPrompt.Create* factory methods from a delegate,
MethodInfo, orAIFunction - Deriving from xref:ModelContextProtocol.Server.McpServerPrompt or xref:ModelContextProtocol.Server.DelegatingMcpServerPrompt
- Implementing a custom xref:ModelContextProtocol.Server.McpRequestHandler`2 via xref:ModelContextProtocol.Server.McpServerHandlers
- Implementing a low-level xref:ModelContextProtocol.Server.McpRequestFilter`2
The attribute-based approach is the most common and is shown throughout this document. Prompts can return ChatMessage instances for simple text/image content, or xref:ModelContextProtocol.Protocol.PromptMessage instances when protocol-specific content types like xref:ModelContextProtocol.Protocol.EmbeddedResourceBlock are needed.
A prompt without arguments:
[McpServerPromptType]
public class MyPrompts
{
[McpServerPrompt, Description("A simple greeting prompt")]
public static ChatMessage Greeting()
=> new(ChatRole.User, "Hello! How can you help me today?");
}Prompts can accept parameters to customize the generated messages. Use [Description] attributes to document each parameter. In addition to prompt arguments, methods can accept special parameter types that are resolved automatically: xref:ModelContextProtocol.Server.McpServer, IProgress<ProgressNotificationValue>, ClaimsPrincipal, and any service registered through dependency injection.
[McpServerPromptType]
public class CodePrompts
{
[McpServerPrompt, Description("Generates a code review prompt")]
public static IEnumerable<ChatMessage> CodeReview(
[Description("The programming language")] string language,
[Description("The code to review")] string code) =>
[
new(ChatRole.User, $"Please review the following {language} code:\n\n```{language}\n{code}\n```"),
new(ChatRole.Assistant, "I'll review the code for correctness, style, and potential improvements.")
];
}
}Register prompt types when building the server:
builder.Services.AddMcpServer()
.WithHttpTransport(o => o.Stateless = true)
.WithPrompts<MyPrompts>()
.WithPrompts<CodePrompts>();Prompt messages can contain more than just text. For text and image content, use ChatMessage from Microsoft.Extensions.AI. DataContent is automatically mapped to the appropriate MCP content block: image MIME types become xref:ModelContextProtocol.Protocol.ImageContentBlock, audio MIME types become xref:ModelContextProtocol.Protocol.AudioContentBlock, and all other MIME types become xref:ModelContextProtocol.Protocol.EmbeddedResourceBlock with binary resource contents. For text embedded resources specifically, use xref:ModelContextProtocol.Protocol.PromptMessage directly.
Include images in prompts using DataContent:
[McpServerPrompt, Description("A prompt that includes an image for analysis")]
public static IEnumerable<ChatMessage> AnalyzeImage(
[Description("Instructions for the analysis")] string instructions)
{
byte[] imageBytes = LoadSampleImage();
return
[
new ChatMessage(ChatRole.User,
[
new TextContent($"Please analyze this image: {instructions}"),
new DataContent(imageBytes, "image/png")
])
];
}For protocol-specific content types like xref:ModelContextProtocol.Protocol.EmbeddedResourceBlock, use xref:ModelContextProtocol.Protocol.PromptMessage instead of ChatMessage. PromptMessage has a Role property and a single Content property of type xref:ModelContextProtocol.Protocol.ContentBlock:
[McpServerPrompt, Description("A prompt that includes a document resource")]
public static IEnumerable<PromptMessage> ReviewDocument(
[Description("The document ID to review")] string documentId)
{
string content = LoadDocument(documentId); // application logic to load by ID
return
[
new PromptMessage
{
Role = Role.User,
Content = new TextContentBlock { Text = "Please review the following document:" }
},
new PromptMessage
{
Role = Role.User,
Content = new EmbeddedResourceBlock
{
Resource = new TextResourceContents
{
Uri = $"docs://documents/{documentId}",
MimeType = "text/plain",
Text = content
}
}
}
];
}For binary resources, use the xref:ModelContextProtocol.Protocol.BlobResourceContents.FromBytes* factory method:
new PromptMessage
{
Role = Role.User,
Content = new EmbeddedResourceBlock
{
Resource = BlobResourceContents.FromBytes(pdfBytes, "data://report.pdf", "application/pdf")
}
}Clients can discover and use prompts through xref:ModelContextProtocol.Client.McpClient.
IList<McpClientPrompt> prompts = await client.ListPromptsAsync();
foreach (var prompt in prompts)
{
Console.WriteLine($"{prompt.Name}: {prompt.Description}");
// Show available arguments
if (prompt.ProtocolPrompt.Arguments is { Count: > 0 })
{
foreach (var arg in prompt.ProtocolPrompt.Arguments)
{
var required = arg.Required == true ? " (required)" : "";
Console.WriteLine($" - {arg.Name}: {arg.Description}{required}");
}
}
}GetPromptResult result = await client.GetPromptAsync(
"code_review",
new Dictionary<string, object?>
{
["language"] = "csharp",
["code"] = "public static int Add(int a, int b) => a + b;"
});
// Process the returned messages (PromptMessage has a single Content block)
foreach (var message in result.Messages)
{
Console.WriteLine($"[{message.Role}]:");
switch (message.Content)
{
case TextContentBlock text:
Console.WriteLine($" {text.Text}");
break;
case ImageContentBlock image:
Console.WriteLine($" [image] {image.MimeType}");
break;
case EmbeddedResourceBlock resource:
Console.WriteLine($" Resource: {resource.Resource.Uri}");
break;
}
}Servers can dynamically add, remove, or modify prompts at runtime and notify connected clients. These are unsolicited notifications, so they require stateful mode or stdio — stateless servers cannot send unsolicited notifications.
// After adding or removing prompts dynamically
await server.SendNotificationAsync(
NotificationMethods.PromptListChangedNotification,
new PromptListChangedNotificationParams());mcpClient.RegisterNotificationHandler(
NotificationMethods.PromptListChangedNotification,
async (notification, cancellationToken) =>
{
var updatedPrompts = await mcpClient.ListPromptsAsync(cancellationToken: cancellationToken);
Console.WriteLine($"Prompt list updated. {updatedPrompts.Count} prompts available.");
});