Read the Model Context Protocol spec and you find a genuinely elegant idea hiding in a small table. A server can expose three kinds of capability — Tools, Resources, and Prompts — and the spec organizes them not by what they do but by a single axis it names the control hierarchy: who decides when this context enters the model. Tools are model-controlled. Resources are application-controlled. Prompts are user-controlled. Three primitives, three different answers to "who's in charge here."

It's a clean piece of design. It's also a near-perfect example of a spec that's more orderly than the world that implements it. Because in practice, of those three lanes, the ecosystem paved exactly one.

The three primitives, by who pulls the trigger

Tools are the famous ones — and the spec is explicit that they are model-controlled: "the language model can discover and invoke tools automatically based on its contextual understanding." A tool has a name, a description, and a JSON-Schema inputSchema; the model calls it (tools/call) when it judges it's useful, and tools can have side effects — write a file, POST to an API. That power is exactly why the spec attaches a warning that there should always be a human able to deny an invocation. Tools are the action lane. This is the same machinery underneath the function-calling MCP was designed to standardize.

Resources are the quiet sibling: read-only data identified by a URI — a file's contents, a database schema, git history. The spec calls them application-driven: the host application "determines how to incorporate context based on its needs," whether that's selecting relevant portions, searching them with embeddings, or passing them whole. Resources support resources/read, change notifications, and Resource Templates — RFC 6570 URI templates like file:///{path} that parameterize a whole family of resources. They are designed to be passive context, not actions.

Prompts are user-controlled: templates "exposed from servers to clients with the intention of the user being able to explicitly select them," typically as slash commands. They take typed arguments and can even embed a resource directly into the message. Prompts are the lane where the human, not the model, decides to pull in a server's capability.

The asymmetry that decides everything

Here's the mechanical fact that the tidy table hides, and it's the whole story: the model cannot fetch a Resource the way it calls a Tool. Tool invocation is a model affordance. Resource access is a client-side operation keyed by URI — the application reads the resource and decides whether to inject it into context. The LLM has no protocol move to go get a resource on its own. Unless the client surfaces it — a file picker, an attach-context menu — the model simply never sees it.

This is where a common misconception needs correcting. People assume the model "browses" Resources or requests them mid-thought like Tools. It doesn't. Resources are application-controlled by design; Prompts are user-controlled; only Tools are model-controlled. If your client doesn't implement a resource-picker UI, your beautifully-designed Resource is invisible.

The protocol answers "who controls this context?" three different ways. The market answered it one way: the model, via Tools, because that's the lane that works everywhere.

Why "everything is a Tool" is rational, not lazy

Lay the design intent next to reality and the gap is stark. The spec wants read-only context to live in Resources and user-triggered workflows to live in Prompts. But PulseMCP's survey of the client landscape describes an ecosystem "stuck catering to the lowest common denominator," where most clients support only the most basic protocol features. Tools are near-universal. Resources and Prompts are unevenly implemented — some clients surface them, many don't, and where Resources exist the UX varies wildly. (The canonical client feature matrix is the place to check before you depend on anything beyond Tools.)

So a server author who wants their read-only data to actually reach the model faces a choice: expose it as a Resource the spec endorses but half the clients ignore, or wrap it as a Tool every client supports. The "wrong" answer — ship read-only context as a Tool — is the correct engineering call today. It costs a model round-trip (the LLM has to decide to call the tool instead of the app just attaching the data) and it bloats the tool list, which is its own scaling problem. But it works, and "works everywhere" beats "elegant but unreachable."

How to design around it

The lesson is the same one the protocol's other under-implemented features keep teaching: treat Resources and Prompts as progressive enhancement, not load-bearing structure. Assume Tools work and build your server's core capability there. Add Resources for the clients that support them — they're genuinely better for large read-only context a user shouldn't have to trigger — but never let your server become useless when the client ignores them. Offer Prompts as a nicety for slash-command clients, not as the only door in.

And don't expect the 2025-11-25 revision to rescue you: it didn't redefine the primitives. The control model and methods are unchanged; the only primitive-level addition was letting servers attach icons. The architecture is settled and good. The thing still maturing is the part the spec can't legislate — whether the clients ever pave the other two lanes.