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 thesampling 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:
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 sameasHandler() method, so switching providers is switching the adapter.
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.
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.