The Context Window

MCP Resource vs Tool Distinction in Agent Design

Misclassifying Tools and Resources wastes tokens on every agent turn and silently tanks performance.

Features Editor · · 11 min read · Updated
Cover illustration for “MCP Resource vs Tool Distinction in Agent Design”
Model Context Protocol · September 8, 2026 · 11 min read · 2,521 words

MCP (Model Context Protocol) gives AI agents one shared way to connect to data and tools, and inside that protocol sits a distinction most teams get wrong. Tools trigger actions, Resources expose data, and mixing up which is which quietly wrecks how an agent performs. Most teams should be building fewer Tools and more Resources than they currently do; skipping Resources is the single biggest unforced error in how agents get built right now, and it costs real money in tokens burned on every turn.

Anthropic put MCP out into the world in November 2024 as an open standard: one protocol for how agents read data, call tools, and use prompt templates, meant to replace the pattern of every team wiring its own connection between a model and whatever system it needs to touch. People started calling it the "USB-C of AI," decent shorthand for a standard meant to work the same way across many different systems. The growth curve backs up the comparison: 100,000 monthly SDK downloads near launch, climbing to 97 million by March 2026, a pace that reportedly outran React's own early adoption curve. That's enough traction that Anthropic, Block, and OpenAI moved MCP's governance to the Linux Foundation under something called the Agentic AI Foundation, a sign that this counts as shared infrastructure now, not a side project someone will deprecate next quarter.

The three primitives and what each one is responsible for

MCP servers expose exactly three primitives, and each one answers to a different boss. Tools are model-controlled: the LLM itself decides mid-conversation whether to call one. Resources are application-controlled: the host or client decides when to load them. Prompts are user-controlled: a person picks a template off a shelf.

The plumbing underneath all three follows the same shape every time. A host is the actual AI application, a client is a stateful session tied to that host, and a server is the thing exposing the capabilities. The agent never reaches into a backend system directly; everything runs through the server's process boundary, which is really the whole point. Prompts deserve a nod here since they're the third leg of this stool, but the interesting tension lives elsewhere, on the Tool/Resource axis. Worth naming early, because the rest of this piece keeps circling back to it: a Tool does something, a Resource shows the readable result of what got done, and a Prompt bundles both into a repeatable workflow. Together, they cover the entire loop an agent runs on.

How Tools work and what makes something a Tool

A Tool is a callable function: a name, a plain-language description, and a JSON Schema spec for whatever input it needs. It does something, hands back a result, and that something might change the state of the world, whether that's sending an email or writing a row to a database.

The model decides when to fire a Tool. It's reading the conversation, deciding on the fly whether this is the moment to write, create, send, execute, submit, or calculate. That covers most of what a Tool is for: anything with a side effect belongs here.

Here's the part that trips people up, and it's worth sitting with rather than skating past: the description attached to a Tool isn't decoration, it's the entire interface the model has into what that Tool does. Suppose the description is vague or misleading. The model either grabs the wrong Tool or stuffs in arguments that don't fit the schema, and now the agent is confidently doing the wrong thing. As of June 2025, Tools can return structured output and resource links too, which blurs the output side a little, but the control model stays exactly the same: model decides, model calls, server executes. Under the hood, the sequence is predictable: the agent queries the servers it's connected to, gets back tool metadata, picks one, sends a structured request, and gets a serialized result back, with the server mediating every step.

How Resources work and what makes something a Resource

A Resource is read-only content addressed by a URI, using schemes like file:///, db://, note://, config://, or stock://. It fetches without touching anything, full stop.

The application decides when a Resource loads, either pre-loading it into context automatically or fetching it when a user explicitly selects it; the model has no say in the timing. Resources come in two flavors. Static ones sit at a fixed URI with fixed content, like a config file or a policy document that doesn't move. Dynamic ones use URI templates that take parameters and generate content on the fly, something like db://users/{id}.

Discovery follows a clean pattern too: resources/list to see what's available, resources/read to pull it, resources/subscribe to get notified when it changes (more on that later, because almost nobody uses it, and that's a real missed opportunity). Resources can carry log files, JSON configs, live data snapshots, file contents, even structured blobs like PDFs or images: anything the model needs to reason about without acting on it. Here's the hard rule the spec is clear about: a Resource handler must not produce side effects. Sneaking a write into a resource handler is a spec violation, plain and simple.

The single question that determines which primitive to reach for

The MCP spec actually answers this, in what it calls the User Interaction Model, and it comes down to one question: who decides when this gets used?

Suppose the model should be free to reach for it mid-conversation without anyone picking it from a menu. That's a Tool. If a user or the application controls when it loads, or if it's reference material the agent reasons from instead of acting through, it's a Resource. The clearest dividing line here is state mutation: anything that writes to a database, fires a notification, edits a file, or hits an external service belongs in a Tool. No exceptions worth carving out.

Put it in concrete terms. API schema docs, system config, log files pulled for diagnosis, a navigable file tree, a policy document: all Resources. Database writes, sending an email, running code, submitting a job, any API call that creates or updates a record: all Tools. The one case that actually took some back-and-forth to work out is a database lookup, because the same underlying operation gets classified differently depending on who calls the shot. If the model decides when to fetch it, that's a Tool; if the application already loaded a snapshot for the model to read, that's a Resource. Restated with the compound pattern from earlier: "create issue" is a Tool, "list open issues" is a Resource, and "triage my backlog" is a Prompt that stitches the two together. Each primitive earns its keep by doing exactly one job.

Why misclassifying primitives has a measurable token cost

Here's the mechanism nobody thinks about until their context window is already full: every Tool schema mounted on an agent loads into context on every single turn, whether or not the model ever calls it. That cost gets paid before the model reads a word the user typed.

At scale this adds up fast. One study cited in Kloia's analysis of MCP optimization found input-token growth ranging from 3.25x up to 236.5x, alongside an average 9.5% drop in accuracy purely from tool-schema overhead sitting in context. Perplexity's CTO has reported tool schema overhead eating up to 72% of available context before the agent even starts on the actual query. That's most of the room the model has to think, gone before it thinks anything.

Resources sidestep this cost entirely, because they don't sit in context until something explicitly asks for them by URI. No schema tax, no per-turn cost. Consider a server exposing something like get_all_sales_records as a Tool: every call to it, every description of it, every turn it's mounted, bloats the context. Swap that for a server-side aggregation Tool like get_sales_summary_by_region, and the heavy lifting moves onto the server where it costs milliseconds and zero tokens. Working through enough of these examples side by side makes the pattern hard to miss: over-tooling carries a real throughput cost, and misclassifying stable reference data as a Tool instead of a Resource is usually the first place to find it, and the cheapest place to fix.

Diagram: The Token Cost of Misclassifying Primitives. Visualizes: Visualize the hidden per-turn token overhead that Tool schemas impose versus the zero per-turn cost of Resources.

Why Resources are underused and what gets built instead

Most teams build Tools first, because Tools are the more intuitive primitive. You call it, it returns something, done. Resources feel like extra homework, so a lot of teams never circle back to write them. Tools get all the attention during development, while Resources, the part that actually saves tokens and money, sit ignored on the spec's shelf.

The numbers back this up. Across more than 5,800 MCP servers currently active in public registries (per Claude Code's own count), resource adoption trails tool adoption by a wide margin. Subscriptions, the change-notification pattern built into Resources, are widely available in the spec yet rarely appear in production deployments. Quality across the ecosystem doesn't help either: users report installation failure rates between 30% and 50% on community-built MCP servers, and the servers that do work skew heavily tool-only.

This matters because real workloads already run on this ecosystem. Stacklok's 2026 report found 41% of surveyed software organizations already in limited or broad production with MCP servers, meaning actual work rides on infrastructure that mostly ignores one-third of its own design. What gets lost in the shuffle: stable reference data that should live in a Resource, loaded once and held by the application, instead gets serialized as Tool output over and over, re-entering the context window on every call.

How resource subscriptions enable event-driven agent patterns

MCP splits notification from retrieval on purpose. When a Resource changes, the server sends a small notification; the client then decides whether, and when, to go fetch the updated content. Nobody's forcing a big payload down the pipe the second something changes.

That separation acts as a safeguard. It stops a server from flooding a client with large data dumps during high-frequency updates, and it puts the client in charge of when it spends the bandwidth. This mirrors the Observer or Pub-Sub pattern familiar from other systems: agents that react to state changes as they happen, instead of polling on some arbitrary schedule and hoping they catch things in time.

Where this actually shows up: an agent watching a config resource for feature-flag changes without checking in on every single turn. Multi-agent setups where one agent's output becomes a Resource that a second agent subscribes to. Compliance-monitoring agents fed by audit logs that update live. These are native use cases for a mechanism sitting right there in the spec, mostly unused. Teams end up rebuilding it badly with custom polling Tools, spending tokens and engineering time to recreate something MCP already gives away for free. That's the real cost of skipping subscriptions: someone spends time reinventing a feature that already existed, worse, on a deadline.

The security boundary the Tool/Resource split creates — and what breaks it

Tools widen the attack surface by design, because they write, delete, send, and execute. Any instruction that reaches a Tool and gets acted on can produce a side effect that doesn't undo itself. Resources, being read-only, can't do any of that on their own, which makes keeping data retrieval inside Resources a real structural limit on how bad things can get.

The danger sneaks in sideways, through prompt injection buried in resource content. An agent calls a Tool that returns something like a document, a pull request title, or a database row, and that content lands in context, sometimes carrying instructions nobody meant to send. This isn't hypothetical. In April 2026, researcher Aonan Guan and a Johns Hopkins team demonstrated exactly this: malicious instructions embedded in content an agent was going to read anyway can be picked up as task context, with adversarial text alone sufficient to redirect agent behavior.

Servers can turn hostile too. One evaluation testing four separate attack vectors found client-side MCP security wildly inconsistent: some clients handle adversarial conditions reasonably, while others have been shown to permit unintended agent behaviors through malicious tool descriptions. Malicious tool descriptions have been shown in research to produce serious unintended agent behaviors.

All of this maps cleanly onto least-privilege thinking, once you sit with what each half of the split is actually for. An agent stuck with read-only Resource access can, worst case, read something it shouldn't have. An agent with Tool access can take that same data and post it somewhere public, delete records outright, or fire off an external message nobody approved. The PR-exfiltration case worked precisely because the agent had a Tool for posting comments and a Resource-equivalent read of PR content sitting in the same context, with no scoped credential standing between them. Every Tool needs to be auditable and scoped tightly, and every data source that doesn't need to trigger an action belongs in a Resource. This split enforces least privilege in practice; skipping it is how a PR title turns into a leaked secret.

A practical decision framework for placing primitives in an agent design

Start with the mutation test. Does the operation change state anywhere: a database, a file, an external service, a message queue? Yes, it's a Tool, no debate needed.

Everything that survives that test gets a second question: will the model decide when to fetch this mid-conversation, or will the application load it ahead of time? Model decides, it's a Tool. Application decides, it's a Resource. Before shipping, count the Tools on a server. A long list of fine-grained ones is a token-budget problem waiting to happen, and it's usually cheaper to cluster related retrieval operations into Resources, or aggregate them server-side, than to fix it later.

Treat the subscription API as a real design option, not an afterthought, for any agent that needs to react to change. It's already built into the spec, it's barely used across the ecosystem, and building custom polling Tools to fake the same behavior wastes tokens and engineering time both. Security review should follow the identical split: for every Tool, ask what the worst case looks like if an injected instruction reaches it; for every Resource, confirm there's no write path hiding inside the handler.

This classification problem shows up constantly in agentic content pipelines, the kind that research, draft, and publish, and any platform running both human editors and AI agents lives inside exactly this design space. A document corpus is a Resource. A publishing action is a Tool. Blur the two and the agent either drowns its context in text it didn't need yet, or takes an action mid-workflow nobody asked for. Gartner predicts 75% of API gateway vendors will support MCP by the end of 2026, and as adoption scales past early experiments, the teams that placed their primitives correctly from the start will have servers that plug cleanly into that broader ecosystem. The teams that shipped tool-only designs because it was faster in month one are going to pay for that shortcut in a refactor later, and refactors are never as cheap as the thing they replaced.

Sources

  1. techcommunity.microsoft.com
  2. zuplo.com
  3. ramwert.medium.com
  4. modelcontextprotocol.info

More in Model Context Protocol