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.
This page walks through the decisions you will hit most often when designing a server and ranks the right choice for each.
How to Use This Comparison
- Read each scenario and check which primitive matches the shape of what you are building, not just what feels convenient to code first.
- When a capability could plausibly fit two primitives, prefer the one whose semantics (action vs data vs instruction) match the reader's mental model of what is happening.
- Revisit these decisions as a server grows; a capability that started as a tool sometimes reveals a cleaner resource or prompt underneath it.
- Treat "Wrong choice" entries as real mistakes worth avoiding, not just theoretical edge cases.
Decision 1: Fetching the contents of a file
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.
Decision 2: Creating a new record in a database
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.
Decision 3: Standardizing how the model summarizes a document
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.
Decision 4: Exposing current server configuration
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.
Decision 5: Sending a notification or performing an external call
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.
Decision 6: Guiding a multi-step code review workflow
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.
Decision 7: Letting the model look up a specific customer record
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.
Decision 8: Enforcing a specific output format across every client
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.
Applying This Comparison in Practice
- Start by asking whether the capability changes anything: if yes, it is a Tool; if it is purely a read, consider a Resource first.
- Reach for a Prompt only when consistency of wording across multiple clients is the actual problem you are solving, not just because you want to save a few lines of text.
- Most production servers use all three primitives together: Tools for actions, Resources for the data those actions touch, and Prompts for the instructions that tie them together consistently.
FAQs
What is the single most important question to ask when choosing a primitive?
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.
Can a capability legitimately be built as either a Tool or a Resource?
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.
Why not just make everything a Tool for simplicity?
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.
When is a Prompt worth the extra design effort?
- When more than one client needs the exact same instruction wording.
- When you want to version and update instructions in one place instead of many.
- When output-format consistency matters across an entire team or product.
What is the most common mistake teams make when designing their first MCP server?
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.
Should configuration values be a Resource or hardcoded into Prompts?
A Resource. Hardcoding configuration into every prompt template means every value change requires editing multiple templates instead of one resource handler.
Is it ever correct to have zero Prompts in a server?
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.
How do Tools and Resources typically work together?
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.
What is the risk of using a Resource for something with side effects?
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.
How should I decide between a lookup Tool and a lookup Resource?
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.
Do all three primitives need to be discoverable by the client?
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.
Can a Prompt reference a Resource inside its generated messages?
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.
Related
- Understanding MCP: Tools, Resources, and Prompts - the conceptual foundation behind each primitive.
- Defining Callable Tools in an MCP Server - schema and handler design for tools.
- Exposing Readable Data as MCP Resources - URI design for resources.
- Building Reusable Prompt Templates with MCP Prompts - designing shared, parameterized prompts.
- MCP Core Concepts Best Practices - a checklist for applying these decisions consistently.
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.