Introduction
While tools let LLMs take actions, resources let them access data. MCP resources are read-only data sources identified by URIs — files, database records, API responses, or any structured data a server wants to expose. Reading resources is how your client retrieves context for the LLM: project files, configuration data, documentation, or real-time information that helps the LLM understand the problem it is solving.
Key Concepts
- client.readResource(): The method that fetches a resource's content from the server. It takes an object with a
urifield (a string identifying the resource). - Resource URIs: Resources are identified by URI strings like
file:///path/to/doc.md,postgres://db/users/schema, or custom schemes likeconfig://app/settings. The server defines the URI scheme. - Text vs Binary Content: Resource content can be text (returned in the
textfield) or binary (returned as base64-encoded data in theblobfield). ThemimeTypefield tells you the content format. - Resource Templates: Some servers expose parameterized URI templates (like
file:///{path}) that accept variable substitution, letting clients request specific resources dynamically.
Real World Context
Your AI code assistant needs to understand the project structure before suggesting changes. The MCP file system server exposes files as resources. Your client reads file:///project/package.json to understand dependencies, file:///project/tsconfig.json for TypeScript configuration, and file:///project/src/index.ts for the entry point. Instead of the LLM calling a tool to read each file (which consumes a tool-use turn), the application proactively reads key resources and injects them into the LLM's context.
Deep Dive
Basic Resource Reading
Reading a resource is straightforward:
typescriptconst resource = await client.readResource({ uri: "file:///project/README.md" });
The response contains a contents array. Each item in the array represents one piece of content from the resource:
typescriptfor (const content of resource.contents) { console.error(`URI: ${content.uri}`); console.error(`MIME type: ${content.mimeType}`); if (content.text) { console.error(`Text: ${content.text}`); } else if (content.blob) { console.error(`Binary data (base64): ${content.blob.length} chars`); } }
Most resources return a single content item, but the array structure allows a resource to include multiple parts (e.g., a database query result might include both the data and the schema).
Text Content
Text resources are the most common. The content includes a text field with the full text:
typescriptconst configResource = await client.readResource({ uri: "config://app/settings" }); const config = configResource.contents[0]; if (config.text) { // Parse as JSON if the MIME type indicates it if (config.mimeType === "application/json") { const settings = JSON.parse(config.text); console.error("App name:", settings.name); } else { console.error("Config:", config.text); } }
The mimeType field tells you how to interpret the text. Common MIME types include text/plain, text/markdown, application/json, and text/csv.
Binary Content
Binary resources (images, PDFs, compiled files) use the blob field with base64-encoded data:
typescriptconst imageResource = await client.readResource({ uri: "screenshots://latest" }); const image = imageResource.contents[0]; if (image.blob) { const buffer = Buffer.from(image.blob, "base64"); // Write to file, display, or pass to a multimodal LLM console.error(`Image size: ${buffer.length} bytes`); console.error(`Format: ${image.mimeType}`); // e.g., "image/png" }
Binary content is less common but important for applications that handle images, documents, or other non-text data.
Resource URI Schemes
MCP does not mandate a specific URI scheme. Servers define their own schemes based on the data they expose:
textScheme Example Server Type ────────────────────────────────────────────────────────────── file:// file:///project/src/app.ts File system postgres:// postgres://mydb/users Database https:// https://api.example.com/v1 API data config:// config://app/settings Configuration git:// git://repo/main/README.md Version control
The scheme helps you understand what kind of data to expect, but the server ultimately defines the URI format and what it returns.
Resource Templates
Some servers expose resource templates with parameterized URIs:
typescriptconst resourcesResult = await client.listResources(); // A server might expose templates like: // { uriTemplate: "file:///{path}", name: "Project file" } // { uriTemplate: "db://users/{id}", name: "User record" } // You fill in the parameters to read specific resources: const userResource = await client.readResource({ uri: "db://users/42" });
Templates follow RFC 6570 URI Template syntax. The client substitutes variables with actual values to construct the final URI.
Injecting Resources into LLM Context
A key pattern is reading resources and including them in the LLM's conversation context:
typescript// Read project context const [readme, config] = await Promise.all([ client.readResource({ uri: "file:///project/README.md" }), client.readResource({ uri: "file:///project/package.json" }) ]); const contextMessage = { role: "user" as const, content: `Here is the project context: ## README ${readme.contents[0].text} ## package.json ${config.contents[0].text} Now, please help me with: ${userQuestion}` };
By reading resources proactively and injecting them into the prompt, you give the LLM relevant context without consuming tool-use turns.
Common Pitfalls
- Assuming all content is text — Always check whether the content has a
textorblobfield. Binary resources will not have atextfield, and trying to read it will give you undefined. - Ignoring MIME types — A resource with
mimeType: "application/json"should be parsed as JSON, not treated as plain text. Use the MIME type to determine how to process the content. - Reading large resources without checking size — Some resources (like database tables or log files) can be very large. Check the resource metadata before reading if the server provides size information.
Best Practices
- Use resources for context, tools for actions — Read resources to build the LLM's context. Use tools when the LLM needs to perform an action. This separation keeps your application clean and predictable.
- Read resources in parallel — When you need multiple resources, use
Promise.allto fetch them concurrently. This reduces latency. - Check the MIME type before processing — Handle different content types appropriately: parse JSON, render Markdown, decode base64 for binary.
Summary
client.readResource({ uri })fetches a resource's content from the server.- Resources are identified by URIs with server-defined schemes (file://, postgres://, config://, etc.).
- Content can be text (in the
textfield) or binary (base64-encoded in theblobfield). - The
mimeTypefield indicates the content format — use it to determine how to process the data. - Resource templates allow parameterized URIs for dynamic resource access.
- Read resources proactively and inject them into LLM context for better responses.
Code Examples
// Read a resource and handle text vs binary content
const resource = await client.readResource({
uri: "file:///project/README.md"
});
for (const content of resource.contents) {
if (content.text) {
console.error(`Text (${content.mimeType}): ${content.text}`);
} else if (content.blob) {
const buffer = Buffer.from(content.blob, "base64");
console.error(`Binary (${content.mimeType}): ${buffer.length} bytes`);
}
}