Introduction
When you retrieve a prompt from an MCP server, you receive a messages array containing structured content. These messages must be rendered into the format your LLM expects. This lesson covers how to interpret prompt responses, handle multi-turn prompts, combine server prompts with application context, and process embedded resources in prompt messages.
Key Concepts
- Prompt Response: The object returned by
client.getPrompt(). It contains amessagesarray where each message has arole(user or assistant) andcontent(text or resource). - Multi-Turn Prompts: Prompts that return multiple messages with alternating roles, setting up a conversation pattern for the LLM to follow.
- Embedded Resources: Prompt messages can include resource content (referenced by URI) inline, allowing prompts to inject server-side data directly into the conversation.
- Content Types: Prompt message content can be
text(plain text) orresource(an embedded MCP resource with URI and content).
Real World Context
A data analysis assistant uses a prompt called analyze-dataset. The server returns a multi-turn prompt: the first message (user role) describes the analysis task with embedded resource content from a CSV file, and the second message (assistant role) demonstrates the expected output format. Your client renders these messages alongside the user's actual question, giving the LLM both the data and the expected behavior pattern.
Deep Dive
A prompt response has a simple structure. The messages array contains objects with role and content fields.
Here is how to render a basic prompt response into messages for an LLM API:
typescriptconst result = await client.getPrompt({ name: "analyze-data", arguments: { dataset: "sales-q4" } }); const llmMessages = result.messages.map(msg => { if (msg.content.type === 'text') { return { role: msg.role, content: msg.content.text }; } if (msg.content.type === 'resource') { const resource = msg.content.resource; return { role: msg.role, content: `[Resource: ${resource.uri}]\n${resource.text ?? '(binary content)'}` }; } return { role: msg.role, content: '' }; });
The key decision is how to handle each content type. Text content maps directly to the LLM message format. Resource content needs to be serialized, typically by including the resource URI as context and inlining the text content.
Multi-turn prompts use alternating roles to establish a conversation pattern. The server might return a user message followed by an assistant message to demonstrate the expected behavior.
The following example shows rendering a multi-turn prompt and appending the user's actual question:
typescriptconst result = await client.getPrompt({ name: "code-review", arguments: { language: "python" } }); // Prompt might return: // [0] { role: "user", content: { type: "text", text: "Review this Python code..." } } // [1] { role: "assistant", content: { type: "text", text: "I'll analyze for..." } } const promptMessages = result.messages.map(msg => ({ role: msg.role, content: msg.content.type === 'text' ? msg.content.text : '' })); // Combine prompt messages with the user's actual request const conversation = [ ...promptMessages, { role: 'user', content: userCode } ]; const response = await llm.chat(conversation);
The prompt messages act as a preamble that sets up the LLM's behavior, followed by the user's actual input.
When prompt messages contain embedded resources, the content type is resource instead of text. The resource object includes a uri, optional mimeType, and either text (for text resources) or blob (for binary, base64-encoded).
Here is a complete rendering function that handles all content types:
typescriptfunction renderPromptMessage( msg: { role: string; content: { type: string; text?: string; resource?: any } } ): { role: string; content: string } { let content = ''; if (msg.content.type === 'text') { content = msg.content.text ?? ''; } else if (msg.content.type === 'resource') { const res = msg.content.resource; if (res.text) { content = `--- Resource: ${res.uri} ---\n${res.text}`; } else if (res.blob) { content = `[Binary resource: ${res.uri}, type: ${res.mimeType ?? 'unknown'}]`; } } return { role: msg.role, content }; } const rendered = result.messages.map(renderPromptMessage);
This function handles text content directly, inlines text-based resources with their URI as context, and provides a placeholder description for binary resources.
Common Pitfalls
- Ignoring the resource content type: Not all prompt messages are plain text. Failing to handle
resourcetype content means losing important context the server intended to provide. - Breaking role alternation: Some LLM APIs require strict user/assistant alternation. If the prompt returns consecutive messages with the same role, you may need to merge them.
- Discarding prompt metadata: The prompt response may include a
descriptionfield at the top level. This can be useful for logging or UI display but should not be injected into the conversation.
Best Practices
- Build a reusable rendering function that maps MCP prompt messages to your LLM's expected format, handling both text and resource content types.
- When combining server prompts with user messages, place the prompt messages first to establish context and behavior patterns.
- Log the rendered prompt messages during development so you can inspect exactly what the LLM receives.
Summary
- Prompt responses contain a
messagesarray withroleandcontentfields. - Content can be
text(plain text) orresource(embedded MCP resource with URI and content). - Multi-turn prompts use alternating user/assistant roles to establish conversation patterns.
- Build a rendering function that handles both content types and converts them to your LLM's message format.
- Combine rendered prompt messages with user input by placing prompts first as context.
Code Examples
const result = await client.getPrompt({
name: "code-review",
arguments: { language: "python" }
});
const promptMessages = result.messages.map(msg => ({
role: msg.role,
content: msg.content.type === 'text' ? msg.content.text : ''
}));
const conversation = [
...promptMessages,
{ role: 'user', content: userCode }
];
const response = await llm.chat(conversation);function renderPromptMessage(msg) {
let content = '';
if (msg.content.type === 'text') {
content = msg.content.text ?? '';
} else if (msg.content.type === 'resource') {
const res = msg.content.resource;
if (res.text) {
content = `--- Resource: ${res.uri} ---\n${res.text}`;
} else if (res.blob) {
content = `[Binary resource: ${res.uri}, type: ${res.mimeType ?? 'unknown'}]`;
}
}
return { role: msg.role, content };
}