MCP resources versus tools: which one you actually wanted
•7 min read
Both can hand a model a block of JSON, so the choice looks cosmetic. It is not. A tool call is a decision the model makes with arguments it invents; a resource read is a fetch the client performs against an address it already holds.
Choose an MCP tool when the model should decide whether to fetch something and with what arguments. Choose an MCP resource when the client already knows the address of the thing and the user or the application decides to put it in context. Both can return the same JSON, which is why the choice looks cosmetic, but they differ in who initiates. A tool is invoked through tools/call with arguments the model produces. A resource is read through resources/read against a URI the client picked from a list.
What is the difference between a tool and a resource in MCP?
A tool and a resource in MCP are two different server primitives with two different wire shapes. A tool is described by a name and an input schema, and calling it means the model produced a set of arguments that satisfy that schema. A resource is described by a URI, an optional MIME type and an optional size, and reading it means the client asked for the contents of an address that already existed. The tool's identity is a verb with parameters; the resource's identity is a noun with a location.
That difference shows up in the discovery calls too. A client lists tools with tools/list and gets schemas it can turn into function definitions. It lists resources with resources/list, and separately lists resources/templates/list to get URI templates, which are parameterised addresses rather than parameterised actions. A server can offer both, and many non-trivial servers do, but a given piece of functionality belongs to one of them and putting it in the wrong place is felt immediately by whoever has to use it.
The same data can travel either path; the paths differ in who starts and how change is signalled.
Question
Tool
Resource
How is it identified
A name plus an input schema
A URI, with an optional MIME type and size
Who decides to invoke it
The model, by producing arguments
The client or the user, by choosing an address
Discovery call
tools/list
resources/list, plus resources/templates/list for templates
Invocation call
tools/call
resources/read
Can the client be notified of changes
Only that the list changed
That the list changed, and that one item changed
Why does the subscribe capability settle most of the argument?
The server capability declarations make the intended split explicit, and they give you a test you can apply without reading any prose. In the protocol's capability object, tools and prompts each declare a single optional flag for whether their list can change. Resources declare that same flag and one more: subscribe. A client that subscribes to a resource is told when that specific item changes and can read it again. There is no equivalent for a tool, because a tool has no contents to change — only a result, produced fresh on every call.
The server is the same in both. What differs is which layers the client implements: how it connects, how it discovers the authorisation server, which of the three capabilities it lists, and whether it reads the hints on each tool.
Market4 turns one release note into a changelog page, a blog post, a mail-out and a week of social posts — and then tells you which of them brought anyone back.
No card to start. Cancel from the settings screen, not from an email.
subscribeThe one capability flag resources declare that tools and prompts do not, which is why watchable state belongs behind a resource.
So the practical question is whether the thing you are exposing has an identity that persists between reads. A configuration file, a document, a schema, a log that grows, a record with a stable id — these have an address and a version, and a client can reasonably want to be told when they change. A search across a date range with filters has no persistent identity; it is a computation, and the answer is different every time because the question was different every time. The first belongs behind a resource. The second belongs behind a tool.
What happens when you put a query behind a resource?
Putting a query behind a resource forces the parameters into the URI, and that is where it starts to hurt. A URI template can carry a couple of path segments cleanly enough, but a report with a date range, a device filter, a country, a row limit and a sort order becomes an address nobody can read and no schema can validate. The model has to build that string by concatenation rather than by filling in a typed object, and a wrong value produces a failed read rather than a validation error that names the field.
The reverse mistake is quieter and more common. Putting a document behind a tool works fine, right up until the client wants to show the user what is in context, let them attach it manually, or refresh it when it changes. None of that is possible for a tool result, because a tool result is a transcript entry rather than an addressable thing. If you find yourself adding a get_thing_by_id tool whose only argument is an id, you have written a resource with extra steps.
Where do prompts fit, and why does Market4 use them instead of big tools?
Prompts are the third server primitive, and they cover the case the other two handle badly: a multi-step plan the user asks for by name. Market4's MCP server registers tools and prompts and no resources at all, and the reason for the prompts is written into the code. Wrapping a chain like read Search Console, then research on SerpApi, then generate art, then write the post into a single tool would produce a pipeline the assistant cannot step out of, and every paid search inside it would spend the customer's money on a decision the assistant never got to make. As a prompt it is a plan the assistant can deviate from, and one the user invoked deliberately. This is the same reasoning behind building an MCP server an assistant can drive rather than one it can only trigger.
0Resources registered by the Market4 MCP server, which exposes its reads as tools and its multi-step plans as prompts.
There is a registration order trap worth knowing if you add prompts to an existing server. The prompts capability is registered the first time a prompt is registered, and the SDK refuses to change declared capabilities once a transport has been attached. Registering prompts after connect therefore fails rather than silently doing nothing, so prompt registration has to happen while the server is still being built. The same ordering applies to any capability a server declares lazily.
How do you decide in practice?
Ask whether the thing has an address that outlives a single call. If yes, it leans resource.
Ask whether a client would sensibly want to be told when it changes. Only resources can answer that per item.
Ask whether the arguments have types worth validating. A schema is a tool's advantage and a URI's weakness.
Ask who should decide to pull it in. Model decides means tool; user or application decides means resource.
Ask whether the answer is computed or stored. A computation with parameters is a tool even when it returns a document.
If it is a plan with several steps and real spending in the middle, it is neither — it is a prompt.
One caveat applies to servers that mix the two. A tool result can carry a link to a resource, and such a link is not guaranteed to appear in the results of a resources/list call. A client that only trusts its own listing will not find that item, so a server handing out resource links from tool results should also list them if it wants them to be discoverable. This is the kind of detail that decides whether a server behaves the same in two different clients.
Can the same data be exposed as both a tool and a resource?
Yes, and sometimes that is right. A document can be listed as a resource so a user can attach it deliberately, and also be reachable through a search tool that returns matching passages. What should not happen is the same access pattern duplicated for no reason, because two paths to identical data means two things to keep in step and a model that picks arbitrarily between them. Expose the second path only when it serves a different initiator.
Do all MCP clients support resources?
Support is per client and per capability rather than universal, which is the practical reason many servers ship tools first. A client negotiates capabilities during initialisation, and a server that puts essential functionality behind resources will appear broken in a client that never lists them. Tools tend to have broader support because they map directly onto the function-calling interface models already use. Check what your target clients actually implement before choosing.
Should a read-only operation always be a resource?
No. Read-only describes the effect, not the initiator, and the initiator is what separates the two primitives. A search across a date range is read-only and is still a tool, because the model has to choose the range and the filters and there is no stable address for the answer. A configuration document is read-only and is a resource, because it has a URI and a client can sensibly ask to be told when it changes.
What is a resource template for?
A resource template describes a family of addresses rather than a single one, using a URI template with placeholders. It is listed through resources/templates/list, separately from concrete resources, so a client can offer the user a way to construct an address it was never handed directly. Templates suit collections with stable identifiers, such as a record per id. They are not a substitute for a typed input schema when the parameters are really query options.
How do prompts differ from both?
A prompt is a named, user-invoked template that returns messages rather than data. It is the right shape for a multi-step plan the assistant should be able to deviate from, which is exactly what a large tool cannot offer: a tool runs to completion on the server and returns one result. Prompts also suit chains containing paid steps, because the user chose to start the chain and the assistant can stop between steps.
A tool error that says "invalid input" ends the run. One that quotes the rejected value, lists the accepted set and names the next move lets the assistant repair the call itself. Four real messages from one codebase, read closely.
The consumer of an MCP tool result is a model with a fixed budget for reading. Paging for that reader is a different design from paging for a web page.