Most MCP servers use one primitive out of five
If you've built an MCP server, odds are it exposes tools. Tools are the obvious primitive: the model decides to call something, gets a result back, moves on. It's the pattern every MCP tutorial leads with, and it's genuinely the most common integration point in production today.
But the Model Context Protocol specifies five primitives, not one — three on the server side, two on the client side — and four of them are quietly underused. This post goes through all five, the framework for deciding when to use which, and where the protocol is headed next: away from local processes and onto the web.
The interaction model: who decides when
Before the individual primitives, it's worth naming the thing that actually organises them, because it's easy to miss in the spec text. Call it the interaction model: the same underlying data can be exposed to a client in three different ways, and the difference is who decides when it shows up in context.
- Prompts are user-driven. The user chooses to pull something into the context window — typically via a slash command in the client.
- Resources are application-driven. The host application (an IDE, a desktop client) decides what to do with the raw data a server exposes.
- Tools are model-driven. The model decides, mid-conversation, whether to call something.
Once you have this framing, most "should this be a tool or a resource?" questions answer themselves: it depends on who should be in control of the decision to invoke it.
Prompts: pre-defined templates the user invokes
Prompts are predefined templates for AI interactions that a server exposes to a client. Concretely: a server can define a named prompt — say, summarize-pr — that a user triggers directly, usually through a slash command in whatever client they're using.
Two things make prompts useful beyond "canned examples":
They're code, not static text. A prompt handler runs server-side, so it can fetch live data before returning the template. A prompt that pulls in the comments on your current GitHub pull request and drops them straight into the context window is a normal, small implementation — a few lines of server code, not a special case.
They support argument completion. If a prompt takes parameters, the server can expose a completion handler so the client can show the user a picker — for example, a dropdown of the user's open pull requests — instead of asking them to type an ID from memory. This is genuinely easy to implement in most SDKs: one function that generates the prompt, one function that returns completion candidates.
The distinction that matters: prompts are things the user explicitly decides to add to context, before they ask the model to do anything with it. That's different from a tool, which the model invokes on its own judgment mid-task.
Resources: raw data, exposed for the application to use
Resources expose raw data or content from a server — file-like objects a client can read. Where prompts are text snippets meant for direct context injection, resources are closer to "here's a data source; do what you want with it."
There are two distinct ways clients use resources:
- Direct injection. Most clients today let a user attach a resource to the context window much like a file attachment — a database schema, a config file, a document.
- Application-side processing. Because resources are just data exposed through a stable interface, an application can do more than paste it into context: build embeddings over it, index it, run retrieval-augmented generation against it, and only pull in the most relevant slices when needed.
The second pattern is largely unexplored in the current MCP ecosystem. Most implementations treat resources as "files you can attach," which works, but leaves the RAG-style use case — indexing a resource corpus and retrieving against it — mostly untouched by existing servers.
A concrete example of the simple case: expose a database schema as a resource, attach it in a desktop client the way you'd attach a file, and ask the model to diagram it. The model doesn't need a tool call to "fetch the schema" — it's already sitting in context because the client pulled it in as a resource.
Tools: the one everyone already knows
Tools are actions the model can invoke — the primitive most MCP servers exist to expose, and the one most documentation already covers well. The short version: the model decides when to call a tool, based on the conversation, and gets a result back. If you've built an MCP server, you've built this.
Sampling: letting the server borrow the client's model
Here's a problem worth stating precisely. Say you want to build an MCP server for an issue tracker that can summarise a discussion thread. Fetching the thread is a normal tool or resource. But summarising it requires a model — and the server doesn't know which model the client has configured, doesn't want to ship its own SDK and API key, and definitely doesn't want to ask the end user for a separate credential just to use one feature.
Sampling is the primitive that solves this: it lets a server request a completion from the client, instead of bringing its own model integration. The server sends a sampling request; the client — which already has a model configured, and already has the user's trust and billing relationship — returns the completion.
This does two things:
- The client keeps control. Security, privacy, and cost stay with whoever is already trusted with them, instead of a third-party server needing its own credentials and its own data-handling story.
- It makes chaining recursive. If you compose MCP servers — a tool call that, mid-execution, invokes further MCP servers downstream — sampling requests can bubble all the way back up to the original client. The client stays in control of cost and privacy no matter how deep the chain goes. This is one of the more direct paths toward genuinely agentic, multi-server MCP systems rather than single-hop tool calls.
Sampling is also, honestly, one of the least-supported primitives in clients today. Anthropic's own first-party products are expected to add support for it, but broad client support is still catching up to what the spec allows.
Roots: telling the server what's actually open
Roots let a client tell a server what it should consider "in scope" — typically, which project directories or workspaces are currently open. The canonical case: an MCP server that runs git commands on your behalf needs to know which working directory to operate in. Roots is how the client answers that question, rather than the server guessing or requiring manual configuration.
It's a smaller, more specific primitive than the other four, and adoption reflects that — some IDEs (VS Code among them) support it, but it's far from universal. Still, if you're building a server that operates on local project state, roots is the correct mechanism rather than a workaround.
Composing all five into one integration
Here's what it looks like when the primitives work together, using a hypothetical MCP server for a chat platform (Slack, Discord, whatever):
- Prompts give the user a starting point: "summarise this thread," "what's new since yesterday" — with completion over recent threads or channels so the user doesn't have to type an ID.
- Resources expose the channel list and recent thread content so the client can index or reason over it directly.
- Tools cover the actions: search, read a channel, read a thread.
- Sampling handles the summarisation step itself, using whatever model the client already has configured.
None of this is exotic — it's the same five primitives, arranged so that each interaction happens through the mechanism that matches who should be deciding it. The result is a materially richer integration than a tools-only server, without adding any new concepts beyond what the spec already provides.
Where MCP is going: off the laptop, onto the web
Of the roughly 10,000 MCP servers the community has built over the protocol's first six to seven months, the large majority are local — a Docker container, a local executable, something you run on your own machine. The next major direction for MCP is moving servers off local processes and onto the web: an MCP server that's simply a website your client connects to, instead of something you download and run.
That shift needs two things: authorization and scaling.
Authorization: OAuth 2.1
The current specification requires OAuth 2.1 for authorization — in practice, OAuth 2.0 with the security practices you should already be following, formalised. This unlocks two patterns:
Consumer-facing remote servers. A provider — say, a payments company — can expose mcp.provider.com as a remote server. The user connects their MCP client, goes through an OAuth login, and now has tools scoped to their own account, backed by a provider they already have a trust relationship with — not an unknown third-party binary running locally. The provider can also update the server continuously, without users needing to pull a new image.
Enterprise-internal servers behind existing SSO. A company can deploy an MCP server internally and put it behind whatever identity provider they already use for single sign-on — Entra ID, Okta, or similar. Employees authenticate the way they always do, and from then on, every MCP interaction inherits that identity automatically. This cleanly separates two jobs that used to be tangled together: teams that build the MCP server itself, and teams that own identity and access — the same separation of concerns most enterprises already apply everywhere else.
Scaling: Streamable HTTP
The other requirement is a transport that scales the way ordinary APIs do. Streamable HTTP is the answer: a simple tool call can return a result directly, much like a REST response — request in, JSON out, connection closed. But when an interaction needs more — a notification, a sampling round-trip — the server can open a stream and send additional messages before returning the final result. Same transport, two modes, chosen based on what the interaction actually needs.
Together, authorization and scaling are what let MCP move from "a protocol for local dev tools" to a plausible standard for how LLM applications talk to the web generally.
What's coming next
A few items on the roadmap worth tracking:
- Asynchronous tasks — primitives for longer-running agent work (hours, not seconds), not just request/response tool calls.
- Elicitation — a way for a server to ask the user for input mid-interaction, rather than failing or guessing.
- An official registry — a central place to find and publish MCP servers, including support for agents to discover and install servers dynamically.
- Multimodality — streaming results and other capabilities beyond text.
- More official SDKs — a Ruby SDK contributed by Shopify, and an official Go SDK in progress.
Why this matters beyond the spec
MCP is Anthropic's protocol, but it's an open specification, and the primitives above are available to any server or client that implements them — Claude included. The part that's easy to miss from the outside is that "MCP support" isn't a single checkbox. A server that only implements tools and a server that implements all five primitives plus remote authorization are both "MCP servers," but they enable very different classes of integration.
That gap is exactly where we spend most of our time when we build agent infrastructure for clients: not wiring up a single tool call, but designing which primitive fits which interaction, and — increasingly — putting the authorization layer in front of it so an MCP server can sit behind existing enterprise SSO rather than a locally-run binary nobody centrally controls. If that enterprise-SSO pattern above sounds like something your own IT team is trying to figure out, that's the exact shape of what we build under Enterprise Solutions.
Read also: Claude Cowork: When the AI Agent Moves to Your Desktop — And What Norwegian SMBs Must Structure First · Claude in the Browser Is Not an AI Strategy