skip to content

In MCP, when should data be exposed as a resource rather than behind a tool?

level: seniorimportance: should knowfreq 55%

answer

  1. who decides, application or model
  2. would a user pick it from a list
  3. stable address, repeatable read
  4. search ranks; addresses do not
  5. hosts are not obliged to surface resources

basics

~20 s

Expose data as a resource when it is addressable by a stable URI, read without side effects, and something the application or user chooses to pull into context. Anything needing real parameters, a query, or a state change belongs behind a tool instead.

solid answer

~50 s

The dividing line in MCP is **who decides**. Resources are application-controlled: the server publishes URI-addressed data, and the host or the user picks what enters the model's context. Tools are model-controlled: the model decides to invoke them mid-loop. So the practical test for a resource is threefold — the data has a **stable identifier** a user could bookmark, reading it is **side-effect free and repeatable**, and it is plausible for a person to choose it from a picker. Files, documents, configuration, records by id and log files fit. Anything that needs several independent parameters, performs a search, ranks, writes, or triggers an action fails all three and belongs behind `tools/call`, which is why servers expose a `search` tool alongside a resource catalogue rather than encoding a query into a URI. A practical caveat: the protocol never obliges a host to surface resources to the model, so a server whose only useful data hides behind resources may look empty in a host that wires up only tools.

go deeper

for a junior

Be able to say that resources are data the application pulls in by URI while tools are actions the model calls, and give one clear example of each.

for a middle

Explain the three tests — stable address, side-effect-free repeatable read, pickable by a person — and why search fails them and belongs behind tools/call.

for a senior

Demonstrate the design call on a real server: which data gets URIs, where a search tool pairs with resource URIs for its hits, and how caching and consent differ between the two primitives.

for a principal

Own the surface strategy across hosts you do not control — deliberate duplication so the server works whether or not resources are surfaced, URI stability as a compatibility commitment, and keeping stateful workflows off the resource side entirely.

## The axis is control, not content MCP's server primitives are separated by **who initiates**, not by what kind of data they carry. Resources are application-controlled: the server publishes what is available, and the host — often via the user — decides what to pull in. Tools are model-controlled: the model chooses to call one during its work. The same underlying bytes can legitimately sit on either side; what changes is who gets to reach for them and when. That framing matters because the common instinct — "read goes in resources, write goes in tools" — is only half right. Plenty of side-effect-free reads still belong behind tools. ## Three tests for a resource **Is it addressable?** A resource is fetched by a URI and nothing else. If a caller needs to supply several independent inputs to get the right answer, the URI has stopped being an address. One or two identifying variables can be handled by a resource template; a bag of filters cannot. **Is the read repeatable and side-effect free?** Reading a resource must not change anything and should return the same content for the same URI until the underlying data changes. If reading consumes a token, advances a cursor, or triggers a job, it is an operation. **Could a person pick it?** Resources surface in host UI as pickers, @-mentions and attachments. If a human could look at a list and say "that one", the resource model fits. If choosing requires running a query first, it does not. A README, a config file, an order by id, yesterday's log, a wiki page: all three tests pass. Full-text search across a corpus: the first and third fail immediately. ## Why search is a tool Search is the canonical example. You could encode a query into a URI, but you would be smuggling an operation past the addressing model: results are ranked, they depend on parameters the user cannot enumerate, they change between calls, and nobody bookmarks them. Search wants a JSON input schema, which tools have and resources do not. The strong pattern for a large corpus is both: a `search` tool the model can call to find candidates, and resources — usually templated — for the documents it finds, so a hit becomes a stable URI the host can attach, cache and show. Search finds, resources address. ## The consent and safety difference Because a tool is model-controlled, hosts gate `tools/call` behind user approval and treat descriptions and annotations as untrusted hints; MCP's own principles put consent and tool safety on the host, since the protocol cannot enforce them. A resource read is initiated by the application, so the consent story is different in kind — the user already chose the data. Putting a destructive operation behind a resource URI to dodge that prompt would be an abuse of the primitive, and hosts are entitled to assume a read is harmless. ## Caching and repeat cost Resources are built for reuse. A `resources/list` result is cacheable and carries the required `ttlMs` and `cacheScope` in revision 2026-07-28, with `"private"` marking a catalogue that depends on the authorization presented. Stable URIs let a host cache content, deduplicate what it packs into context, and re-fetch cheaply. Tool call results are not cacheable in the same way — every invocation is a fresh execution. Data that is read repeatedly and changes slowly gains real efficiency from living behind a URI. ## The pragmatic caveat Nothing in the specification requires a host to expose resources to the model at all — that is precisely what application-controlled means. A server whose only capability is a rich resource catalogue can therefore look empty in a host that wires only tools into the model loop. Servers that care about working everywhere publish both: resources for hosts that surface them, plus a small fetch or search tool that reaches the same data. That is duplication with a purpose, not indecision. ## Stateless-era considerations Revision 2026-07-28 makes MCP explicitly stateless, and the resource set MUST NOT vary per connection, though it MAY vary by the authorization presented. So resources are a poor place to hide per-conversation state: a URI cannot mean "the file we were just discussing". Cross-call state now lives in explicit server-minted handles passed as ordinary tool arguments, which pushes stateful workflows firmly onto the tool side and leaves resources as what they should be — durable, addressable data. ## Common mistakes Encoding query parameters into resource URIs; exposing an action as a resource read; assuming a host will show the model everything in `resources/list`; and, in the other direction, hiding stable documents behind a `get_document` tool where a URI would have let the host cache, attach and display them.

  • Why not model full-text search as a resource template with the query as a variable?
    Because the result is not an address. Search results are ranked, depend on parameters a user cannot enumerate, change between identical calls, and are worthless to bookmark or cache. Templates exist for identifying variables like a path or an id. The right shape is a search tool with a JSON input schema that returns hits, each carrying a stable resource URI the host can then read, attach and cache.
  • Your server exposes documents only as resources and users report it does nothing. What happened?
    Resources are application-controlled, and nothing in the spec obliges a host to surface them to the model — some hosts wire only tools into the loop. The fix is to publish both: keep the resource catalogue for hosts that show it, and add a small fetch or search tool that reaches the same data, so the server is useful regardless of how the host is built.
  • How does statelessness in 2026-07-28 affect what you put behind a resource URI?
    It rules out conversational meaning. The revision makes MCP stateless and forbids the resource set from varying per connection, though it may vary by the authorization presented, so a URI can never mean "the file we were just discussing". Cross-call state lives in explicit server-minted handles passed as ordinary tool arguments, which keeps resources as durable addressable data and pushes stateful flows onto tools.

Resources are the shelves a user browses; tools are the staff you ask to go do something. Anything that requires asking rather than pointing is not a shelf.

saying these in an interview costs you the question

  • Encoding search queries and filters into resource URIs
  • Putting an action with side effects behind resources/read
  • Assuming the host feeds every listed resource to the model
  • Choosing resources purely because the operation is a read
  • Expecting a resource URI to carry conversational context

context