# MCP Tools vs Resources vs Prompts: How to Choose the Right Primitive

> A decision procedure for MCP's three server-side primitives — who controls each one, a worked example of what it costs to expose a Resource as a Tool by mistake, and how Sampling and Elicitation fit as the client-side counterparts.

Guide: MCP in practice — part 10
Published: 2026-08-21 · Updated: 2026-08-21 · 1490 words
Canonical: https://changegamer.ai/articles/mcp-resources-and-prompts-vs-tools
JSON: https://changegamer.ai/api/articles/mcp-resources-and-prompts-vs-tools.json
Pillar: https://changegamer.ai/articles/mcp-server-in-production.md

## In short

- MCP's three server-side primitives answer to three different controllers: the model decides when to call a Tool, the host application decides when to inject a Resource, and the user decides when to invoke a Prompt — and none of the three can invoke either of the others.
- Content that is read-only, always-relevant, and owned by the application — a config blob, the file a user has open, a live status value — belongs in a Resource, not a Tool, because a Resource is injected on the application's schedule while a Tool depends on the model remembering to ask for it.
- Registering read-only context as a Tool rather than a Resource adds a permanent entry to tools/list that every session pays a token cost for, and makes whether the model actually has that context on a given turn a matter of whether it chose to call the tool rather than a guarantee the application controls.
- Sampling and Elicitation are the client-side counterparts to Tools and run in the opposite direction: instead of a model asking a server to act, a server asks the connected client for an LLM completion or for structured user input, and both require a human review step before the result reaches the model or the server.
- ChangeGamer's own /mcp server, checked directly against its request handler as of August 2026, registers ten capabilities exclusively as Tools and declares no resources or prompts capability at all, including for its read-only resource catalog.

---

[MCP server in production](/articles/mcp-server-in-production) spends one paragraph on choosing between Tools, Resources and Prompts before moving on to naming, schemas and the injection surface a Tool specifically creates. That one paragraph is the right amount of space for a pillar covering seven other decisions — but the choice it describes has consequences the pillar does not have room to unpack, and getting it wrong is a design smell in a specific, non-security direction: a bloated, unreliable toolset, not a vulnerability. This article is that unpacking.

## MCP splits primitive control three ways: model, application, user

MCP's three server-side primitives are distinguished by who decides when each one gets used, not by what kind of data they carry. A **Tool** is invoked when the model, reasoning over the current conversation, decides to call it — the server has no say in timing. A **Resource** is injected when the host application decides to add it to context, and the model never initiates that fetch on its own. A **Prompt** is invoked when the user explicitly selects it, typically via a slash command in the client's UI, and the model cannot reach for it autonomously ([MCP primitives](/resources/mcp-primitives)).

| Primitive | Who decides to invoke it | Typical shape |
|---|---|---|
| Tool | The model, autonomously, mid-conversation | An action with side effects, or a lookup that depends on a model-chosen argument |
| Resource | The host application, on its own schedule | Read-only context the app already knows it wants injected |
| Prompt | The user, by explicit selection | A reusable instruction template with user-filled arguments |

This is a control-model distinction, not a data-format one — a Resource and a Tool can both return the exact same JSON payload. What differs is who is on the hook for deciding, each time, whether that payload reaches the model at all.

## How do you choose between a Tool, a Resource, and a Prompt for a given capability?

Choose based on who needs to make the timing decision for that specific capability, working through three questions in order. First: does using this depend on an argument only the model can supply — a search query, a record ID, a value derived from what the conversation just established? If yes, it has to be a Tool, because nothing else in MCP lets the model supply an argument at call time. Second, if no argument is needed: does the host application already know it wants this content in context, on a schedule the application itself controls rather than one the model negotiates? If yes, it is a Resource. Third: is this a reusable instruction template that a human, not the model, picks explicitly to kick off a task? If yes, it is a Prompt.

A file viewer that returns whatever file a user has open is a Resource — the application already knows which file and injects it without asking the model. A file search tool that takes a model-chosen query string is a Tool — nothing about the query is known until the model decides what to search for. A `/review_pr` template that a user selects and fills with a PR URL is a Prompt — the model never triggers a code review on its own initiative through this mechanism.

### An honest example of the trade-off, not a template to copy blindly

Not every server that could cleanly separate Tools from Resources actually does, and that is sometimes a defensible trade rather than a mistake. ChangeGamer's own `/mcp` server, checked directly against its Cloudflare Worker request handler as of August 2026, declares `capabilities: { tools: {} }` only and implements all ten of its capabilities as `server.tool()` handlers — including `list_resources` and `get_resource`, which serve exactly the kind of read-only, catalog-style content the primitive taxonomy would suggest as Resource candidates. Nothing in that file registers a `server.resource()` or `server.prompt()` handler. That is a legitimate simplification for a server whose whole surface is model-driven lookup rather than app-injected context: a tools-only server behaves identically across every MCP host, while Resource support has historically varied by client implementation. It is not evidence that Resources are unnecessary — it is evidence that "Tool-only" is itself a valid design when nothing you expose needs application-controlled injection timing.

## What does exposing a Resource as a Tool actually cost you?

Exposing application-owned, always-relevant context as a Tool instead of a Resource costs you reliability and toolset bloat, not just an architectural purity point. Take the concrete example the primitive reference itself uses: a coding-assistant server exposing whichever file the user has open. Built correctly, the host calls `resources/read` the moment the user opens the file, and the model sees that content as context it never had to ask for ([MCP primitives](/resources/mcp-primitives)). Built as a `get_current_file` Tool instead, three concrete failure shapes follow:

- **Skipped context.** The model has to remember to call `get_current_file` before reasoning about the file. On a turn where it does not — because the conversation drifted, or because the model judged the call unnecessary — it answers using whatever file content it last saw, which may be several turns and several edits stale, or may be nothing at all.
- **Redundant calls.** A model unsure whether its existing context is current has no way to distinguish "the application already refreshed this for me" from "I need to fetch it again," so it may call `get_current_file` defensively on turns where the file has not changed, burning a round trip for no new information.
- **Permanent toolset weight.** `get_current_file` sits in `tools/list` next to every actual action the server exposes — `write_file`, `run_tests`, whatever else the server does — for the life of every session, whether or not that session ever calls it. A capability that should have been free, guaranteed context becomes one more line item the model has to weigh against every other tool on every single turn.

None of this is a security failure — the content is not attacker-controlled, and nothing here is about prompt injection or exfiltration. It is a fit failure: the wrong primitive for a capability whose timing was never supposed to be the model's decision in the first place.

## Where do Sampling and Elicitation fit into the primitive map?

Sampling and Elicitation are MCP's client-side primitives, and they run in the opposite direction from a Tool call: instead of the model asking the server to do something, the server asks the connected client for help. **Sampling** lets a server request an LLM completion from the host client via `sampling/createMessage`, with the spec requiring the client to let a human review both the outgoing request and the returned response before either reaches the model or the server — a deliberate check against a server silently using the model to generate or exfiltrate content the user never sanctioned. **Elicitation**, added in the 2025-06-18 spec revision, lets a server request structured mid-session input from the user via `elicitation/create`, rendered as a form the user can accept, reject, or cancel; servers are explicitly barred from using it to request passwords or other credentials ([MCP primitives](/resources/mcp-primitives)).

Both matter for completing the primitive picture, but neither substitutes for the Tool-vs-Resource-vs-Prompt decision above — they answer a different question (does the server need something *from* the client, rather than does the client need something *from* the server). One dated fact worth carrying forward: under SEP-2577, the 2026-07-28 spec revision — shipped final, on schedule, on that date — marked Sampling and Roots (along with Logging) as deprecated. This is an annotation-only deprecation: both keep working, and removal is barred for at least twelve months from 2026-07-28, so a server built on Sampling as of August 2026 is not broken, but it is building on a primitive with a stated end date rather than an indefinite one. The full JSON-RPC method names and message shapes for Sampling, Roots and Elicitation are in [MCP primitives](/resources/mcp-primitives) rather than reproduced here.

## Where this leaves you

Pick the primitive by asking who needs to control the timing of the capability, not by what the payload looks like: a model-supplied argument means Tool, application-owned injection timing means Resource, and explicit user selection means Prompt. Getting this wrong at the Resource/Tool boundary specifically does not create a vulnerability — it creates a toolset that grows every session's token cost and makes context availability a matter of the model remembering to ask rather than the application guaranteeing it. Sampling and Elicitation complete the primitive set from the other direction, letting a server ask the client for a completion or for structured input, each gated behind human review. For the full production checklist this primitive choice sits inside — transport, auth, versioning, testing, distribution, observability and failure modes — see [MCP server in production](/articles/mcp-server-in-production); for the connect-time and call-time injection risks specific to Tools once you have chosen one, see [defending MCP clients against tool description and output injection](/articles/mcp-tool-description-injection).

## Frequently asked questions

### What is the difference between an MCP Tool, Resource, and Prompt?

A Tool is an action the model decides on its own to invoke, such as calling an API or running a calculation; a Resource is read-only context the host application injects on its own schedule, which the model never requests directly; and a Prompt is a reusable message template the user explicitly selects, typically through a slash command, which the model cannot invoke by itself. The distinction is who controls the timing of use — model, application, or user, respectively — not how the data happens to be formatted.

### When should app-injected context be a Resource instead of a Tool?

App-injected context belongs in a Resource whenever the host application already knows it wants that content in the model's context and controls when it gets added, such as the file a user has open or a status value the UI already displays — the model never has to decide to fetch it because the application injects it directly. If a piece of content depends on an argument the model has to choose, such as a search query or a record ID, it needs the model's judgment to produce that argument and therefore has to be a Tool instead.

### What actually breaks if I expose a Resource as a Tool by mistake?

Exposing read-only, application-owned context as a Tool instead of a Resource turns a guarantee into a gamble: the model now has to remember to call the tool on a given turn to have that context at all, rather than receiving it automatically, so answers can end up based on stale or entirely missing context whenever the model skips the call. It also adds a permanent line to tools/list that every session pays a token cost for and that the model has to weigh against every other tool on every turn, regardless of whether that particular session ever needs it.

### Is MCP Sampling the same thing as a Tool call?

No — Sampling runs in the opposite direction from a Tool call. A Tool call is the model asking the server to do something; Sampling is the server asking the connected client to run an LLM completion on the server's behalf, subject to a human review step before either the outgoing request or the returned response reaches the model. Confusing the two misattributes who is making the decision: with Tools, the model chooses to call; with Sampling, the server initiates the request and the client mediates it.

### Does ChangeGamer's own MCP server expose Resources, or only Tools?

ChangeGamer's /mcp server, as verified directly against its Cloudflare Worker request handler in August 2026, declares only a tools capability and implements all ten of its capabilities — including list_resources and get_resource, which serve read-only reference content — as MCP Tools rather than MCP Resources. That is a deliberate simplification, not an oversight the primitive-choice framework above would flag as automatically wrong: a tools-only server works against every MCP host uniformly, while Resource support varies by client implementation.


---

## The rest of this guide

- [MCP Server in Production: How to Build, Ship and Run One](https://changegamer.ai/articles/mcp-server-in-production.md): The operator playbook for taking an MCP server past the quickstart: transport choice, OAuth 2.1 auth, tool design, versioning against a moving spec, testing across clients, distribution, observability, cost and the failure modes that show up once real clients connect.
- [stdio vs. Streamable HTTP for MCP Servers: A Decision Framework](https://changegamer.ai/articles/mcp-stdio-vs-streamable-http.md): Which MCP transport to build against and why: the single-client-vs-shared decision rule, how state works without a session handshake under the 2026-07-28 spec, the auth-model switching cost, and what actually breaks migrating off HTTP+SSE.
- [How to Implement OAuth 2.1 for an MCP Server](https://changegamer.ai/articles/mcp-oauth-implementation.md): A wire-level implementation walkthrough for OAuth 2.1 on a remote MCP server: what the discovery documents actually contain, CIMD vs. Dynamic Client Registration in your server code, per-SEP detail from the 2026-07-28 hardening set, and token-validation mechanics.
- [Defending MCP Clients Against Tool Description and Output Injection](https://changegamer.ai/articles/mcp-tool-description-injection.md): Two distinct MCP injection surfaces — a tool description at connect-time and a tool's return value at call-time — and the client-side architectural patterns (Dual LLM, Action-Selector, Context-Minimization) that contain each one.
- [How to Test an MCP Server in CI](https://changegamer.ai/articles/testing-mcp-servers-in-ci.md): The implementation mechanics below the three-layer test pyramid: what a mocked MCP transport actually replaces, what a Streamable HTTP cassette contains, a concrete CI job/trigger shape, and how to catch spec-version drift before it reaches production.
- [MCP Server Versioning and Spec Migration: An Operator Playbook](https://changegamer.ai/articles/mcp-server-versioning-and-spec-migration.md): A migration runbook for MCP server operators: feature-detecting via capabilities instead of hard protocolVersion branching, a dual-version fleet rollout with rollback triggers, a compatibility shim for legacy clients still sending initialize, and a deprecation calendar built off the 12-month SEP-2577 floor.
- [MCP Server Observability with OpenTelemetry: Spans, Metrics, and Trace Correlation](https://changegamer.ai/articles/mcp-server-observability-opentelemetry.md): Instrumenting an MCP server past the pillar's baseline: what to put on a tool-call span beyond gen_ai.tool.name, what replaces the deprecated Logging primitive in practice, per-tool-name latency and error-rate metrics, and how a trace ID actually survives the agent-to-upstream-API hop.
- [How to Publish an MCP Server to the Official Registry](https://changegamer.ai/articles/mcp-server-registry-publishing-playbook.md): A step-by-step walkthrough of the mcp-publisher CLI and the server.json manifest for publishing an MCP server to registry.modelcontextprotocol.io, how to republish after a version bump, and how the registry relates to aggregators, marketplaces, and direct distribution.
- [MCP Server Cost Optimization: Toolset Size, Caching Hints, and Fan-Out](https://changegamer.ai/articles/mcp-server-cost-optimization.md): How the token cost of an MCP server's tool list, the 2026-07-28 spec's ttlMs/cacheScope caching hints, fan-out from callers you do not control, and per-tool-name cost visibility each shape what a production MCP server actually costs to run.
- [Common MCP Server Failure Modes and How to Fix Them](https://changegamer.ai/articles/mcp-server-failure-modes.md): A runtime playbook for the two MCP server failure modes with no dedicated deep-dive elsewhere: unrecoverable state after a mid-call crash, and malformed or hallucinated tool calls that reach the handler despite upstream validation.
- [The MCP Server Production Launch Checklist](https://changegamer.ai/articles/mcp-server-production-launch-checklist.md): A phase-by-phase go/no-go checklist for launching an MCP server: checkable gate conditions for transport and auth, tool design, cross-client testing, publish readiness, observability, and ongoing operation — with links to the mechanics each gate depends on.
- [Zero-Touch Enterprise Authorization for MCP Servers: ID-JAG and SEP-990](https://changegamer.ai/articles/mcp-enterprise-sso-id-jag.md): How Enterprise-Managed Authorization (SEP-990) removes the per-server OAuth consent screen for MCP servers: the ID-JAG grant mechanism, its RFC 8693/7523 building blocks, named launch adopters as of August 2026, and how it layers on top of standard OAuth 2.1 rather than replacing it.

## Reference resources

- https://changegamer.ai/resources/mcp-primitives.md

All guides: https://changegamer.ai/api/articles.json · Reference corpus: https://changegamer.ai/llms.txt
