Skip to main content
Sampling is how a server borrows the client’s LLM. When a tool calls ctx.sample(), it asks the connected client to run an inference and return the result — the server never holds an API key or talks to a provider itself. The client answers that request, and a sampling adapter is what turns the answer into a real model call. The adapter sits between MCP’s provider-neutral request format and a specific provider’s SDK, translating the messages, tools, and options in both directions.

How sampling works

A server-initiated sampling request lands on the client as a handler. You provide that handler, and providing it is also what advertises the sampling capability to the server — a server only calls ctx.sample() when it sees the client supports it, so a client with no sampling handler simply never receives the request. An adapter’s asHandler() produces exactly the handler the client expects, so wiring one in is a single line:
From there it is automatic. Every ctx.sample() on the server flows to the adapter, which converts the request to an Anthropic message call, runs it, and converts the response back into MCP’s shape — including tool-use round-trips, so a server that asks the model to call tools during sampling works without extra wiring. The adapter wires the same handler on both protocol eras, and the era decides how the request reaches it. On the legacy era the server pushes ctx.sample() mid-handler and the handler answers directly. On the modern era there is no server-to-client push channel — sampling as a push is deprecated in revision 2026-07-28 (SEP-2577) — so the server instead answers a call with an input-required result, and the client fulfils it through this very same sampling handler. That auto-fulfilment is on by default; you can tune it through the inputRequired option and turn it off with inputRequired: { autoFulfill: false } when you want to handle the round trip yourself. Wiring one adapter covers both eras.

Provider adapters

The three built-in adapters cover the major providers, and each owns the quirks of its SDK so you do not have to. They take a constructed provider client and expose the same asHandler() method, so switching providers is switching the adapter.
The provider SDKs are optional peer dependencies — install only the one you use. The adapters import their SDK types with import type, so a missing peer never causes a runtime error in a client that doesn’t reach for it. When you need a provider these don’t cover — a self-hosted model, a gateway, or a custom completion service — GenericSamplingAdapter wraps any async completion function. It handles model resolution and the maxTokens default for you and leaves the message format to your function, so anything you can call asynchronously can answer sampling requests.

Streaming and model selection

Every adapter accepts two shared options. onToken fires for each text delta as the provider streams its response, which lets you surface partial output — a live cursor, a progress display — while the inference is still running. The full result is still returned when the call completes; the callback is purely for observing the stream as it arrives.
The server can express a preference for which model to use through MCP’s model preferences, and modelSelector decides how to honor it. Pass a string to pin every sampling call to one model regardless of what the server asks for, or pass a function that receives the server’s preferences and returns the model name — the way to map abstract hints like cost or intelligence onto the concrete models a provider offers. Without a selector, each adapter falls back to a sensible default for its provider.