Skip to main content

MCP Has Six Primitives, Not Three. Here's the Half You're Ignoring.

· 12 min read
TheMCPGuy
MCP Developer & Educator

A mirror split down the middle: on one side, the symbols for Tools, Resources, and Prompts; on the other, the symbols for Roots, Sampling, and Elicitation, identical in weight but reversed

Pop quiz. Name the MCP primitives.

If you've read any MCP introduction in the last eighteen months, you said: Tools, Resources, Prompts. You're right. Those are the things you build, the things you expose, the things every tutorial walks you through.

You are also wrong, in the sense that you are halfway right.

MCP has six primitives. The three you know are server-side: things your server exposes to the client. The three nobody seems to want to talk about are client-side: things the client exposes to your server. They are Roots, Sampling, and Elicitation, and the reason they get ignored is exactly the reason you should care about them.

Why You've Never Heard of the Other Half

The first six months of MCP content focused, sensibly, on the obvious story: you build a server, it exposes tools, the AI uses them. That's the part most developers need first. Most people writing tutorials never get past it.

But MCP is not actually a one-way protocol. The spec is fully bidirectional. Servers can ask clients for things, not just respond to client requests. The client capabilities are how that conversation works.

The asymmetry of attention is partly an SDK story. Until quite recently, most Java and Python frameworks made the server-side primitives much easier to use than the client-side ones. If you were building with Spring AI, you were probably writing @Tool-annotated beans long before you knew you could write a RootsHandler. The protocol always supported it; the ergonomics didn't.

That has changed in the last year. The 2025-06-18 spec made the client-side primitives more useful by adding Elicitation. The 2025-11-25 spec polished the corners. And the SDKs are catching up. If you only know the three server-side primitives, you're working with half the protocol.

Update (2026-07-31): the deprecation is no longer a proposal

When this post was published, deprecating three of these six primitives was only a draft proposal. It is now ratified. The 2026-07-28 specification, published 28 July 2026, deprecates Roots, Sampling, and Logging (SEP-2577), with suggested migrations toward tool parameters, direct LLM-provider integration, and OpenTelemetry respectively. Deprecated does not mean removed: the same revision introduced a feature-lifecycle policy guaranteeing a minimum twelve-month window, and these features remain fully functional throughout it, but new implementations should not adopt them. Elicitation is not deprecated and remains the most actively recommended client-side primitive, though 2026-07-28 changes how it is delivered: server-initiated requests are replaced by the Multi Round-Trip Requests pattern, where the server returns resultType: "input_required" and the client retries with the answers. Read the rest of this post in that context.

Primitive 4: Roots (The Server Asks "What Am I Allowed To See?")

Suppose you write a filesystem MCP server. It can read files. Wonderful. Now: which files?

The server doesn't have arbitrary access to the user's machine. It can't. Imagine the security disaster if it did. Some entity has to decide what's on-limits and what isn't. That entity is the client, because the client is the part that knows what the user has authorised.

Roots are the mechanism for that conversation. When a client supports Roots, it advertises that it can answer the question: "Here are the directories (or, more generally, URI scopes) that this server is allowed to operate on." A filesystem MCP server can call roots/list on the client to find out: maybe the user has granted access to ~/Documents/work and nothing else. The server then knows the boundary of its world.

If the user adds a folder to the workspace, the client emits notifications/roots/list_changed. The server re-queries. The boundary updates live, mid-session, without anyone reconnecting.

What this enables in practice:

  • Workspace-aware servers. Your IDE plugin (Cursor, Continue, Claude Code) can tell the MCP server which project the user is currently working in. The server scopes everything to that project. Switch projects, the scope changes.
  • Multi-tenant clients. A single client can serve multiple users; each user has their own roots; the server doesn't have to manage that mapping.
  • Principle of least access at the protocol layer. The server cannot accidentally read outside its roots, because there's an explicit protocol-level fence. This is much stronger than "we'll just be careful in the code."

If you've ever shipped an MCP server that took a base_directory configuration parameter at startup time, Roots are the more honest version of that pattern. The user, through the client, gets to dynamically tell you what counts as the base.

Primitive 5: Sampling (The Server Asks the AI for Help)

This one bends people's brains, so we'll go slowly.

When you think of an MCP server, you think of something the AI uses. The AI calls a tool; the server runs code; the result goes back to the AI. The intelligence is in the AI; the server is just hands.

But what if the server needs the intelligence too?

Suppose your MCP server fetches a web page and needs to summarise it. Or fetches a code file and needs to extract its dependencies. Or processes a customer support ticket and needs to classify its sentiment. These are tasks that ought to use an LLM. But your MCP server doesn't have direct access to one. It doesn't have an API key. It doesn't know which provider the user is using. It doesn't know if the user wants their requests sent to a third party at all.

Sampling is the protocol's answer. The server can call sampling/createMessage on the client to say: "I have a prompt. Please run it through whatever model you're using, with whatever budget you allow, and give me the response." The client owns the model relationship. The client can refuse, log, redact, change models, or budget the request. The server doesn't have to think about any of that. It just gets a completion.

This is what makes truly composable agents possible. Without Sampling, every MCP server has to either:

  1. Be entirely deterministic (no LLM reasoning inside the server), or
  2. Bring its own model (with all the configuration, secrets, and surprise-vendor-coupling that implies).

With Sampling, the server can borrow the client's brain. A summarise_url tool that uses Sampling internally to produce a high-quality summary, in whatever model the user has chosen, with the user retaining full control. That's a kind of architectural cleanness you can't get any other way.

The caveat, and it's a real one: Sampling has historically been one of the least-implemented capabilities. Many clients don't support it. In fact, the current draft spec proposes to deprecate Sampling (alongside Roots and Logging) under SEP-2577, with the suggested migration being direct integration with LLM provider APIs from inside your server. Sampling is still in the stable spec today, but if you're designing new servers, weigh that direction carefully and check whether your target clients support it before depending on it.

Primitive 6: Elicitation (The Server Asks the User a Question)

This is the newest of the client-side primitives. It landed in 2025-06-18 and is the one most likely to change how you design tools.

Picture this. Your MCP server has a deploy_service tool. The user (through the AI) asks to deploy payment-service. Your tool is ready to run, but you need one more piece of information: which environment? Staging, production, or canary?

Before Elicitation, you had four bad options:

  1. Make the AI ask the user in chat. ("Which environment?") The user answers, the AI parses, the AI re-calls your tool. Three round-trips, hope nothing gets lost in translation.
  2. Add environment to your tool's required parameters and hope the AI guessed right.
  3. Pop up an OS-level dialog. Out of band. Awful UX.
  4. Just pick a default and pray.

Elicitation is option 5. Mid-tool-call, the server can call elicitation/create on the client with a JSON Schema describing the structured input it needs. The client surfaces a form to the user, in the host UI, that exactly matches that schema. The user fills it in. The response comes back to the server as a validated object. The tool continues.

The user always retains the right to cancel. The schema is what the server asked for, but the client owns the rendering. The conversation in chat is uninterrupted.

What this changes:

  • Structured questions stop bouncing through the model. Previously, "ask the user" had to happen via the model, which meant the model had to understand the question and the answer. Now the question can be structured (a date picker, a dropdown, a typed text field) and the model just receives the validated answer.
  • Trust boundaries become cleaner. The server explicitly admits when it doesn't have all the information. The user explicitly approves before the operation proceeds. No more "the AI just decided to deploy to production because it interpreted my sentence aggressively."
  • The "are you sure?" pattern gets a protocol-level home. High-risk operations can elicit explicit confirmation, with the confirmation being a typed response, not a chat string the model might paraphrase.

This is the primitive I'd predict will reshape tool design over the next year. Once developers internalise that "ask the user a structured question" is a protocol-supported operation, a lot of tools that currently overstuff their parameter schemas will quietly transition to "elicit what's missing."

Why the Asymmetry Matters

The three server-side primitives encode what your server provides. The three client-side primitives encode what your server can ask for. Both are about explicit capabilities, declared at the start of a session.

This explicit declaration is the part that makes MCP feel different from other AI integration approaches. In a normal SDK, you have to assume the worst about what the other side can do. You write defensive code. You handle the cases that may never arise. You build for the lowest common denominator.

In MCP, the capability declaration at session start tells you exactly what the other side can do. If the client doesn't advertise sampling, you don't call sampling/createMessage, and you also don't have to write code defending against the case where it might not work. If the client advertises elicitation, you can write your tool assuming you can ask for structured input. The contract is upfront.

That bidirectional capability negotiation is what makes MCP, in my view, a serious protocol rather than a thin convention around tool calls. The other half of the primitives is where it shows.

Practical Translation

If you're designing an MCP server today, here are concrete questions to ask:

On Roots:

  • Does my server have a notion of "scope"? Could it benefit from the client telling me what scope to use?
  • Am I currently accepting a base_directory (or workspace, or project) configuration parameter that should really be dynamic?

On Sampling:

  • Are there steps inside my tool implementation that would benefit from an LLM call (classification, summarisation, decision-making)?
  • If yes, am I currently solving that by either (a) being deterministic, or (b) bringing my own model? Could Sampling let me offload that decision to the client?

On Elicitation:

  • Are there optional parameters in my tools that the model frequently gets wrong, that a structured question to the user would always answer correctly?
  • Are there high-risk operations where I'd like explicit user confirmation outside the chat stream?

The first time you ask these questions, you'll probably find at least one tool in your server that would be better if it used one of the client-side primitives. That's how I knew, the first time, that the other half of the protocol was load-bearing.

The Bigger Picture

The fact that "MCP has three primitives" became conventional wisdom is a textbook case of protocols being learned through tutorials rather than specs. The tutorial writers wanted to ship the minimum viable mental model. The minimum viable mental model is "tools, resources, prompts." That's good enough to get someone building. It's also wrong by half.

If you're building servers, the three server-side primitives are where you'll spend most of your time. Roots, Sampling, and Elicitation are where you'll unlock the next layer of capability. Many of the patterns that look hard in a server-only world (live workspace scoping, embedded reasoning, mid-tool user input) become clean when you remember that the protocol is two-way.

So: MCP has six primitives. Tools, Resources, Prompts on one side. Roots, Sampling, Elicitation on the other. They're not optional. They're not advanced. They're the part of the protocol that explains the design choices in the part you already know.


For the protocol-level mechanics, the Capability Negotiation module walks through how clients and servers declare and discover these capabilities at session start.

For a deeper dive on Elicitation specifically (the human-in-the-loop primitive), the Human in the Loop module of the Agentic Workflows course shows how to design tools around it — that course is coming soon.

And if you want to see how all six primitives compose into a real architecture, the MCP Architecture Patterns course threads them through the bigger picture. It is also coming soon.

Three primitives is the bumper sticker. Six is the protocol.