MCP Resources, Tools, and Prompts: the Three Primitives

Sort MCP capabilities by who pulls the trigger, or pay a 1,248-token tool tax every session

MCP Resources, Tools, and Prompts: the Three Primitives

You wrapped the company’s deploy API in an MCP server, and it works. Ask it about deploys and the agent pulls the production config, checks what shipped last week, and triggers the build when you say go. Every capability went in the same way, registered as a tool: the one primitive the model can call on its own. That last clause is the whole reason. If you want the agent to do something, it has to be a tool, so everything the agent touches becomes one.

The Model Context Protocol actually hands a server three separate levers for the same job, sorted along one axis: who controls invocation. In the current spec, dated 2026-07-28, resources are application-controlled, tools are model-controlled, prompts are user-controlled. The host application can decide that something runs. The model can decide. A human can decide. Three levers, and the tool reflex reaches for the same one every time.

The cost of that collapse is quiet, recurring, and computable. A tool definition rides in the model’s tool list on every turn of every session for as long as the server stays connected, whether or not the model ever calls it. Here is the claim, stated up front and re-derived below: demote the two reads in a small deploy server from tool to resource, and one ordinary eight-turn deploy-review session sheds 1,248 tokens of schema the model was never asked to weigh. The reads do not get cheaper. They stop being re-offered to the model as a choice on every single turn.

Check whether you’re already paying this

Section titled “Check whether you’re already paying this”

One minute on your own server settles whether this piece is for you, and it settles it before any arithmetic below.

Open the file where your server registers its capabilities and count the tools. For each one, ask: does the model need to decide, mid-conversation, whether to run this, or would nearly every session that wants this answer want it the same way? A config lookup, a record fetch, a schema dump: every session wants those identically, at the same point, with the same parameters. A mutation, or a search whose query the model composes on the spot: those are decisions.

If everything in your tool list is a genuine decision, this sort will not move anything for you. Close the tab; nothing below applies. If reads are sitting in the tool list, which is the default shape most teams ship, the rest of this piece prices what that costs per turn and shows the re-sort that removes it.

A read that every session wants the same way is not a decision, and only decisions belong in the tool list.

One server, four capabilities, eight turns

Section titled “One server, four capabilities, eight turns”

Fix one example and carry it through every section, the way this site’s arithmetic pieces do.

The server wraps an internal deploy API and owns four capabilities. Two reads: get_deploy_config(environment), which returns the region (us-east-1), the strategy (blue-green), the required approvers (@platform), and the healthy-instance floor (min_healthy: 2); and list_recent_deploys(environment, limit), which returns build ids, deployers, and timestamps. One write: trigger_deploy(build_id, environment), which mutates infrastructure and requires explicit confirmation. One checklist: deploy_review(build_id), the pre-production review procedure the team already trusts, currently living in a README half of them have opened.

A sibling piece on this site argues why one MCP server beats ten hand-written integrations (one server, not ten); that is the how-many-servers question. This piece takes the server as given and asks the question inside it: which of the three primitives each capability belongs on.

The session to price is one ordinary deploy review: is build 4821 safe to ship to production? Eight turns. One, you ask the question. Two, the model calls for the production config. Three, it reads the result. Four, it calls for recent deploys. Five, it reads them. Six, it weighs the decision. Seven, you confirm. Eight, it triggers the deploy.

The yardstick, declared once so every number below is checkable: each definition gets written out as its actual JSON and measured as the compact wire form, with characters divided by four as the token estimate. That chars-per-four ratio is an approximation. What the argument leans on is the ratio between definition sizes and the arithmetic built on them, and both survive any consistent estimate. The window is 200,000 tokens, the same convention this site’s other pieces use.

Four named capabilities, one eight-turn conversation, one declared yardstick: everything below is arithmetic on these fixed inputs.

The reflex design: reads as tools, 78 tokens each

Section titled “The reflex design: reads as tools, 78 tokens each”

Compute the obvious design first, the one most teams ship, before anything clever appears.

Everything the agent touches is a tool, so the two reads and the write all register the same way. Here is get_deploy_config exactly as the model sees it:

{
"name": "get_deploy_config",
"description": "Get the deploy configuration for an environment: region, deploy strategy, required approvers, and minimum healthy instance count.",
"inputSchema": {
"type": "object",
"properties": {
"environment": { "type": "string", "enum": ["staging", "production"] }
},
"required": ["environment"]
}
}

Compact that onto the wire and it is 312 characters: 78 tokens at four characters per token. list_recent_deploys measures 78 too, and so does trigger_deploy, because all three share a shape: a name, a description, and an inputSchema, the block telling the model what it may fill in. The inputSchema carries most of the weight, and it exists for a reason. The model is being asked to decide to make this call, and to parameterize it.

Now the step that turns a one-time cost into a per-turn one. Hosts drive the model through a completions API, and the tools array is part of the request. Every turn where the model might call a tool, the whole array goes along: every name, every description, every inputSchema, again. The 2026-07-28 revision made the protocol itself stateless, which pushes the same way: the request carries what the model needs, every time, and nothing server-side fills in the gaps. The site’s own MCP servers chapter already warns that a server exposing 60 tools can become a 60-tool tax, depending on the host. That dependence is real: a host can put definitions in context immediately, defer schemas until first use, or index them. The common shape, and the one priced below, is the first: definitions resident from the first turn.

Count only the two reads. The trigger’s 78 tokens are the fair price of a real decision; they sit in the tool list in every design in this piece, so they cancel out of the comparison. The two reads are the misclassification, and they are what the arithmetic counts. Two reads at 78 tokens each is 156 tokens of schema riding along per turn. Across the eight-turn session: 156 times 8 is 1,248 tokens of schema, sent on the two turns where the model actually calls the reads, and sent again on the six turns where it never touches them. On turn six, while the model weighs whether build 4821 is safe, both reads’ schemas are still sitting in its tool list as options it could pick.

Remember 1,248: the schema deadweight two misclassified reads cost one ordinary session, priced before any fix has been named.

The same reads as resources: 25 tokens, no schema

Section titled “The same reads as resources: 25 tokens, no schema”

Re-sort the two reads onto the lever the spec built for them, and pay the plant off.

A resource is read-only data the host application fetches and injects: a file, a record, a config blob. The application decides when to pull it. Here is the production config as a resource, as the host sees it:

{
"uri": "deploy://config/production",
"name": "Production deploy config",
"mimeType": "application/json"
}

One hundred characters compact: 25 tokens. The recent-deploys resource measures 26. Notice what is missing. No inputSchema, nothing for the model to fill in, because the model never decides to call one. A resource is never a candidate in the tool list at all: the host either has injected the data by the time the model reads the question, or it has not, and the model is never asked to weigh should-I-call-this as an option, turn after turn. On turn six of the same session, the config facts are already in context and the tool list holds one entry instead of three.

Pay off the plant. All-tool design: 156 tokens of read-schema per turn, 1,248 across eight turns. Three-primitive design: zero per turn, zero across the session, because the reads are no longer in the tool list to be resent. The saving is exactly 1,248, matching the plant with no arithmetic slack. The config data itself sits deliberately outside the comparison: the model needs those facts under either design, so only the recurring schema cost is being counted. Every step of this is checkable with a pencil, which is the point of sizing the toy this way.

Two properties keep a resource from being a frozen one-time dump, and both came through the 2026-07-28 revision unchanged in shape. Each resource can carry annotations: an audience (user, assistant, or both), a priority from 0.0 to 1.0, and a lastModified timestamp. Mark the deploy-config resource priority 1.0 and you have told the host it is the most important context you serve; mark a verbose changelog 0.2 and you have flagged it as the first thing to drop when the budget tightens. And resources stay live mid-session: subscriptions go through the general subscriptions/listen request with a resourceSubscriptions filter, and the server pushes notifications/resources/updated when a resource changes, so the host re-reads and the model’s picture of production stays current without a single tool call.

A resource costs 25 tokens, never recurs, and never asks the model to decide anything: that is what the application-controlled lever buys.

The move deserves a name, and by now it should feel forced rather than clever.

Before deciding a capability’s primitive, ask one question: who should pull the trigger? Should the application decide this runs, should the model decide mid-conversation, or should a human reach for it deliberately? Let that answer, and not “can the model reach it,” choose resource, tool, or prompt.

Run the four capabilities through it. The deploy config: every session that asks about production wants it identically, so the application should decide. Resource. Recent deploys: same reasoning, same lever. Resource. Triggering a build: a decision with consequences, weighed fresh each time, so the model should decide, inside whatever confirmation gate you build around it. Tool. Gating a tool that genuinely is a tool, and deciding what a server may be trusted to touch once invoked, are separate questions with their own pieces (elicitation plus tool annotations, the confused deputy server, the server is the trust boundary). The review checklist: a procedure a person reaches for at review time, on purpose. Prompt.

The reflex design answered a different question, “what can the model call,” and sorted everything onto the one lever that answer exposes.

The trigger sort asks who should decide, and lets the control axis the spec already shipped do the sorting for you.

The third stop on the same toy, priced with the same yardstick.

A prompt is a templated workflow the user invokes, surfaced by the host as a slash command. Here is the deploy review as the host lists it:

{
"name": "deploy_review",
"title": "Review a build for production",
"description": "Run the pre-production deploy checklist",
"arguments": [{ "name": "build_id", "required": true }]
}

One hundred seventy characters: 43 tokens for the listing. The template body, the checklist itself, ships inside the prompt and enters the conversation once, on the turn a human types /deploy_review 4821. Like a resource, and unlike a tool, nothing here recurs in the model’s tool list while it sits unused. The zero it earns in the table below is a different zero from the resource’s, and the difference matters: the resource’s data was needed in the session and got injected; the prompt carries a workflow, and was never a read at all.

This section stays thin on purpose. What a prompt buys a team once you have decided to ship one, how to embed resources into its messages, the completion API, and when not to ship a prompt are covered in depth by write the prompt so your team doesn’t have to. That piece begins where the classification decision is already made. This piece is about the decision itself, and the per-turn price of skipping it. (If your real goal is bundling that slash command together with subagents, hooks, and servers into one shareable unit, that is a packaging question, and package the workflow is its piece.)

A prompt puts the workflow on the user’s lever at 43 tokens standing by, and costs the model’s window nothing until a human invokes it.

Convert the plant into the two currencies this site keeps separate.

Window space first, because it is the axis that does not go away. On a 200,000-token window, 1,248 tokens is 0.624 percent of everything the model will ever hold, spent on schemas nobody needed. That is one session of one server with two misclassified reads. The table at the end runs the same arithmetic at larger miscounts.

Dollars, with the label on them. At Claude Opus 5’s list input price of $5 per million tokens, 1,248 tokens costs $0.00624 uncached. Worst case, stated as a ceiling: most real hosts get prompt-caching discounts on a repeated, unchanged block like a tool schema, so the actual bill is smaller than the raw count suggests. If the dollar figure is what ends your interest, notice what the concession does and does not buy. A cached token still occupies the window, still sits between the model and the conversation, on every turn. The price-versus-footprint split is the one stop reading your codebase in the window already drew for search traffic, and it applies unchanged to schema the model never needed at all.

The dollars are a rounding error; the window occupancy is the bill that never clears.

“Make every read a resource” overshoots, and the honest boundary is worth drawing before the table hardens it.

Some reads have a parameter the model genuinely computes at runtime. Search is the clearest case: “find the deploy that introduced this regression” carries a query the model composes from its reading of the problem, over a space you cannot list in advance. Choosing what to read is itself the work there, so the model-controlled lever is right, and the inputSchema is the fair price of it. A config lookup does not meet that bar. A code search does.

For the parameterized-but-enumerable middle, the spec ships a dedicated shape: a resource template, a URI with variables the host fills in on demand.

{
"uriTemplate": "deploy://config/{environment}",
"name": "Deploy config by environment",
"mimeType": "application/json"
}

One template covers staging, production, and whatever environment gets added next, without registering one tool per environment. And what a tool returns once it is one is a separate question again: type your tool boundaries gives a tool a declared output schema so a UI can trust the structured result over the agent’s prose.

Keep lookups on resources and templates, let genuinely investigative reads stay tools, and keep that tool set small enough to defend item by item.

Everything above reduces to one table. The control column is the spec’s own, dated 2026-07-28; the sizes are this piece’s toy; the last column is the session already priced.

PrimitiveWho controls it (MCP spec, 2026-07-28)Definition size (this toy, tokens)Recurs in the tool list every turn?8-turn cost from these 2 reads
ResourceApplication25No0
ToolModel78Yes1,248
PromptUser43No0

Read the tool row precisely. The 1,248 is what the two reads cost when they are modeled as tools; the trigger’s schema also recurs, in every design, and that recurrence is the honest price of a real action. The prompt’s zero means it was never a read to begin with.

One axis, three levers, three sizes, one recurring cost: this table is the whole piece compressed.

Four limits, said out loud rather than implied away.

The dollar figure is a worst-case ceiling. It assumes no caching, most hosts cache, and the window occupancy underneath it is the part that survives.

This piece is anchored to the 2026-07-28 specification, and the dating matters more than usual: that revision is the largest since MCP launched. Sessions went stateless, the initialize handshake is gone, and Sampling, Roots, and Logging entered a formal 12-month deprecation window. All of that is named here and then deliberately set aside. The three server primitives this piece sorts came through that revision intact, and the who-controls-it mapping is quoted from the revision’s own pages.

The trigger sort is necessary, not sufficient. A server can sort every capability correctly and still register twenty genuine model-controlled actions, and every one still pays its own per-turn schema tax, with no primitive-level shortcut. How many tools is too many is a real question, and it is handed forward below rather than answered here.

The token counts are the declared chars-per-four approximation throughout; no tokenizer ran. The ratio between definition sizes and the linearity of the tax carry the argument, and both survive any consistent estimate. The absolute counts are illustrative.

Nothing here fixes a server with too many real tools; it stops reads from pretending to be them.

Close on the same map, replayed row by row.

Resource: application-controlled, 25 tokens, never recurs, zero across the session. Tool: model-controlled, 78 tokens, recurs every turn, 1,248 for these two reads across eight turns. Prompt: user-controlled, 43 tokens, never recurs, zero, because it was never data the model needed.

Widen one column and the shape holds past the toy. The same two formulas, per-turn tax equals reads times 78 and session tax equals per-turn times turns, run at growing server maturity:

Reads misclassified as toolsPer-turn tax (tokens)8-turn total (tokens)Share of a 200,000-token window
21561,2480.62%
53903,1201.56%
129367,4883.74%

The twelve-read row is this server as many teams actually ship it, a flat list of a dozen near-identical verbs: 7,488 tokens of schema per ordinary session, 3.74 percent of the window, spent re-offering choices nobody needed. The tax is linear in the miscount and linear in turns. It never compounds cleverly, and it never turns off: it is a flat rate charged on every turn for as long as the server stays connected.

Two questions this piece hands forward rather than pretends to settle. First, the trigger sort tells you which primitive a capability belongs on, never how many capabilities legitimately belong on the model’s axis at all: a correctly sorted server can still hand the model real actions by the dozen, and each keeps paying its own per-turn tax, with no shortcut priced here. Second, the three primitives this piece sorted are the base the spec’s newer extensions, Tasks, Skills over MCP, and MCP Apps, build on top of, and none of them are explored here.

Sort every capability by who should pull the trigger, and the tax that remains is at least the tax you chose.


About the numbers. The four capability definitions, their compact character counts (312, 311, 309, 100, 101, 170), the token sizes derived from them (78, 78, 78, 25, 26, 43), the eight-turn session, and every figure built on those (156 per turn, the 1,248 plant and payoff, 3,120, 7,488, 0.624 percent, $0.00624) are toy numbers authored for hand-tracing and re-checked by a script before publishing. The token counts use the declared chars-per-four approximation, so the ratios are load-bearing rather than the absolute counts. Claude Opus 5’s $5 per million input tokens is quoted from Anthropic’s own pricing page, fetched August 16, 2026, and will drift. The who-controls-it mapping, the annotations shape, and the subscriptions mechanism are quoted from the MCP specification dated 2026-07-28 (its server-concepts and resources pages, fetched August 16, 2026); the stateless redesign and the 12-month deprecation of Sampling, Roots, and Logging come from the protocol’s own 2026-07-28 release announcement. The 200,000-token window is this site’s standing convention, not a measured limit of any model.

For the per-tool mechanics, see MCP servers for building the server, Slash commands for surfacing prompts, and Rules for the broader pattern of writing a convention down once.