Sampling Primitive: The Server Asks the Client's LLM to Generate

~12 min read

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.

Sampling is the first of the 3 client-side MCP primitives, and it flips the usual direction of the Tools/Resources/Prompts primitives (all server-provided, covered in this topic's earlier subtopics): instead of the server offering something TO the client, Sampling is the server REQUESTING something FROM the client.

The mechanism relies on a simple but important fact: the client side always has an LLM available (it's the whole reason the client exists — to mediate between an LLM app and MCP servers). Sampling lets a server ask the client's LLM to generate some completions mid-workflow, whenever the server's own logic needs language-model reasoning it doesn't have the capability to do itself. Critically, the client still controls permissions and safety around this — the server is REQUESTING a generation, not unilaterally commanding the client's LLM, and the client can apply whatever guardrails or approval steps it wants around honoring that request.

This course's concrete example grounds this well: an MCP server with travel-booking tools can ask the client's LLM to pick the optimal flight from a list of options it has retrieved. The server itself might just be a thin wrapper around a flight-search API — it doesn't need its own separate LLM or reasoning capability to make this kind of judgment call; it can simply ask the client (which already has an LLM) to do that reasoning and return the result.

This is a genuinely different capability from what plain, one-directional function calling can offer: it means an MCP server can be relatively 'dumb' in its own right (no embedded reasoning of its own) while still tapping into sophisticated LLM reasoning by borrowing the client's model for specific sub-decisions — exactly the two-way communication this topic's main content identifies as what makes MCP more than 'just another tool-calling standard.'

💻 Code example

# Server-side: requesting a sampling completion from the client's LLM
# mid-workflow — the server itself has no LLM of its own here.
from mcp.server.fastmcp import FastMCP
from mcp.types import SamplingMessage, TextContent

mcp = FastMCP("travel-server")

@mcp.tool()
async def book_optimal_flight(ctx, destination: str, flight_options: list[dict]) -> str:
    """The server doesn't decide which flight is 'optimal' itself —
    it asks the client's LLM to reason about it via Sampling."""
    prompt = (
        f"Given these flight options to {destination}, pick the best one "
        f"considering price and total travel time: {flight_options}"
    )

    # This is the Sampling request — asking the CLIENT's LLM to generate,
    # not calling an LLM the server owns itself
    result = await ctx.session.create_message(
        messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
        max_tokens=200,
    )
    # The client controls permissions/safety around honoring this request —
    # the server can only ask, not unilaterally command the client's LLM
    return result.content.text

💬 Deep Dive with AI

Key points

  • Sampling is server-initiated: unlike Tools/Resources/Prompts (server offers TO client), the server REQUESTS something FROM the client here
  • It works because the client side always has an LLM available — the server borrows it rather than needing its own
  • The client still controls permissions and safety — the server requests, it doesn't command
  • The book's example: an MCP server with travel tools asks the client's LLM to pick the optimal flight from a list
  • This lets a server stay 'dumb' (no embedded reasoning) while still tapping into sophisticated LLM judgment when needed