In MCP, when should data be exposed as a resource rather than behind a tool?
answer
- who decides, application or model
- would a user pick it from a list
- stable address, repeatable read
- search ranks; addresses do not
- hosts are not obliged to surface resources
basics
~20 sExpose 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 sThe 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
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.
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.
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.
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