Calling an LLM directly
Hold a provider API key in your server’s environment, create the client once at module scope so connections are reused across calls, and generate inside the tool. You choose the model, control the prompt, see the token usage, and can test the tool with no client attached.Asking the caller’s model
A tool asks for a completion by returning anInputRequiredResult whose input_requests map holds a CreateMessageRequest under a key you choose. That result completes the round normally. The client runs the completion, then re-issues the same call_tool with the answer attached, and your tool reads it from ctx.input_responses under the same key — a CreateMessageResult. Because the tool runs from the top on every round, the presence of ctx.input_responses is what tells the two rounds apart: None on the first call, populated on the continuation.
fastmcp.Client drives that loop for you and answers from the sampling_handler it already has, so a client written for a handshake-era server needs no extra wiring to satisfy a modern tool that asks this way.
InputRequiredResult needs a 2026-07-28 connection, and FastMCP names the era mismatch if an older client reaches the tool; the conformance suite exercises this route on that version. The map can carry several requests at once and mix kinds — a sampling request beside an elicitation or a roots request — with each answer coming back under its own key. Elicitation covers the mechanics of the pattern in full, including how to carry state across rounds.
The removed methods
Context has no sample() and no sample_step(); touching either raises AttributeError on every protocol era, rather than failing at runtime only against modern clients. FastMCP() accepts neither sampling_handler= nor sampling_handler_behavior=, and naming one raises a TypeError that points at the migration.
The reason is the distinction MCP draws between telling and asking. A notification is fire-and-forget: the server emits it and moves on, and it travels down the response stream the caller already opened, so nothing has to be held open on the server’s behalf. That is why logging is untouched by any of this — ctx.info() and its siblings reach the client mid-call on every era. Sampling is the other kind. sampling/createMessage goes out and the caller must answer before the tool can continue, which needs a live, addressable connection the server can reach into, and the 2026-07-28 revision removed server-initiated requests (SEP-2577) precisely because a stateless protocol has no such thing.
What the protocol removed is the pushing, not the asking, so the capability survives in the shape described above. Keeping ctx.sample() alongside it would mean shipping a method whose outcome against a default client — one that negotiates the modern era — is a runtime failure.
Servers on FastMCP 3 still have
ctx.sample() and ctx.sample_step(), documented in the FastMCP 3 sampling guide. Nothing changes for them until they upgrade.
