Overview

A resource is a URI-addressed piece of read-only content with a MIME type: a file, a document, a database row, a configuration value. Resources are application-driven: the host decides how to surface them (a picker, search, automatic inclusion), unlike tools, which the model calls. Use a resource for data the agent reads but never modifies, and a tool for anything with side effects.

Assign every resource a stable, self-evident URI

github://org/repo/issues/401
postgres://mydb/public/users/42
file:///project/src/main.rs

The scheme names the integration, the host the workspace or database, the path the object hierarchy. Avoid opaque IDs such as resource://a1b2c3d4 that need a lookup to interpret. Use https:// only when the client can fetch the content directly from the web without the server; use file:// for filesystem-like data (it need not map to a real disk) and a custom scheme otherwise. Custom schemes must follow RFC 3986. Expose parameterized families with resources/templates/list and RFC 6570 URI templates such as file:///{path}.

Describe resources so the model can route before reading

resources/list returns metadata only (URI, name, title, description, MIME type, size) and supports pagination. resources/read returns content, possibly several contents entries for one URI. Write descriptions that decide relevance without a fetch: “Open GitHub issue #401: authentication fails on mobile” beats “Issue data”. Optional annotations carry audience (user, assistant), priority (0.0 to 1.0), and lastModified so clients can filter and rank.

Cache with ttlMs and cacheScope

List and read results carry ttlMs, a freshness hint in milliseconds, and cacheScope ("public" or "private"), which controls whether shared intermediaries may cache. Set private for anything user-specific. Resource lists may vary by the caller’s authorization but must not vary per connection.

Subscribe only for data that changes during a task

Declare subscribe: true and listChanged in the resources capability only if you support them. Under the 2026-07-28 revision a client opts in through subscriptions/listen with the URIs in resourceSubscriptions; the server acknowledges, then streams notifications/resources/updated tagged with io.modelcontextprotocol/subscriptionId, and the client re-reads. The old resources/subscribe and resources/unsubscribe requests are replaced. Use it for a build status or ticket state so the agent does not poll with a tool. Static documents need no subscription.

A tool result that carries a long document occupies the context for the rest of the task. Return a resource_link content block (uri, name, mimeType) and let the client fetch it when needed. Links returned by tools are not guaranteed to appear in resources/list. This also lets an orchestrator hand a subagent a URI instead of duplicating the content; see multi-agent.

Handle errors and validate URIs

A missing resource is JSON-RPC error -32602 (Invalid Params); clients should also accept the old -32002. Never return an empty contents array for a missing resource, since it is ambiguous with an empty one. Validate every URI and sanitize paths to block directory traversal on file:// resources; see mcp-security.