6 Core MCP Primitives: Sampling, Roots, Elicitation, Tools, Resources, Prompts
The 3 client-side primitives (Sampling, Roots, Elicitation) and 3 server-side primitives (Tools, Resources, Prompts) that make MCP more than 'just tool calling' — including the model/app/user control framing and a unifying travel-booking example.
Establish Transport pipe connection
Host client (IDE or AI assistant) connects to the server process over standard output streams.
6 Core MCP Primitives
A single running example — an AI travel-booking assistant — showing all 6 MCP primitives at once. The 3 server-side primitives (Tools, Resources, Prompts) are exposed by the server to the client. The 3 client-side primitives (Sampling, Roots, Elicitation) are requests the server makes back to the client mid-task. Tools = model decides when to call them, Resources = the app decides, Prompts = the user decides.
6 Core MCP Primitives
▶📚 Prerequisites(1)
🎓 Learning objectives
- •Name all 6 MCP primitives and whether each is client-side or server-side
- •Explain the 'who controls invocation' framing: Tools (model-controlled), Resources (app-controlled), Prompts (user-controlled)
- •Describe what Elicitation adds that Tools and Resources alone cannot provide
- •Trace a concrete example (travel booking) through all 6 primitives
What is it?
Many developers think MCP is just another tool-calling standard, but that's only scratching the surface. Unlike simple tool calling, MCP creates two-way communication between AI apps and servers, formalized as 6 core primitives — 3 offered by the client (Sampling, Roots, Elicitation) and 3 offered by the server (Tools, Resources, Prompts). Together, these 6 primitives cover the full MCP surface: operations (Tools), structured context retrieval (Resources), user interactions (Prompts, Elicitation), server-initiated model access (Sampling), and access scoping (Roots).
Why it exists
If MCP only had Tools, it would indeed be 'just another tool-calling standard' with extra ceremony. The other 5 primitives exist because real AI-agent workflows need more than one-way 'model calls a function' interactions. Sampling exists because a server sometimes needs the client's LLM to make a judgment call (e.g., picking the best flight from a list) without the server needing its own separate LLM access. Roots exists because servers need scoped, sandboxed access to resources like the filesystem, not unrestricted access. Elicitation exists because some tasks genuinely can't be completed with information available upfront — a booking flow needs to ask about seat preference mid-task, not before it starts. Resources and Prompts exist to separate passive context delivery and user-directed workflow templates from the model-initiated action loop that Tools alone would create.
Problem it solves
Without Sampling, a server needing an LLM's judgment (e.g., 'which of these 5 flights best matches the user's stated preferences?') would need to bundle its own separate LLM integration, duplicating what the client already has. Without Roots, a server would either need full unrestricted filesystem/resource access (a security risk) or the client would need bespoke, non-standardized ways to sandbox each server individually. Without Elicitation, any task requiring mid-flow clarification (a booking needing a seat preference, a form needing a missing field) would have to either guess, fail, or force all possible inputs to be gathered upfront regardless of whether they're needed. Without the Tools/Resources/Prompts split, every capability would look like an undifferentiated 'function,' losing the useful signal of who actually controls when it's invoked (the model, the app, or the user) — which matters enormously for both UX design and safety.
Intuition
Think of a good travel agent (the MCP server) working with a client (you, via an AI app). Sometimes the agent needs your gut judgment on something nuanced ('which of these three flights do you actually prefer?') — that's Sampling, the agent borrowing your judgment rather than guessing. The agent is only allowed to look through your travel folder, not your entire filing cabinet — that's Roots, a scoped boundary. Partway through booking, the agent realizes they need one more piece of information from you (seat preference) that wasn't given upfront — that's Elicitation, a structured mid-task question. The agent can actually book the flight (Tools — an action with real effects), can pull up your calendar to check for conflicts (Resources — read-only reference), and can offer you a pre-built 'plan a vacation' package deal template (Prompts — a user-selected starting point) rather than you having to describe the entire trip from scratch every time.
Analogy
The 3 server-side primitives map cleanly onto 'who's allowed to press the button': Tools are model-controlled (the AI itself decides to call them, like an autonomous assistant reaching for a tool on its own initiative), Resources are app-controlled (the surrounding application decides when to fetch them, like a librarian deciding which reference book to hand you based on your question), and Prompts are user-controlled (you explicitly pick a template from a menu, like ordering a specific dish rather than asking the chef to freestyle). The 3 client-side primitives are about the client extending trust and structure back to the server: Sampling lends the server your own AI's judgment, Roots draws a fence around what the server can touch, and Elicitation lets the server politely interrupt to ask a real question instead of guessing.
Technical explanation
The 3 client-side primitives: (1) Sampling — the client side always has an LLM available; if needed, the server can ask the client's LLM to generate completions, while the client retains control over permissions and safety (e.g., an MCP server with travel tools asking the LLM to pick the optimal flight from a list of options). (2) Roots — this lets the client define what files/resources the server is allowed to access, making interactions sandboxed and scoped (e.g., a travel-booking server might be given access to only a specific calendar directory, from which it can read the user's schedule, rather than the entire filesystem). (3) Elicitation — this allows servers to request structured user input mid-task rather than requiring everything upfront (e.g., a travel-booking server asking for seat preference, room type, or contact number partway through finalizing a booking).
The 3 server-side primitives, reframed by who controls their invocation: (4) Tools — controlled by the model; functions that do things with real effects (write to databases, trigger logic, send emails) — e.g. search_flights, send_messages, create_calendar_events. Implemented via a decorator like @mcp.tool(); when the AI calls tools/call with a tool name and arguments, the server executes the corresponding function and returns structured results, which the client surfaces to the AI. Because tools can have side effects (file I/O, network calls, sending real emails), MCP implementations often require explicit user permission on first use (e.g., a client popping up 'The AI wants to use the get_weather tool, allow yes/no?'), keeping a human in the loop for powerful actions. (5) Resources — controlled by the app; passive, read-only data (files, calendars, knowledge bases) accessed via a decorator like @mcp.resource('file://{path}'), typically fetched under the host application's control rather than spontaneously by the model, to avoid the model reading arbitrary data it wasn't meant to see. (6) Prompts — controlled by the user; pre-built instruction templates or multi-turn workflows (e.g., 'plan a vacation', 'draft an email', 'summarize my meetings') that a user selects from a UI, which the host then fetches from the server — the server-defined prompt function typically returns a list of message objects (in a chat-message format) that set up the scenario before the model generates its own response.
Architecture
A complete MCP interaction spans both sides symmetrically: the Client exposes Sampling (lend the server LLM judgment), Roots (scope server access), and Elicitation (relay server's mid-task questions to the user) — while the Server exposes Tools (model-invoked actions), Resources (app-invoked read-only data), and Prompts (user-invoked templates). This 3+3 structure is what makes MCP a genuine two-way protocol rather than a one-directional 'model calls server functions' pattern — the server can request things FROM the client (a judgment call via Sampling, a clarifying answer via Elicitation) just as the client can request things FROM the server (executing a tool, fetching a resource, retrieving a prompt template).
Workflow
- When designing an MCP server, first decide, for each piece of functionality, which of the 3 server-side primitives it belongs to: does the AI model decide when to invoke it (Tool), does the surrounding app decide when to fetch it (Resource), or does the user explicitly select it from a menu (Prompt)?
- For any Tool with real side effects (sending data, modifying state, spending money), design an explicit permission-confirmation step so a human stays in the loop before the action executes.
- If your server ever needs the AI's judgment on something nuanced rather than deterministic logic, use Sampling to ask the client's LLM directly instead of bundling a separate model integration into your server.
- Scope your server's access explicitly using Roots — never assume unrestricted filesystem or resource access; request only the specific paths/directories genuinely needed.
- For any workflow where required information genuinely isn't available upfront (a booking needing a seat choice, a form needing clarification), use Elicitation to request it mid-task in a structured way rather than guessing or forcing all inputs upfront.
- Test the full loop end-to-end with a realistic multi-step scenario (like the travel-booking example) to confirm all 6 primitives you're using compose correctly.
Example
from mcp.server.fastmcp import FastMCP
mcp = FastMCP('travel-booking')
Tool — model-controlled, has real side effects
@mcp.tool() def book_flight(flight_id: str, passenger_name: str) -> dict: '''Book a specific flight. Requires user confirmation before executing.''' return reservation_system.book(flight_id, passenger_name)
Resource — app-controlled, read-only
@mcp.resource('calendar://{user_id}/schedule') def get_calendar(user_id: str) -> str: '''Read-only access to the user's calendar, scoped via Roots.''' return calendar_service.read_schedule(user_id)
Prompt — user-controlled, a pre-built workflow template
@mcp.prompt() def plan_a_vacation(destination: str) -> list[dict]: return [ {'role': 'system', 'content': 'You are a vacation planning assistant.'}, {'role': 'user', 'content': f'Plan a trip to {destination}.'}, ]
Sampling — server asks the client's LLM to make a judgment call
async def pick_best_flight(mcp_context, flight_options: list[dict]) -> dict: result = await mcp_context.sample( f'Given these flights: {flight_options}, which best matches a preference ' f'for morning departures and direct routes?' ) return result
Elicitation — server asks the user a structured mid-task question
async def finalize_booking(mcp_context, flight_id: str): seat_pref = await mcp_context.elicit( prompt='What is your seat preference?', schema={'type': 'string', 'enum': ['window', 'aisle', 'middle']}, ) return book_flight(flight_id, seat_preference=seat_pref)
Roots — the client scopes what this server may access (configured client-side)
client_config.json: { "roots": ["calendar://user123/"] }
Real-world usage
Claude Desktop's permission-prompt UX ('The AI wants to use X tool, allow yes/no?') is a direct, shipped implementation of the Tools primitive's model-controlled-but-governance-approved design. Enterprise MCP deployments (e.g., Block/Square's internal servers) use Roots extensively to sandbox exactly which internal databases or file paths a given MCP server may touch, a critical requirement for compliance and security review. Booking and scheduling assistants (travel, restaurant reservation, calendar-management AI products) are the most natural adopters of Elicitation, since these workflows routinely need to ask a clarifying question mid-task that can't be predicted or gathered upfront. IDE-integrated MCP servers (like those used in Cursor) lean heavily on Sampling for code-related judgment calls where the server has structured information (e.g., a list of possible refactors) but wants the client's LLM to make the final qualitative choice.
Trade-offs
Implementing all 6 primitives is more work than a minimal Tools-only MCP server, but unlocks meaningfully richer, two-way interactions — a server relying on Tools alone can only wait to be called, never ask the client for judgment (Sampling) or clarification (Elicitation), and has no standardized way to declare scoped access boundaries (Roots) or separate passive knowledge (Resources) from user-selected workflows (Prompts). For a simple, single-purpose server exposing one or two clearly model-invoked actions, a Tools-only implementation is entirely reasonable — the full 6-primitive model pays off as server complexity and interactivity grow.
Visual explanation
A single running example — an AI travel-booking assistant — illustrating all 6 primitives at once.
Center: [MCP Client] ↔ [MCP Server: Travel Booking].
From the client side, 3 arrows: 'Sampling: server asks client's LLM to pick the optimal flight from a list', 'Roots: client scopes server access to a specific calendar directory only', 'Elicitation: server asks client mid-task for seat/room preference'.
From the server side, 3 arrows: 'Tools (model-controlled): search_flights, book_flight, send_confirmation_email', 'Resources (app-controlled): read_calendar, retrieve_past_bookings', 'Prompts (user-controlled): "plan a vacation" template, "summarize my itinerary" template'.
A small legend box notes: Tools = model decides, Resources = app decides, Prompts = user decides, and the 3 client-side primitives all originate from the server making a request back to the client.
Advantages
- —
The Tools/Resources/Prompts split by 'who controls invocation' gives both UX designers and safety reviewers a precise, standardized vocabulary for reasoning about a capability's risk and behavior
- —
Sampling lets servers access AI judgment without duplicating a separate LLM integration, keeping servers lighter and centralizing model access on the client
- —
Roots gives a standardized, protocol-level way to sandbox server access instead of relying on ad hoc, per-server security conventions
- —
Elicitation enables genuinely interactive, multi-step workflows (like bookings) that can't be completed correctly by gathering all inputs upfront
Disadvantages
- —
Implementing all 6 primitives is meaningfully more engineering effort than a simple Tools-only MCP server
- —
The added two-way communication (Sampling, Elicitation) introduces more points where a server interaction can stall waiting on a client/user response
- —
Not all MCP clients fully support all 6 primitives yet, since the ecosystem is still maturing — servers relying on newer primitives like Elicitation may not work with every client
- —
The permission-prompt UX for Tools, while safer, adds friction that some product experiences may need to carefully design around rather than blindly relying on default confirmations
Common mistakes
- —
Implementing every capability as a Tool by default without considering whether it's actually app-controlled (Resource) or user-controlled (Prompt) data/workflow, losing useful invocation-control signal
- —
Giving a server unrestricted access instead of using Roots to scope exactly what it needs, creating unnecessary security exposure
- —
Building a booking-style or form-filling workflow that tries to gather all possible inputs upfront instead of using Elicitation to ask only what's needed, when it's needed
- —
Bundling a separate, redundant LLM integration into a server instead of using Sampling to borrow the client's already-available model access
- —
Treating MCP as 'just tool calling' and never using Sampling, Roots, Elicitation, Resources, or Prompts at all — missing most of what makes MCP a genuine two-way protocol
📂 Subtopics
Tools Primitive: Model-Controlled Actions with Side Effects
Tools are executable functions the AI can invoke, usually triggered by the model's own choice — but since they can have real side effects, MCP implementations often require explicit user permission before execution.
~15 min
Resources Primitive: Read-Only Data the Host Controls
Resources give the AI read-only access to data — files, knowledge base snippets, query results — without handing over the ability to change anything. Unlike tools, they're typically fetched under the HOST application's control, not spontaneously by the model.
~12 min
Prompts Primitive: Reusable, User/Developer-Controlled Templates
Prompts are predefined templates or multi-turn workflows the server can supply, letting a host reuse sophisticated prompt patterns without hardcoding them — usually selected by a user or developer, not decided spontaneously by the model.
~12 min
Sampling Primitive: The Server Asks the Client's LLM to Generate
The client side always has an LLM — Sampling lets the server ask that LLM to generate completions mid-workflow, while the client still controls permissions and safety.
~12 min
Roots Primitive: Scoping What Files the Server Can Access
Roots lets the client define exactly what files a server can access, making interactions secured, sandboxed, and scoped — a boundary set by the client, not requested by the server.
~10 min
Elicitation Primitive: The Server Asks the User for More Info Mid-Task
Elicitation lets a server request structured user input mid-task — pausing a multi-step workflow to ask for a missing preference or detail, rather than guessing or failing outright.
~10 min