MCP tools vs resources: which one to expose
An MCP server can expose three kinds of thing, and the specification separates them by who controls each one rather than by what each one contains:
- Tools are functions the model invokes when it decides it needs to act.
- Resources are addressable content that the host application, or the user, places into the model’s context.
- Prompts are templated messages and workflows that a person selects deliberately.
The practical rule follows from that. If it changes something, or takes an argument the model has to choose, it is a tool. If it is content you are making available to be read, it is a resource. If it is a workflow a person picks, it is a prompt.
Tools and resources are the pair that overlap, because both can be phrased as “get me X”. What separates them is who decides when it happens.
Who decides when each one is used?
That is the real dividing line.
| Chosen by | Typical trigger | |
|---|---|---|
| Tool | The model | It decides mid-task that it needs to act |
| Resource | The application or the user | Something is attached to the context |
| Prompt | The user | They pick it from a menu |
A tool is invoked because the model concluded it should be. That is why tool descriptions matter so much: where the host exposes them, they are the entire basis for that decision, and they are read by a model rather than a person.
A resource is not chosen by the model in that sense. It is content, identified by a URI, that the host can read and place into context. The host decides what to include, which is also why resources are a poor fit for anything with side effects.
A prompt is user-facing. It shows up as something a person selects, often in a menu or a slash command, and expands into a templated interaction.
Why is nearly everything published as a tool?
Two things push in the same direction, and only one of them is a design decision.
The compatibility reason: tool support is near-universal in practice. Almost every MCP client implements tools, while resource and prompt support is more varied. If you expose a resource and your users’ client ignores resources, your capability is invisible. Shipping get_document as a tool reaches the widest set of clients, subject as always to protocol-version compatibility and whatever the client is authorised to see.
The other reason is that a tool is the default shape most SDK examples start from. A tool called read_config that takes no arguments and returns a file is doing a resource’s job under a verb.
The cost of leaning on tools for everything is real, though indirect. Tool descriptions have to reach the model for it to choose between them, so a server exposing thirty read-only tools gives the model thirty things to weigh and, depending on how the host handles exposure and caching, can consume context repeatedly. Model tool-selection accuracy degrades as the list grows. Resources do not compete for that attention, because the model is not picking from them.
So the trade is a straightforward one: use resources for content when your target clients support them, and fall back to a read-only tool where they do not. The cost lands on the length of the tool list rather than on any single entry in it.
How do you decide, in practice?
Three questions, in order:
- Does it have side effects, or take parameters the model must choose? Tool.
- Is it addressable content that the host might want to place into context? Resource. Files, documents, records, schemas, logs.
- Is it a workflow a person triggers deliberately? Prompt.
Two worked examples:
- A server for a ticketing system.
create_ticketandtransition_ticketare tools: they act, and the model chooses the arguments. The text of ticketABC-123is a resource: it is content, addressable by URI, safe to read. “Summarise my open tickets” is a prompt: a user picks it. - A server for a codebase.
run_testsis a tool. A source file is a resource. “Review this file for security issues” is a prompt.
The ticketing example splits a single system across all three, which is common. A server that exposes only tools may have nothing that fits the other two, but it is worth checking that the content and workflow cases were considered rather than skipped.
Does the split affect a trust score?
It affects what we can assess.
We check schema quality on the tools a server declares, because a tool with a vague description or a loose schema is one a model will misuse. That check has something to work with only when the surface is actually declared. We also record primitive coverage, since a server declaring nothing at all is a different proposition from one declaring a clear surface.
One caveat applies to every declared primitive: a declaration is a claim, not a capability. We can see that a tool exists and read its schema. We cannot verify it does what its description says. The specification draws that line for one part of a declaration: clients MUST treat tool annotations as untrusted unless they come from a trusted server. It makes no equivalent demand about descriptions, so treating a description as unverified is our own position rather than a rule we can cite. Either way, a clean schema is a floor and not an assurance.
Where to go next
- What a client does with all this: MCP client vs server.
- How tools reach the model: MCP vs function calling.
- Seeing your own surface as a client sees it: point the MCP Inspector at your server and read the three tabs.
Tools act, resources inform, prompts are chosen by people. When a case is genuinely ambiguous, ask who decides that it happens.