What is PromptMessage?
PromptMessage is the atomic unit of a Prompt response in MCP. When a client calls `prompts/get`, the server returns a `GetPromptResult` whose `messages` array is composed entirely of PromptMessage objects. Each object pairs a `role` — either `"user"` or `"assistant"` — with a `content` block typed as a `ContentBlock` union (TextContent, ImageContent, or EmbeddedResource). The host then injects this ordered message list directly into the active conversation history before the next LLM turn begins. The design mirrors the familiar chat-completion message schema intentionally: a developer who already understands `{ role, content }` tuples from the OpenAI or Anthropic APIs needs almost no ramp-up time. Restricting `role` to exactly two string literals keeps prompt templates predictable and avoids the ambiguity of system-role injection, which is handled separately through the optional `_meta` / system fields at the `GetPromptResult` level rather than inside individual messages. Both fields are required — there is no concept of a partial or role-only message in this type. This means server authors cannot return a placeholder or streaming-style delta; every PromptMessage in the array must be fully formed before the response is sent. This constraint makes Prompt responses inherently non-streaming, distinguishing them from Tool call results where incremental progress notifications are common.
When to use
Inside GetPromptResult to deliver the prompt's content.
When NOT to use
Outside of prompt results — for tool output use ContentBlocks directly.
Notes
Role enum is strictly two values
Only 'user' and 'assistant' are valid role strings. Servers that attempt to inject a 'system' message via PromptMessage will fail schema validation at the transport layer. System-level context should instead be placed in the top-level `system` field of `GetPromptResult` if the MCP server SDK you are using exposes it.
ContentBlock union — know which variants land
The content field accepts TextContent, ImageContent, or EmbeddedResource. ImageContent carries a base64-encoded data URI, so large images bloat the entire prompts/get response synchronously. In production, prefer EmbeddedResource references for anything beyond small thumbnails to avoid hitting JSON payload size limits in transports like stdio.
Ordering mirrors final conversation injection
The host injects the messages array in index order immediately before the next user turn. A common pitfall is returning a final 'assistant' role message that ends mid-thought: many LLM providers will then complete that partial assistant turn rather than responding to the user's query, producing unexpected output. Always verify the logical role sequence ends on a 'user' message unless a partial assistant prefix is intentional.
No partial or delta messages supported
Unlike tool-call progress notifications, there is no streaming or chunked delivery for Prompt messages — the entire array must be assembled on the server before the response is sent. For prompts that require expensive retrieval or generation to populate, consider caching the result or using a Tool instead, which supports incremental notifications via `notifications/progress`.
SDK validation differences across languages
The TypeScript SDK performs runtime Zod validation on both `role` and `content` before returning, throwing a typed error on malformed content blocks. The Python SDK (as of v1.x) does lighter structural checking and may silently drop unknown content block variants. When writing cross-SDK servers, emit only the three canonical content block types and test against both SDK validators before deploying.
Fields
| Field | Type | Required | Purpose |
|---|---|---|---|
| role | 'user' | 'assistant' | yes | Who is speaking. |
| content | ContentBlock | yes | Text, image, audio, embedded resource, or link. |
Examples
User prompt message
{
"role": "user",
"content": { "type": "text", "text": "Plan a 5-day trip to Tokyo." }
}
Common mistakes
❌ Returning plain strings instead of ContentBlocks
✅ Always wrap text in { type: 'text', text: '...' }.