Welcome to Chapter 5! In the previous chapter, MCP Client Integration, we successfully connected to a server and requested a resource.
However, we ended on a cliffhanger: What happens if the resource is an image, a PDF, or a compiled program?
LLMs (Large Language Models) are text processing engines. They have a "Context Window"โa limited amount of memory for the conversation.
If you try to read a small text file, the AI handles it easily. But binary data (like an image) is sent over the network as a Base64 string. This is a massive block of random-looking text that encodes the file.
The Problem:
If you feed a 5MB image converted to Base64 text into the AI's chat window, you will instantly fill up its memory. The AI will crash, forget instructions, or become incredibly slow.
The Solution:
Instead of giving the AI the actual file data, we save the file to the user's hard drive and give the AI a Reference (a file path).
The Use Case:
A user asks: "Analyze this logo.png."
1. The Tool fetches the image data (Base64).
2. The Tool intercepts it.
3. The Tool saves it to a temporary folder.
4. The Tool tells the AI: "I have downloaded the image to
/tmp/logo.png."
This is the Content Persistence Strategy.
When an MCP server sends binary data, it sends it in a field called blob. This is the raw data encoded as text.
We must catch this data before it reaches the AI. We decode it from Base64 back into raw binary bytes (0s and 1s).
This simply means "saving to disk." We write the binary bytes to a file so they persist (exist) outside the chat memory.
We modify the output. We remove the heavy blob data and replace it with a lightweight blobSavedTo path.
We implement this logic inside the call function of our ReadMcpResourceTool. We loop through the results received from the server and process them.
We iterate through the contents. If it is text, we return it immediately.
// inside result.contents.map loop...
if ('text' in c) {
// Pass text directly to the AI
return { uri: c.uri, mimeType: c.mimeType, text: c.text }
}
Explanation: If the text property exists, our job is done. The AI can read text directly.
If it's not text, we check if it is a valid blob.
// Check if 'blob' exists and is a string
if (!('blob' in c) || typeof c.blob !== 'string') {
// If neither text nor blob, return metadata only
return { uri: c.uri, mimeType: c.mimeType }
}
Explanation: This is a safety check. If the server sent empty data, we skip the heavy processing.
Here is the core magic. We take the Base64 string and save it.
// Generate a unique ID for the file
const persistId = `mcp-resource-${Date.now()}-${i}`
// Helper function to decode and save
const persisted = await persistBinaryContent(
Buffer.from(c.blob, 'base64'), // Decode Base64 to Binary
c.mimeType,
persistId,
)
Explanation:
Buffer.from(..., 'base64'): Transforms the text string back into raw image/file data.persistBinaryContent: A helper utility that handles writing the file to a temp folder.Finally, we return a clean object to the AI.
return {
uri: c.uri,
mimeType: c.mimeType,
// The path where we saved it
blobSavedTo: persisted.filepath,
// A friendly message for the AI
text: `[Resource saved to ${persisted.filepath}]`,
}
Explanation: Notice we populate the text field with a message describing the action. The AI reads this and understands: "Ah, I didn't get the image data, but I know where it is stored."
Let's visualize how data transforms from a "Network Packet" to a "Local File."
You might wonder what persistBinaryContent does. While we won't write the file system code here, conceptually it performs these steps:
mimeType (e.g., image/png) to decide if the file should end in .png or .jpg.fs.writeFile to physically put the bytes on the hard drive.Sometimes saving a file fails (e.g., disk full). We handle this gracefully:
if ('error' in persisted) {
return {
uri: c.uri,
mimeType: c.mimeType,
text: `Binary content could not be saved: ${persisted.error}`,
}
}
Explanation: Instead of crashing the tool, we return a text message explaining the failure. The AI can then report this error to the user.
Without this strategy, ReadMcpResourceTool would only be useful for code files and text documents. By implementing Persistence, we make our tool capable of handling:
We have turned a text-only tool into a universal file handler, all while keeping our AI lightweight and responsive.
In this chapter, you learned the Content Persistence Strategy:
Now we have a file path in our result data. But showing a file path like /tmp/mcp-resource-123.png to a user isn't very pretty. We want to actually show them the image or a nice clickable link in their chat interface.
Next Chapter: User Interface Rendering
Generated by Code IQ