MCP Tools vs Resources vs Prompts Comparison
Every capability you add to an MCP server has to become a Tool, a Resource, or a Prompt, and picking the right one keeps your server predictable for every client that connects to it.
Search across all documentation pages
Every capability you add to an MCP server has to become a Tool, a Resource, or a Prompt, and picking the right one keeps your server predictable for every client that connects to it.
This page walks through the decisions you will hit most often when designing a server and ranks the right choice for each.
Scenario: A server needs to let a client read the contents of a specific file on disk.
| Rank | Choice | Approach |
|---|---|---|
| Best | Resource | @mcp.resource("file://{path}") addresses the file by URI and returns its contents on read |
| 2nd | Tool | A read_file(path: str) tool works but hides the fact that this is a pure, cacheable read |
| 3rd | Prompt | Not a fit; prompts produce instructions, not raw file content |
Wrong choice: Building this as a tool named get_file_action that also logs an audit entry as a side effect, since that mixes a read with a write inside something clients will assume is safe to call repeatedly.
Why best is best: A resource makes the read-only nature explicit and lets any client cache or list it without guessing at side effects.
Scenario: A server needs to let the model create a new row, such as a task or a ticket.
| Rank | Choice | Approach |
|---|---|---|
| Best | Tool | A create_task(title: str, priority: str) tool performs the insert and returns the new id |
| 2nd | Resource | Not appropriate; resources should not mutate state |
| 3rd | Prompt | Not a fit; a prompt cannot perform a database write |
Wrong choice: Exposing creation as a resource URI like tasks://create?title=X, since resource reads should be idempotent and side-effect free, and a client might re-read it without expecting a new row each time.
Why best is best: A tool clearly signals "this takes an action," and the model understands calling it again may create a second record.
Scenario: Multiple clients need the model to summarize documents the exact same way every time.
| Rank | Choice | Approach |
|---|---|---|
| Best | Prompt | A summarize_document prompt template packages the exact instructions and formatting rules once |
| 2nd | Tool | A summarize(text: str) tool could work if the summarization logic itself runs server-side, not via the model |
| 3rd | Resource | Not a fit; resources hold data, not instructions |
Wrong choice: Relying on every client to write its own ad hoc "please summarize this" instruction, since wording drift across clients produces inconsistent summaries even for the same document. Why best is best: A Prompt is exactly the primitive designed for shared, versioned instructions that multiple clients invoke consistently.
Scenario: A server has settings (feature flags, limits, defaults) a client or model may need to see.
| Rank | Choice | Approach |
|---|---|---|
| Best | Resource | A static config://settings resource returns the current configuration as JSON |
| 2nd | Tool | A get_config() tool works but implies an action for what is really a static read |
| 3rd | Prompt | Not a fit; configuration is data, not instructions |
Wrong choice: Hardcoding configuration values into every prompt template instead of exposing them as a resource, which means every template has to be edited whenever a setting changes. Why best is best: A resource is the natural home for anything that is "current state to read," especially when it rarely or never has side effects.
Scenario: The model needs to send an email, post a message, or call an external API that changes something outside the server.
| Rank | Choice | Approach |
|---|---|---|
| Best | Tool | A send_email(to: str, subject: str, body: str) tool performs the call and reports success or failure |
| 2nd | Resource | Not appropriate; this is an action with real-world side effects |
| 3rd | Prompt | Not a fit; a prompt cannot perform an external call on its own |
Wrong choice: Building this as a resource that "sends" on every read, since a client that re-reads a resource (for caching or display) would unexpectedly trigger a second send. Why best is best: A tool makes the action explicit, and the model treats every call as something with a real consequence rather than a safe, repeatable read.
Scenario: A team wants every client to run code review the same way, with the same checklist and tone.
| Rank | Choice | Approach |
|---|---|---|
| Best | Prompt | A code_review prompt with arguments for the diff and focus areas produces consistent instructions |
| 2nd | Tool | A run_static_analysis(path: str) tool could complement the prompt for the mechanical parts of review |
| 3rd | Resource | Not a fit on its own, though review guidelines could also be exposed as a resource for reference |
Wrong choice: Pasting the review checklist into each client's own system prompt separately, since updates then have to be made in multiple places and drift out of sync.
Why best is best: A Prompt centralizes the instruction in the server, so every client that invokes code_review gets the current version automatically.
Scenario: A support tool needs the model to pull up one customer's record by id.
| Rank | Choice | Approach |
|---|---|---|
| Best | Resource | customers://{customer_id} addresses each record by a stable, cacheable URI |
| 2nd | Tool | A get_customer(customer_id: str) tool works if the lookup also needs to enforce dynamic access rules per call |
| 3rd | Prompt | Not a fit; prompts do not fetch data |
Wrong choice: Building a single search_customers tool that always returns every matching field regardless of what the model actually needs, since that leaks more data into context than necessary.
Why best is best: A resource keyed by id gives a predictable, minimal, and addressable way to fetch exactly one record.
Scenario: Several client applications need the model to always respond to a certain request type in a fixed JSON shape.
| Rank | Choice | Approach |
|---|---|---|
| Best | Prompt | A prompt template with explicit output-format instructions, invoked the same way by every client |
| 2nd | Tool | Structured tool output can enforce format if the formatting happens in code rather than model output |
| 3rd | Resource | Not a fit; resources do not shape model output |
Wrong choice: Trusting each client's developers to remember and copy the exact formatting instructions into their own prompts, since even small wording differences produce inconsistent output shapes. Why best is best: A shared Prompt guarantees every client sends the model the same formatting instructions, byte for byte.
Does this operation change anything? If yes, it is a Tool. If it only reads existing data, consider a Resource. If it is packaging reusable instructions rather than data or an action, it is a Prompt.
Sometimes, such as a data lookup that could be a parameterized resource or a read-only tool. Prefer the Resource when the data is genuinely static-shaped and addressable; prefer the Tool when the lookup needs per-call logic like dynamic authorization checks.
Making everything a tool hides which operations are safe reads and which have side effects, which makes the server harder to reason about and can lead the model to treat destructive actions as casually as a lookup.
Building every capability as a tool, even pure reads, because tools are the most obvious analog to function calling. This buries safe, cacheable data lookups inside the same primitive as destructive actions.
A Resource. Hardcoding configuration into every prompt template means every value change requires editing multiple templates instead of one resource handler.
Yes. Many useful servers expose only Tools and Resources; Prompts are worth adding specifically when wording consistency across multiple clients becomes a real, observed problem.
A Tool often creates or updates data, and a Resource then exposes that same data for later reading, keyed by whatever id the tool returned.
Clients may assume resources are safe to read repeatedly or cache. A resource with a hidden side effect can trigger unwanted repeated actions, like sending duplicate notifications.
If the lookup is a straightforward id-to-record read, use a Resource. If it needs custom logic per call, such as filtering by the current user's permissions, a Tool gives you more room to run that logic before returning a result.
Yes. Clients call list_tools(), list_resources() (and list_resource_templates()), and list_prompts() to discover what a server offers, regardless of which primitives it uses.
A prompt template can be written to include a resource's URI or content as part of its generated instructions, giving the model both consistent phrasing and relevant data in one invocation.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the current Model Context Protocol specification. Model names, SDK versions, and the MCP spec move quickly - verify current specifics at platform.claude.com/docs and modelcontextprotocol.io before relying on them.
Reviewed by Chris St. John·Last updated Jul 16, 2026