MCP Resources vs Tools vs Prompts: Stop Putting Everything in a Tool
Most first MCP servers export one giant run_query tool and call it a day. It works in a demo and then the model starts guessing table names, stuffing 40k tokens of JSON into the next turn, and skipping the resource that already had the schema. The protocol gives you three primitives — resources, tools, and prompts — because they hit the model at different times with different contracts. Mixing them up is how you get an "agent" that is just a confused RPC client.
What each primitive is for
Tools are actions: they have a JSON schema, the model chooses to call them, and they return a result into the conversation. Side effects belong here — create a record, send a mail, run a search. So do reads that are parameterized by a decision the model must make ("fetch case 123 because the user named it").
Resources are fetchable content with a URI. The client (or the user, or the host) can attach them; some hosts let the model request them. They are for data that should be in context as documents, not as the return value of an improvised query: a schema catalog, a runbook, yesterday's incident notes, a Dataverse table definition. Resources can be listed and read without inventing arguments. They cache. They have MIME types.
Prompts are named templates the host can offer as slash-commands or menu items: "explain this plugin trace," "draft a dual-write runbook." They are not model-invented. They encode a workflow you already know is useful, with slots for resources and arguments. Think of them as saved plays, not as tools with a prettier name.
If you only remember one split: tools change the world or answer a question the model posed; resources are the library; prompts are the playbook.
A Dataverse example that stays honest
A bad server: one tool dataverse_fetchxml with a free-string fetchXml argument. The model will write invalid FetchXML, retry, and burn budget.
A better split:
- Resource
dataverse://org/tables/{logicalName}— entity metadata, attributes, relationships. Small, cacheable, meant to be attached before the model writes a query. - Resource
dataverse://org/tables— list of logical names the agent is allowed to see (already permission-filtered). - Tool
dataverse_retrieve—logicalName,id, optionalcolumns. Tight schema, no FetchXML in the common path. - Tool
dataverse_query— a constrained filter object (field, op, value), not a raw query language, plus a hard row cap. - Prompt
debug-plugin-trace— slots for a trace resource URI and a table name, with instructions that say "read metadata first, then retrieve, never guess attribute names."
The model still acts. It just cannot skip the catalog or open a firehose.
Size and freshness
Tool results land in the transcript and live there until compaction. A 200-row Dataverse dump in a tool result is a tax on every later turn. Resources can be re-read, paginated, or omitted from the next request. Prefer a resource for anything over a couple of kilobytes that the user might want to keep pinning; prefer a tool for a short, one-off answer.
Stale data: a tool is always "now." A resource needs a cache header or an etag in the server so the host can refresh. If you expose "current inventory" as a resource with a 10-minute cache, say so in the description or you will debug "the agent hallucinated stock" that was just a cached resource.
Permissions sit on different edges
Tool calls should go through the same authorization as your API — user token, never a god-mode service account "because it's an agent." Resource URIs are capabilities: if you can mint dataverse://org/tables/incident, you have granted read on incidents. Do not put secrets in resource bodies. Do not make a tool that accepts a raw SQL string and a connection name; that is a confused deputy with extra steps.
Prompts should not silently escalate. A prompt that includes "you may call delete_record" is an instruction, not an allowlist. The allowlist is the tool set the host enabled.
Rule of thumb
- Catalog, schema, docs, blobs, traces → resource
- Search, get-by-id, create, update, delete, "run this job" → tool
- Recurring specialist workflow with a known opening move → prompt
- If you are about to add a tool argument called
modewith seven enums, you probably wanted two tools and a prompt
MCP is not a second REST. It is a way to hang a library, a set of hands, and a few rehearsed scripts on a model that is otherwise guessing. Use all three, or the guesswork comes back.
Keep reading
Building Your First MCP Server in Python: A Hands-On Walkthrough
From zero to a working Model Context Protocol server in about 100 lines — tools, resources, a local client test, and the traps that will bite you on day one.
An MCP Server for Dynamics 365 Finance and Operations: Natural-Language Access to ERP
How to expose Dynamics 365 Finance and Operations to Claude through MCP — the architecture, the data entity choices, the auth model, and the operations that should never be one prompt away.
Claude Code Hooks: Automating Repo Guardrails Without Pre-Commit Fatigue
Use Claude Code hooks to enforce policy at the moment actions happen — before edits, before tool calls, on session start — without relying on pre-commit hooks everyone learns to bypass.
Prompt Caching with Claude: What It Saves, Where It Fails, and How to Set It Up in Production
A precise look at Claude's prompt caching — how the cache hit math actually works, the 5-minute and 1-hour TTL variants, and the subtle mistakes that silently disable it.
Tool Use with Claude: Parallel Calls, Retries, and the Patterns That Survive Production
The tool-use features of the Claude API look straightforward in the docs. These are the production patterns that separate a demo from a system that handles real traffic — parallel tool calls, retry loops, error recovery, and tool_choice tactics.
Claude Agent SDK vs Direct API: When to Reach for Each
The Claude Agent SDK is the fastest path to a capable coding-style agent. The raw messages API is the fastest path to understanding what your agent is doing. Here's when each is the right tool.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.