Skip to content
mnzes

What are MCP's primitives?

ByDiógenes MenezesLearning AI in public

12 min read

MCP has three server primitives: tools, which the model invokes; resources, which the application attaches to the context; and prompts, which the user picks on purpose. The specification calls the split a control hierarchy, and it does not sort the three by the shape of the data. It sorts them by who decides when the thing enters the context1.

That sentence settles almost every classification question. The same sales report can be a tool, a resource or a prompt, and the right answer depends on which actor pulls the trigger rather than on the content.

All three show up in the Model Context Protocol as what a server exposes. If you are only ever going to write tools, the text describing each one deserves its own attention, because it lands inside the model’s reasoning. And if the question is how any of this turns into a call to your model provider, the comparison lives in MCP or function calling.

the modeltoolsfires it alonetools/callreads or writesthe applicationresourcesattaches itresources/readread-only, has a URIthe userpromptspicks itprompts/getslash command
Figure 1The primitive describes the trigger, not the data. Only one of the three can be fired by the model mid-task, and that is the one carrying the risk.

Tools: what the model fires

Tools are executable functions. The client discovers them with tools/list and invokes them with tools/call, passing arguments that must match the tool’s inputSchema, a JSON Schema that defaults to draft 2020-12 when no $schema is present2.

A tool may also declare an outputSchema. When it does, the server must return structured results conforming to that schema in the structuredContent field, and the client should validate them. For backwards compatibility, the same serialized JSON usually also arrives as a text block in content2.

The detail most implementations get wrong is the error channel, because there are two. Protocol errors cover malformed requests, unknown tools and server failures, and come back as ordinary JSON-RPC errors. Execution errors come back inside the result, with isError set to true and the failure text in content. The specification spells out why: execution errors carry actionable feedback and clients should hand them to the model so it can self-correct, while protocol errors may be passed along but are less likely to result in successful recovery2. A server returning -32603 because a date was formatted wrong robs the model of the chance to retry with the right date.

Two small conventions with real effects. Tool names should run 1 to 128 characters, use only ASCII letters, digits, underscore, hyphen and dot, and be unique within the server. And servers should return tools in a deterministic order, because that lets the client cache the list and improves prompt cache hit rates when tools go into the model’s context2. Iterating a map with unstable ordering there costs actual money.

Tool or resource for the same data

the same sales reportas a toolthe model decidesto call itget_sales_reportenters context mid-task,if the model asks for itas a resourcethe applicationdecides to attach itsales://2026-07enters context before thetask, by the app’s choice
Figure 2The same report changes nature depending on who pulls it. As a tool it occupies context on every call; as a resource it only costs when someone attaches it.

Take a monthly sales report and expose it both ways.

As a tool, get_sales_report takes the month as an argument and the model calls it mid-task whenever it decides it needs to. The tool declaration sits in the context on every model call, used or not. The model picks the argument, which is good when the right month depends on the conversation and bad when the model picks the wrong month.

As a resource, the same report becomes sales://2026-07 with a stable URI and enters the context when the application attaches it. It occupies nothing in the prompt until then. In exchange, somebody has to choose: either the user, from a picker, or the application, by some heuristic. The specification says resources are application-driven, with the host determining how to incorporate context, and lists the possible patterns, including automatic inclusion based on heuristics or on the model’s own selection1.

The security difference falls out of that and usually goes unnoticed. The protocol gives the model no channel for reading a resource: resources/read is a client call. Text injected into a tool result can push the model into calling another tool, but not into pulling a resource on its own — unless the application chose to let the model select, which is one of the patterns the spec permits. If you are designing a server over sensitive data, exposing it as a resource moves the decision outside the model.

Resources: what the application attaches

Every resource is identified by a URI. The client lists them with resources/list and reads them with resources/read, and a single read may return several contents, which is what happens when the URI points at a directory1.

Parameterized resources exist as templates, in the RFC 6570 URI template format, listed under resources/templates/list. Their arguments can be auto-completed through the completion API, which is what lets an interface offer suggestions while the user types a path.

The specification defines a handful of URI schemes and is opinionated about one of them. Use https:// only when the client can fetch the resource straight off the web without going through the MCP server; for anything else, prefer another scheme or define your own, even when the server itself is the one downloading the content over the internet1. There are also file:// and git://, and nothing stops you from defining a scheme of your own.

Each resource can carry annotations: audience, whose values are user and assistant, priority between 0.0 and 1.0, and lastModified in ISO 8601. They are hints for the client to filter and prioritize what enters the context, not rules the protocol enforces.

One error rule prevents a silent bug: a missing resource returns -32602, and the server must not return an empty contents array for a resource that does not exist, because that is ambiguous between “exists and is empty” and “does not exist”1.

Prompts: what the user picks

Prompts are message templates the server exposes and the user invokes. The client lists them with prompts/list and resolves them with prompts/get, passing the declared arguments. What comes back is a list of messages with role user or assistant and content that may be text, image, audio, a resource link or an embedded resource.

The specification defines prompts as user-controlled and takes care to qualify the phrase: it refers to who decides when the prompt is used, not to who authors its content, which is defined by the server1. That distinction matters for anyone assessing risk. A prompt is not more trustworthy for being “the user’s”; it is server text the user agreed to paste into the conversation.

The typical case is the slash command, and the spec uses that very example. Arguments accept completion through the completion API, and the list can change at runtime, with the server notifying anyone holding an open subscription stream.

A concrete example makes it stick. A repository server exposes a code_review prompt with one required argument named code. In the editor it shows up as a command; the user selects a snippet, the client calls prompts/get with that snippet in the arguments, and gets back an assembled user message carrying the server’s review instruction with the code embedded. The model has seen none of this yet: what reaches it is the conversation with the finished message inside, indistinguishable from something the user typed.

How to decide

who decides when thisenters the context?toolside effects possiblethe model, mid-taskresourceread-only, has a URIthe app, while assemblingpromptmessage templatethe user, on purpose
Figure 3The deciding question is not what the data is but whose decision it is to bring it in. Answering that first settles most of the doubtful cases.

The question is who decides when the thing enters the context.

If the answer is the model, mid-task, with parameters it chooses on the spot, it is a tool. If it is the application or the user, before the task, over content with a stable identity, it is a resource. If it is the user deliberately starting a flow with a ready-made shape, it is a prompt.

A secondary test breaks the ties: side effects only exist in tools. If the operation writes somewhere, sends mail or charges someone, it is neither resource nor prompt, because neither one has execution semantics.

Where the split breaks down

Everything becomes a tool. This is the most common failure and the most expensive one. Servers expose reads as tools because tools work in every client and because the model can call them unaided. The cost lands in context: each tool declaration takes room on every call, and selection degrades as options multiply. A 2025 paper measured that degradation in an MCP stress test and reported tool selection accuracy of 43.13% against a 13.62% baseline, cutting prompt tokens by over 50% by retrieving only the relevant tool descriptions through semantic search3. Another paper that year attacked the same problem by organizing tools into a hierarchical taxonomy and filtering by query, with results pointing the same way4. Both numbers come from specific experimental setups rather than a law, but the direction is consistent across the two.

The other two depend on the host implementing them. A resource needs an interface to be picked from; a prompt needs somewhere for commands to appear. A server betting on resources may end up invisible in hosts that only built the tool path. Public evidence here is weak: the available studies measured configuration, SDK usage and oversight controls, not coverage by primitive5. Treat it as a portability risk, not a measured fact.

Descriptions remain untrusted input. True for all three primitives, but worst in tools, because the description is what steers the model’s choice. The 2025 study of 67,057 servers described exactly this path: attacker-controlled tool metadata shapes the model’s reasoning and induces operations the host executes without independent verification6.

“User-controlled” does not mean safe. The prompt’s content belongs to the server. The user controls the moment, and only that. A malicious prompt is injection by invitation.

The wrong error channel destroys recovery. A server that treats a business failure as a protocol error turns a problem the model would have solved into a dead stop. It is a two-line bug with a large effect on observed behaviour.

What each one costs

Order-of-magnitude figures, enough to see the shape of the bill.

A tool costs context permanently. Name, description and argument schema land in the hundreds of tokens per tool, resent on every model call. Thirty thin tools cost far more than five fat ones covering the same ground, and they make selection worse besides.

A resource costs almost nothing until it is read, and then costs the size of its content. It is the tool’s mirror image: expensive per use, cheap to exist. That is why a large catalogue of resources is workable and a large catalogue of tools is not.

A prompt costs the size of the messages it returns, and only when the user invokes it. The prompts/list entry itself is small.

The practical consequence is a single one: whatever you expose as a tool, you pay for always.

Where to start

  1. Start with a handful of read-only tools. They work in every client and give the fastest signal on whether the design makes sense.
  2. Move anything with a natural URI to a resource. Files, documents, records with stable identifiers. That takes weight off the prompt and takes the decision away from the model.
  3. Write the tool description as if it were the only documentation. It is the one part of the declaration the model reads before deciding.
  4. Separate the two error channels on day one. isError for domain failures, JSON-RPC errors for invalid requests.
  5. Return the tool list in the same order every time. It costs one line and improves prompt caching.
  6. Measure the size of your tool prefix before adding the next one. Once the list is past a dozen, your next problem is selection, not capability.

Step 2 is the one most servers skip, and it is why tool lists grow until the model starts choosing badly.

Footnotes

  1. The server primitives page of revision 2026-07-28 is the source for the control hierarchy, the application-driven model for resources, the URI schemes, the annotations and the definition of prompts as user-controlled. Verified on 30 July 2026. 2 3 4 5 6

  2. The tools page of the same revision is the source for inputSchema and outputSchema, for structuredContent, for the two error channels, for the naming rules and for the deterministic ordering recommendation. 2 3 4

  3. Gan and Sun (2025) measured, in an MCP stress test, tool selection accuracy of 43.13% against a 13.62% baseline by retrieving only the relevant descriptions through semantic search, cutting prompt tokens by more than 50%.

  4. Antonioni et al. (2025) organized MCP tools into a hierarchical taxonomy and filtered by query, reducing prompt size without meaningfully compromising capability and improving selection as the tool count grows.

  5. Majeed et al. (2026) characterized 1,723 MCP-consuming applications by configuration, SDK usage and human-oversight mechanisms, without breaking the data down by primitive.

  6. Li and Gao (2025) described how attacker-controlled tool metadata shapes the model’s reasoning and induces operations the host executes without independent verification, based on 67,057 servers across six registries.

Frequently asked questions

What are MCP's primitives?
Three, all exposed by the server: tools, which the model invokes; resources, which the application attaches; and prompts, which the user picks. The specification calls this a control hierarchy, and it is the key to the split: each primitive has a different actor deciding when it gets used.
What is the difference between a tool and a resource in MCP?
A tool is executable and the model invokes it on its own with tools/call, possibly with side effects. A resource is content identified by a URI, read through resources/read at the application's discretion. The practical difference is trigger and cost: a tool occupies context on every call, a resource only when attached.
When should I use a resource instead of a tool?
When the content is read-only, has a stable identity, and it makes sense for the user or the application to pick it before the task starts. A file, a database schema, a policy document. If the model needs to fetch it mid-reasoning with parameters it chooses on the spot, that is a tool.
What is the prompt primitive in MCP?
A message template the server exposes and the user deliberately invokes, typically as a slash command. The server returns a list of assembled messages from prompts/get with the arguments filled in. The user controls when the prompt is used; the content is still written by the server.
Can a tool return structured data?
Yes. The result can carry structuredContent, a JSON value conforming to the outputSchema the tool declared. Servers that declare an outputSchema must return conforming results, and clients should validate them. For backwards compatibility the same serialized JSON usually also arrives as a text block.
Is a tool error a protocol error?
No, and conflating the two is expensive. Business failures, input validation and API errors come back in the result with isError set to true, and the client should hand them to the model so it can self-correct. Protocol errors are for unknown tools and malformed requests, which models rarely fix.

References

  1. Agentic AI Foundation. Model Context Protocol specification, revision 2026-07-28 — Server primitives (2026)
  2. Agentic AI Foundation. Model Context Protocol specification, revision 2026-07-28 — Tools (2026)
  3. Gan, T., Sun, Q.. RAG-MCP: Mitigating Prompt Bloat in LLM Tool Selection via Retrieval-Augmented Generation (2025)arXiv:2505.03275
  4. Antonioni, E. et al.. JSPLIT: A Taxonomy-based Solution for Prompt Bloating in Model Context Protocol (2025)arXiv:2510.14537
  5. Li, X., Gao, X.. A First Look at the Security Issues in the Model Context Protocol Ecosystem (2025)arXiv:2510.16558
  6. Majeed, M. H. A., Mahmoud, M., Nadi, S.. An Empirical Study of Model Context Protocol Applications (2026)arXiv:2607.25635