Resources: context the application controls
The second primitive answers a different question. Tools are the model doing; resources are the model knowing — read-only context a server offers, which the application (not the model) chooses to load. That control-model difference, easy to skim past, is the whole design.
What a resource is
A resource is a piece of readable content behind a URI. Paper Trail exposes its returns policy:
@mcp.resource("policy://returns")
def returns_policy() -> str:
"""The store's returns policy, verbatim."""
return ("Returns accepted within 14 days of delivery. Books must be unread. "
"Refunds to the original payment method in 5-7 working days. "
"Damaged-in-transit claims need a photo within 48 hours.")Discovery and a read, captured:
== resources ==
policy://returns: The store's returns policy, verbatim.
== resources/read policy://returns ==
Returns accepted within 14 days of delivery. Books must be unread. Refunds to th…The URI scheme is yours to invent — policy://, file://, db://orders — it is an identifier, not a network address. Content can be text or binary (each read returns its mimeType), so a resource can as easily be a PDF, an image, or a CSV as a paragraph.
Beyond fixed URIs, resource templates describe families: register orders://{order_id}/history and you have exposed a history document for every order without enumerating any. Discovery lists the template; the client fills the blank at read time.
Application-controlled, and why
The model cannot pull a resource into context on its own. The host decides — usually by letting the user attach one (in Claude Desktop, resources appear in the attachment picker, exactly like a file). This is deliberate asymmetry: *what the model can do is the model's call, behind a gate; what the model gets to see is the human's.* Context is attention and tokens, both scarce; context is also exposure, since anything loaded can be quoted, summarised, or leaked into an answer. MCP puts that budget in the hands of the party accountable for it.
The practical consequence: a resource is pull, not push. Nothing auto-loads, and nothing auto-refreshes — if the policy document changes after the user attached it, the conversation holds the old text until it is re-attached (unless the server declared subscribe support and the host uses it, which in practice most do not). Design accordingly: resources suit content that is stable across a conversation.
Resource or tool? The decision that actually recurs
You will face this weekly: the returns policy could just as well be a get_returns_policy() tool. The framework's own heuristic is the control model — who should decide this enters the conversation? A reference document a user consciously brings in: resource. A lookup the model should perform mid-reasoning, on parameters it chose: tool. Side effects or computation on demand: always a tool. Content addressed by an id the model will discover mid-conversation ("check order PT-1041's history"): a tool in practice, even if a template exists — because the model cannot read resources unprompted, and a capability the model cannot reach is not part of its repertoire.
There is honest overlap, and hosts' uneven resource support pushes real-world servers toward tools. When you do wrap content in a tool for reachability, keep it read-only and say so in the description — the control model you give up at the protocol layer you can partly restore in the contract.
Try this: for Paper Trail, decide — resource or tool? — for each of: the store's shipping-rate card, the current contents of a customer's cart, and last month's bestseller list. All three are "just data"; the control model gives three different answers. (Rates: resource — stable reference a human attaches. Cart: tool — the model needs it mid-conversation, addressed by customer. Bestsellers: defensible either way, and saying why is the skill.)