Overview

Resources are MCP’s primitive for read-only context. A resource has a URI, a MIME type, and content. The model or client fetches it by URI without triggering side effects. Resources are the right choice for files, documents, database rows, and configuration values the agent needs to read but not modify. Tools are for actions; resources are for data. Keeping this separation short-circuits tool sprawl and gives the model a stable reference scheme that works across sessions.

Assign every resource a stable, predictable URI

A resource URI is the stable address the client and model use to refer to a piece of data. Design URIs so the pattern is self-evident from a single example.

github://org/repo/issues/401
notion://workspace/page/abc123
postgres://mydb/public/users/42
fs://project/src/main.ts

Use the scheme to identify the integration, the host segment for workspace or database, and the path for the object hierarchy. Avoid opaque identifiers like resource://a1b2c3d4 that require a separate lookup to interpret.

Stable URIs let the model refer to a resource in one turn and retrieve it in a later turn without re-resolving the address. They also appear in structured logs as clean correlation keys.

Use resources for read-only context; use tools for writes and side effects

The distinction matters for two reasons. First, resources do not require confirmation in most client permission models. The agent can fetch a resource freely; it must confirm before calling a write tool. Second, resources are cacheable. A client can store a fetched resource and reuse it across turns without calling the server again.

Apply the rule mechanically: if the operation reads without modifying state, model it as a resource. If it creates, updates, deletes, sends, or triggers anything, model it as a tool. A get_issue call that only reads should be a resource at github://org/repo/issues/401, not a tool. See mcp-tool-design for the complementary tool rules.

Implement subscriptions for data that changes during a session

The resource subscription pattern lets the server push notifications/resources/updated when a resource changes. The client re-fetches the resource in response. This is the correct pattern for live data: a build status, a ticket state, a monitoring metric.

{ "jsonrpc": "2.0", "method": "notifications/resources/updated",
  "params": { "uri": "github://org/repo/actions/run/12345" } }

Subscriptions eliminate polling. Without them, the agent must call a tool repeatedly to detect state changes, which burns context and API quota. With them, the server drives updates and the agent reacts. Only implement subscriptions for resources that genuinely change; static documentation pages do not need them.

Load resource content lazily; list metadata eagerly

resources/list returns metadata: URI, name, description, MIME type. It does not return content. resources/read fetches content for a single URI. This separation is intentional: the client can show the user a list of available resources without paying the cost of loading all of them.

Design resource metadata to be self-sufficient for routing decisions. A description of "Open GitHub issue #401: Authentication fails on mobile" lets the model decide whether to fetch the resource before it pays for the content. A description of "Issue data" does not.

Lazy loading matters most when a server exposes hundreds of resources. A project filesystem server may list thousands of files; the model should be able to pick the two it needs without fetching all of them.

Prefer resources over tool returns for large, stable data

When a tool returns a large blob of text (a file, a long document, a database export), the content occupies the context window for the rest of the session. A resource reference occupies a single URI string and is fetched on demand.

Pattern: return a resource URI from the tool call, let the client decide whether to fetch it. A create_document tool that returns { "uri": "notion://workspace/page/abc123" } rather than the full document content gives the client control over when to load it.

This pattern is especially useful in multi-agent pipelines where an orchestrator needs to pass context to a subagent without duplicating a large payload. The subagent fetches the resource from the URI when it needs the content.