Tutorials Logic, IN info@tutorialslogic.com

MCP Resources: URI Design, Templates, Pagination, and Resource Scale

MCP Resources

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.

Resource Anatomy

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.

  • Use domain-driven URI schemes such as policy://, repo://, report://, or ticket://
  • Include MIME type whenever the client benefits from rendering hints
  • Treat resource URIs as public contract surfaces inside the protocol

Resource Templates

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.

  • Templates are ideal for dynamic but structurally predictable resources
  • Template parameters need the same validation discipline as tool parameters
  • Dynamic resources should not bypass audit and authorization logic

Context Scale

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.

  • Use resources for context, not for uncontrolled data exfiltration
  • Paginate large lists of resources
  • Prefer resource links from tools when the host should fetch on demand

URI Contracts

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.

  • Keep URI schemes domain-specific and readable.
  • Avoid exposing arbitrary filesystem paths or backend URLs directly.
  • Validate template parameters before reading data.
  • Attach metadata for freshness, size, owner, and source version.
  • Return clear no-access and not-found errors without leaking hidden resource names.

Large Content

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.

  • Prefer metadata-first discovery for large collections.
  • Use pagination or byte/section ranges for long documents.
  • Provide summaries as separate resources when they are generated and versioned.
  • Keep raw content, summaries, and search results distinct.
  • Audit reads for sensitive resource families.

Safe Discovery

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.

  • Filter discovery by permission and tenant.
  • Validate template parameters before lookup.
  • Expose content type, freshness, and size metadata.
  • Use pagination or search for large collections.
  • Avoid leaking hidden resource names in errors.

Provenance and Bounds

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.

  • Return excerpts, pages, or sections for large resources.
  • Attach source URI and version metadata.
  • Label resource content as untrusted evidence.
  • Audit reads for sensitive resource families.
  • Test oversized and malformed backend responses.

Resource Review

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.

  • Review discovery leakage separately from read leakage.
  • Preserve provenance for citations.
  • Use pagination and summaries for large objects.
  • Test unauthorized, stale, and missing resources.

Resource Lifecycle

A resource lifecycle includes `resources/list`, optional template discovery, `resources/read`, and possibly subscription notifications. Concrete resources have URIs; templates describe parameterized URI families. Normalize and validate every expanded URI, because a syntactically valid template argument can still escape a tenant, repository, or filesystem boundary.

Subscriptions are useful only when the client negotiated support and the server can identify meaningful updates. After `resources/subscribe`, a server may send updated notifications for that URI; the client then decides whether to read again. Notifications should not carry an unlimited document body or bypass the normal read authorization check.

Distinguish embedded resource content from a resource link. Embedding supplies bounded content in the current result, while a link gives the client a URI it may resolve under its own policy. Preserve MIME type, provenance, audience, and modification metadata where they help the host render or cite evidence correctly.

  • Authorize list visibility, template expansion, read access, and subscription separately.
  • Validate canonical URIs after decoding and template substitution.
  • Use pagination and bounded reads for large collections.
  • Recheck access when a subscribed resource changes.
  • Preserve source identity when resource content becomes model context.

Resource Lifecycle Examples

Static Resource Registration

Static Resource Registration
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'),
      },
    ],
  })
);
  • The URI is business-domain specific, not tied to a filesystem path.
  • The resource remains read-only even if the backing store is complex.

Resource Template for Dynamic Profiles

Resource Template for Dynamic Profiles
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)),
      },
    ],
  })
);
  • Template parameters should still be validated and authorized in the loader path.
  • The returned content is text JSON, which many hosts can display or summarize easily.
Before you move on

Resource Contract Review

4 checks
  • Design URIs as stable protocol identifiers.
  • Expose MIME type and size metadata where useful.
  • Use templates for dynamic families, not ad hoc string parsing everywhere.
  • Make large content fetchable on demand instead of always embedding it.

Resource Questions

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.

Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.