What is SamplingMessage?
SamplingMessage is the atomic unit of conversation history passed to a host LLM during an MCP sampling request. It mirrors the role/content envelope found in OpenAI-compatible and Anthropic chat APIs, giving MCP servers a familiar structure when they need the client to perform an inference call on their behalf. Each message carries exactly one role — either "user" or "assistant" — and one content block, making the schema intentionally minimal and easy to validate. Within the MCP message flow, SamplingMessage instances appear inside a CreateMessageRequest as the messages array. The server constructs this array to represent the conversation so far, the client (typically Claude Desktop or a similar host) prepends its own system prompt and appends any model-preference hints, then forwards the assembled context to the underlying LLM. This separation means the server never has direct access to the client's system prompt, which is a deliberate privacy boundary in the protocol design. The content block type (SamplingMessageContentBlock) is a discriminated union covering TextContent, ImageContent, AudioContent, ToolUseContent, and ToolResultContent. The tool-bearing variants enable agentic loops where a server drives multi-turn tool-call cycles entirely through the sampling primitive. AudioContent was added in a later revision of the spec, so older SDK versions may not deserialize it gracefully — always check SDK release notes before relying on audio payloads in production.
When to use
Inside CreateMessageRequest.params.messages.
When NOT to use
Outside sampling — use PromptMessage in prompt results.
Notes
Single content block per message
Unlike the Anthropic Messages API, where a message's content field accepts an array of blocks, SamplingMessage allows exactly one SamplingMessageContentBlock per message. If you need to interleave text and an image in a single turn, you must split them into consecutive messages with the same role, or pack them into a single TextContent string. Violating this constraint causes validation failures in strict SDK implementations.
ToolUseContent enables agentic loops
Servers that need multi-turn tool-call cycles can alternate ToolUseContent (role: assistant) and ToolResultContent (role: user) messages inside the same sampling request. This lets a single CreateMessageRequest encode an arbitrarily deep reasoning trace without the server needing its own inference stack. Keep these arrays short in practice — most hosts impose a token budget on the full messages array.
Image and audio payload size limits
ImageContent and AudioContent embed data as base64 strings, which means large media blobs balloon the JSON payload sent over the transport. SSE and stdio transports both have practical frame-size limits; audio above roughly 30 seconds often exceeds what default configurations can handle. Prefer passing a URI reference when the host supports it, and document your server's media constraints clearly.
Role ordering is not enforced by the schema
The SamplingMessage schema does not mandate strict user/assistant alternation, but many LLM backends will reject or misinterpret sequences with consecutive same-role messages. Always validate your messages array before passing it in a CreateMessageRequest, especially when constructing history from tool-loop replays where two assistant turns can appear back-to-back.
SDK deserialization of newer content types
AudioContent was introduced after the initial sampling specification stabilized, so TypeScript SDK versions below 1.1 and Python SDK versions below 1.2 will throw on unknown discriminant values rather than falling back gracefully. Pin your MCP SDK version and set an explicit protocol version in your server's initialization handshake to avoid silent deserialization errors when clients and servers are on mismatched releases.
Fields
| Field | Type | Required | Purpose |
|---|---|---|---|
| role | 'user' | 'assistant' | yes | Who is speaking. |
| content | SamplingMessageContentBlock | yes | Text/image/audio/tool block. |
Examples
User message
{ "role": "user", "content": { "type": "text", "text": "Hello" } }
Common mistakes
❌ Using role:'system'
✅ Use systemPrompt at the request level — messages are user/assistant only.