Resources are the read surface of MCP. They let a host fetch stable, readable context without overloading tools with content delivery responsibilities.
Good resource design starts with URI strategy, then scales into templates, pagination, metadata, and update notifications.
A resource should feel like a durable address for content. If a host wants to fetch it tomorrow or reference it in a tool result, the identifier should still make sense.
A resource typically includes a URI, name, optional title and description, MIME type, and sometimes size metadata. That combination tells the host both what the thing is and how expensive it may be to read or include in context.
The server decides how the URI scheme maps to actual storage. The spec does not force business-domain URIs to look like file paths, which means you can design identifiers that reflect your domain rather than your backend internals.
Not every resource can be listed exhaustively up front. Dynamic domains such as user profiles, reports by date, or repository files often need URI templates. MCP resource templates let the server describe a family of resources without pre-registering every instance.
Templates should still be bounded. A server that exposes users://{userId}/profile should validate the parameter and enforce authorization before reading actual content.
Resources frequently represent content that is too large to dump into every model request. Hosts need enough metadata to decide whether to fetch, summarize, chunk, or ignore the content.
That is why size and MIME type matter. A 2 KB markdown policy and a 40 MB binary artifact should not be treated the same way by a host, even if both are technically resources.
A resource URI is more than a pointer. It is a stable contract that tells the host how to request context safely and tells the server what authorization, pagination, freshness, and formatting rules apply. Poor resource design often looks convenient at first, such as exposing arbitrary backend URLs or raw database identifiers, but it becomes hard to secure and hard for the model to understand.
Use predictable URI families that match user concepts. A policy server might expose `policy://handbook/{section}`, `policy://rule/{rule_id}`, and `policy://search/{query}`. A repository server might expose `repo://{workspace}/file/{path}` only inside approved roots. Each URI pattern should have clear ownership, allowed parameters, error behavior, and permission checks.
Versioning matters when resources become long-lived references in prompts, traces, or saved workflows. If content can change, include metadata such as last modified time, version, etag, or source revision. The model should know whether it is reading a current document, a historical snapshot, or a summarized representation.
Resources often fail in production because they are too large. A host may discover a resource successfully, but reading the entire object can blow through context limits, increase cost, or expose unnecessary data. Treat large resources as collections, windows, or summaries rather than single giant blobs.
A good server offers progressive access. First return metadata and a compact preview. Then allow the host to request a page, range, section, or specific version. If the user asks a broad question, combine resources with a search tool that returns ranked resource references rather than embedding every possible document in one response.
Resource discovery is where the host learns what context exists. It should help the model find useful information without revealing sensitive names, internal topology, or unauthorized records. A resource that cannot be read by the current user often should not be discoverable by that user either.
Use resource templates for predictable families and concrete resources for important known objects. Templates make dynamic access possible, but every template parameter must be validated. If a template accepts a file path, normalize it and check containment. If it accepts an ID, check tenant and object-level permissions before returning content.
Resources should describe content type and expected size. The host can make better decisions when it knows whether a resource is text, JSON, binary metadata, a document section, or a large collection. Size and freshness metadata also help the host avoid stuffing inappropriate content into the model context.
Discovery should be fast. If listing resources requires expensive backend scans, introduce indexes, pagination, filters, or search tools. Slow discovery makes every host feel unreliable even when individual reads work.
Reading a resource should return bounded, meaningful context. If the resource is too large, return a section, range, summary, or page instead of dumping everything. The model needs enough evidence to answer, not every byte the backend can provide.
Provenance is the difference between useful context and anonymous text. Include the source URI, version, document title, section, timestamp, and any relevant access context. Later, when an answer cites a fact, the system can trace it back to the exact resource read.
Resources can contain adversarial instructions. A policy document, issue comment, README, or webpage may tell the model to ignore rules. The host and prompt should label resource content as evidence, not instructions. Server-side permissions and host-side policy remain in charge.
For production systems, audit sensitive reads. Even read-only context can be confidential. Track who read which resource, through which host, for which run, and whether the content was used in a final answer.
Pick one resource family and review its full lifecycle: discovery, read, pagination, authorization, caching, citation, and deletion. This exercise catches the difference between exposing data and exposing safe context. The model needs useful evidence, not unrestricted backend access.
Check what a user can learn from discovery alone. Resource names, IDs, titles, and counts can reveal sensitive information even before content is read. If discovery is sensitive, filter it with the same seriousness as read access.
Then check what happens when content is large, stale, missing, or unauthorized. The server should return bounded results and clear status without leaking hidden objects or internal implementation details.
server.registerResource(
'password-policy',
'policy://security/password-rotation',
{
title: 'Password Rotation Policy',
description: 'Official security policy for password rotation.',
mimeType: 'text/markdown',
},
async (uri) => ({
contents: [
{
uri: uri.href,
text: await loadPolicyMarkdown('password-rotation'),
},
],
})
);
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
server.registerResource(
'user-profile',
new ResourceTemplate('users://{userId}/profile', { list: undefined }),
{
title: 'User Profile',
mimeType: 'application/json',
},
async (uri, { userId }) => ({
contents: [
{
uri: uri.href,
text: JSON.stringify(await loadApprovedUserProfile(userId)),
},
],
})
);
Yes. Resources can return binary blobs, typically base64-encoded, with the correct MIME type.
Not necessarily. Dynamic template-backed resources may be discoverable through templates rather than a full concrete list.
Explore 500+ free tutorials across 20+ languages and frameworks.