Part 19 of 22

MCP tools vs. resources vs. prompts: when to use each

LLM Mart · Sep 18, 2026 · 5 views 195 listing impressions
MCP tools vs. resources vs. prompts: when to use each

An MCP server can expose three kinds of capability, and they differ in who decides when they are used. Tools are model-controlled: the model invokes them to perform operations. Resources are application-driven: the host decides what context to pull in. Prompts are user-controlled: a person picks one, usually from a menu.

Pick by control model first. Most confusing MCP servers are servers that expressed everything as tools because tools were the primitive the author understood.

The three primitives at a glance

Tools Resources Prompts
Who invokes The model The host application The user
Purpose Perform an operation Supply readable context Supply a reusable message template
Identified by A name A URI A name
Core methods tools/list, tools/call resources/list, resources/read prompts/list, prompts/get
Side effects Expected and allowed Should be none None
Typical UI An approval prompt A context picker or automatic inclusion A slash command
Cost of getting it wrong Unapproved actions Context bloat Feature nobody discovers

If MCP itself is new, start with what an MCP server is and come back to this decision once the protocol shape is familiar.

Tools: model-controlled operations

A tool is a function the model can decide to call. That is the whole distinction, and everything else follows from it.

Because the model chooses, the description is the interface. tools/list returns each tool's name, optional title, description, and JSON Schema for its input, and those fields are all the model has when deciding. The specification also asks servers to return tools in a deterministic order, so clients can cache the list reliably and prompt caching stays effective.

Because the model chooses, a human belongs in the loop. The specification is explicit that applications should make clear which tools are exposed, indicate when they are invoked, and offer confirmation so a person can deny an invocation.

Use a tool when the operation:

  • changes something outside the conversation;
  • needs arguments the model must reason about;
  • is worth interrupting a person to approve.
// tools/call
{ "name": "create_invoice", "arguments": { "customer_id": "c_812", "amount_cents": 4900 } }

Two design habits pay for themselves. Annotate behaviour with readOnlyHint, destructiveHint, and idempotentHint so hosts can auto-approve safe tools and gate dangerous ones. And return actionable failures as tool results with isError: true rather than protocol errors, because clients pass those to the model so it can correct itself — an out-of-range date, a malformed identifier, a business-rule rejection.

Resources: application-driven context

A resource is data identified by a URI that a client can read. Files, database schemas, API responses, configuration, documentation.

Resources are application-driven: the host decides how to incorporate them. It might show a picker so the user selects them, let the user search and filter, or include them automatically by heuristic. The protocol does not mandate any of these.

Use a resource when the thing is:

  • content to be read rather than an action to be taken;
  • addressable by a stable identifier;
  • something a person or an application should be able to choose deliberately.
// resources/read
{ "uri": "file:///project/src/main.rs" }

Three features are easy to miss and change what resources are good for:

Templates. resources/templates/list exposes parameterised resources using RFC 6570 URI templates, so file:///{path} describes a family of resources rather than a fixed list. Arguments can be auto-completed through the completion API.

Annotations. A resource can carry an audience (user, assistant, or both), a priority from 0.0 to 1.0, and a lastModified timestamp. Clients use these to filter, prioritise what enters context, and sort by recency. A server that annotates well gives the host something to reason with instead of a flat list.

Subscriptions. A server that declares the subscribe capability can deliver notifications/resources/updated for watched URIs, so a client can re-read content when it changes rather than polling.

Resources should not have side effects. A resources/read that mutates state is a tool wearing the wrong label, and it will be read automatically by hosts that include context by heuristic.

Prompts: user-controlled templates

A prompt is a named, parameterised message template. prompts/get returns a list of messages with roles and content, ready to seed a conversation.

Prompts are user-controlled. That phrase refers to who decides when the prompt is used, not who authors it — the content is defined by the server. Hosts typically surface them as slash commands, so people discover them by typing /.

Use a prompt when you are packaging:

  • a repeatable way of asking for something, with slots to fill;
  • an entry point a person should be able to find and trigger;
  • a message sequence that includes reference material the model should see first.
// prompts/get
{ "name": "code_review", "arguments": { "code": "def hello():\n    print('world')" } }

A prompt message can carry text, images, audio, a resource_link, or an embedded resource — so a prompt can pull your documentation or a code sample into the conversation without the model needing a separate read.

The decision, in order

Ask these in sequence and stop at the first yes.

  1. Does it change state, or reach an external system that can refuse? → Tool.
  2. Is it content addressable by a URI that a person or host should be able to select? → Resource.
  3. Is it a way of starting a task that a person should trigger by name? → Prompt.

If a capability answers yes to two of them, it is usually two capabilities. "Search the knowledge base and return the best article" is a tool. "The article, by URI" is a resource. Shipping both is fine and often right; shipping one that quietly does both is what makes a server hard to reason about.

Four common misuses

Everything is a tool. The most common shape, because tools are the primitive most tutorials start with. The symptom is a get_document tool whose entire body is a file read. The cost is that hosts lose the ability to let a user pick context deliberately, and every read passes through the model's tool-calling budget.

Reference material as a tool result. A get_style_guide tool that returns 8,000 words puts the whole document into context on a call the model made on a hunch. As a resource, the host decides. As a reusable agent skill, it may not need to be in an MCP server at all.

A resource that acts. A resources/read that provisions, sends, or bills. Hosts may read resources automatically; anything with consequences must be a tool so it can be approved.

A prompt doing a tool's job. A prompt that instructs the model to call a sequence of tools is fine. A prompt that is the only place a required step is documented is a workflow with no enforcement.

Combining them well

The three primitives are strongest together, and the combinations are worth designing on purpose.

Pattern How it works
Tool returns a resource_link The tool does the search; the client fetches full content only if needed. Keeps results small.
Tool embeds a resource The result carries the content inline, with no resources/read round trip. Use when the content is small and always needed.
Prompt embeds a resource A /review prompt that arrives with your style guide already attached.
Resource plus subscription Long-lived context the client re-reads when the server says it changed.

The resource_link pattern in particular is how you keep a search tool from becoming a context firehose: return identifiers and titles, let the client decide what to open.

Security follows the control model

Each primitive has a different exposure, and the specification treats them differently for good reason.

Tools are the sharp edge. Servers must validate all inputs, implement access controls, rate-limit invocations, and sanitise outputs. Clients should prompt for confirmation on sensitive operations and show tool inputs to the user before the call, which is the defence against accidental or malicious data exfiltration through arguments.

Resources need URI validation and path sanitisation — the specification calls out directory traversal explicitly for file:// resources — plus permission checks before any read.

Prompts need input and output validation like anything else that composes text a model will follow. A prompt that interpolates untrusted text is an injection vector.

One rule spans all three: the set of tools, resources, or prompts a server returns may vary by the authorization presented on the request, because credentials are per-request input. It must not vary per-connection or as a side effect of other requests. And filtering a list is never access control — enforce authorization in the handler that does the work.

A worksheet for your own server

For each capability you are planning, fill in one row:

Capability Who should decide when it runs Does it change anything Addressable by URI Primitive
Look up an order Model, mid-task No Yes, orders://{id} Tool for search, resource for the record
Refund an order Model, with approval Yes No Tool
The refund policy Host or user No Yes Resource
"Draft a refund email" User No No Prompt

If the "who decides" column says "the model" for every row, re-read the list. Something in it is context the host should have been allowed to choose.

Next step: Browse MCP servers on LLM Mart and compare how their tools and resources match their stated purpose — then check your own server against the same question before you build the next tool into it.

Sources

0 0 0 0 Sign in to react

Comments (0)

Sign in to join the conversation.

No comments yet.