The imperative ctx.sample()/ctx.list_roots() stay removed, but both
capabilities survive as input-required requests, as tests/conformance
exercises on 2026-07-28. Direct LLM calls remain the recommendation for
generation; roots has no round-trip-budget objection.
Deletes fastmcp/server/sampling/, Context.sample/sample_step/list_roots, and
FastMCP(sampling_handler=). The proxy's handshake-era relay now reaches the
front session through the SDK directly.
fastmcp.types re-exported 29 mcp_types symbols verbatim, which was
pointless indirection users had to discover. It now holds only Textarea,
the one type FastMCP actually defines; everything else imports from
mcp_types directly. These mirrors were added during unreleased SDK v2
migration work and never shipped, so this is not a breaking change.
* Add guard-mode MRTR server support (SEP-2322)
* Add server-side MRTR guard tests
* Add MRTR guard docs, exports, and output-schema handling
* Apply formatting to MRTR guard changes
* Fix MRTR review round 1: middleware-safe suspend, Annotated strip, stable audience
- ToolInputRequired subclasses BaseException (CancelledError precedent) so
error middleware's broad except Exception cannot swallow a suspension
- Strip InputRequiredResult arms inside Annotated return types
- Reject a custom RequestStateSecurity without a stable audience (random
per-replica server names would break shared-key verification)
* Fix static analysis: rewrite tuple([...]) as tuple literal (C409)
* Recognize InputRequiredResult inside Annotated union arms
_is_input_required_type now peels Annotated first, so a metadata-carrying
guard arm (str | Annotated[InputRequiredResult, Field(...)]) is stripped
and the data arm's output schema survives.
* docs: frame multi-round tools as elicitation on the modern protocol
Fold multi-round-tools.mdx into elicitation.mdx as two eras of one
capability; drop pause/suspend framing for the stateless per-round model.
* Transport MRTR asks as InputRequiredToolResult, not a raised signal
An input-required result is the full result of a stateless MRTR leg, so it
flows through the middleware chain as an ordinary ToolResult subclass instead
of a raised ToolInputRequired(BaseException). Middleware observes it, caching
skips it, and response-limiting leaves it untouched.
* Document MRTR middleware interaction and the isinstance pattern
* Update MRTR change-register verify note to InputRequiredToolResult
* Align test module docstring with result-cycle framing
* Fix MRTR review: bypass cache on continuation legs; soften audience guard
- ResponseCachingMiddleware skips read AND write on continuation legs:
the cache key is name+arguments only, so a continuation's final result
would be served to later fresh calls, which would never be asked
- The stable-audience check is a warning, not an error: a policy object
cannot reveal whether its keys are shared, and single-process
customization (ephemeral ttl, custom codec) is legitimate unnamed
* Treat state-only rounds as continuations in the response cache
A round carrying request_state but no questions retries with
input_responses=None; request_state alone must bypass the cache or its
terminal result is stored under the fresh-call key.
* Fix MRTR review round: preserve asks through transforms, empty-name audience, docs predicate
- TransformedTool.run returns an InputRequiredToolResult intact instead of
reshaping it into an empty ToolResult for non-object output schemas
- audience warning uses a falsy-name check (empty string also autogenerates
a per-replica name)
- the elicitation docs continuation predicate checks request_state too
* Add create_proxy(mode=) opt-in for guard round-tripping through proxies
An auto-created proxy client stays handshake-era by default (a dual-era
backend serves both, and one proxy session is one era; handshake preserves
server-initiated push forwarding). Pass create_proxy(target, mode="auto")
to negotiate modern so an upstream guard's InputRequiredResult round-trips —
the two are mutually exclusive per session.
* Wrap raw InputRequiredResult returned by a transform_fn
A custom transform function may return the raw ask directly, like any tool
body — wrap it into InputRequiredToolResult so it survives output
normalization and reaches the wire, not only pre-wrapped forwarded guards.
* Reject input-required results from background tasks
* Unwrap type aliases before stripping guard arms
* Apply ruff format
* Recursively strip guard arms through nested and composed aliases
* Reflect MRTR continuation fields on the middleware message
* Suppress output schema for InputRequiredResult subclasses
* Forward progress on modern proxy tool calls
* Suppress output schema for bare aliased guard returns
* Suppress output schema for any surviving guard return wrapping
* Add AnthropicSamplingHandler
Adds a sampling handler for the Anthropic API at
fastmcp.client.sampling.handlers.anthropic, alongside the existing
OpenAI handler. Includes full support for tool calling.
Install with: pip install fastmcp[anthropic]
* Update default model
* Update sampling docs to cover both OpenAI and Anthropic handlers
* Use AsyncAnthropic, fix falsy value handling, handle tool_choice none
* Propagate isError to Anthropic, join multiple text blocks, fix docs
* Unify SamplingHandler and promote OpenAI handler
Consolidates ServerSamplingHandler and ClientSamplingHandler into a single
SamplingHandler type alias. Moves OpenAISamplingHandler from experimental
to fastmcp.client.sampling.handlers.openai as the canonical location.
Backwards compatibility maintained for imports from experimental.
* Remove unreachable code paths in OpenAI handler
* Fix docstring and use elif for mutually exclusive branches
* MCP → SDK (vocab change only)
* WIP: Sampling API with SamplingResult[T] and result_type
* SEP-1577: Sampling with tools
- Add tools and result_type parameters to ctx.sample()
- Update OpenAI handler for tool content types
- Client advertises sampling.tools capability by default
- Collect tool results into single message with list content
* Fix tool result content handling in OpenAI handler
* Remove @sampling_tool decorator - pass functions directly to sample()
Functions passed to ctx.sample(tools=[...]) are now auto-converted
via SamplingTool.from_function(). Users can still use that method
directly for custom name/description overrides.
* Remove auto-conversion of MCP tools to sampling tools
Users want MCP tools passed to ctx.sample() to go through the full MCP
machinery (middleware, native responses) rather than being auto-converted
to direct function calls. Now only SamplingTool and plain callables are
accepted - passing a FastMCP Tool raises a clear TypeError.
Also bumps mcp dependency to >=1.24.0 for required sampling features.
* Refactor sampling API: replace sample_iter() with sample_step()
Replace the mutable SampleRun/sample_iter() pattern with a simpler stateless
sample_step() function. sample_step() makes a single LLM call and returns a
SampleStep with the response and history. sample() now loops sample_step()
internally.
Key changes:
- Add sample_step() for fine-grained control over the sampling loop
- Remove SampleRun class and sample_iter() method
- Structured output uses tool description only (no prompt modification)
- execute_tools parameter controls automatic vs manual tool execution
* Address CodeRabbit nitpicks
* Address CodeRabbit review feedback for sampling tools
- Fix temperature=0.0 being dropped due to falsy evaluation
- Add ToolChoice.name support for forcing specific tools
- Replace assert statements with explicit RuntimeError checks
- Add mask_error_details parameter to sample()/sample_step() with ToolError escape hatch
- Fix hasattr patterns with proper isinstance checks
- Document mask_error_details and add OpenAI prerequisites to docs
* Address additional CodeRabbit review feedback
- Catch ValidationError specifically instead of bare Exception
- Update result_type docs to mention dataclasses and basic types
- Raise ValueError for unknown tool_choice modes
- Validate sampling_handler_behavior to catch typos
- Remove ToolChoice.name handling (not part of MCP spec)
- Validate tool_choice string in sample_step()
* Review fixes for sampling tools PR
- Remove internal functions from sampling __init__.py exports
- Remove fragile is_text property, use not is_tool_use instead
- Inline call_client into context.py, remove from run.py
- Fix SamplingMessage docs to use TextContent
- Handle result.text being None in doc examples
- Simplify client sampling docs to recommend OpenAISamplingHandler
- Add sampling_capabilities override documentation
- Raise iteration limit from 50 to 100
- Remove _parse_model_preferences duplication
- Use AsyncOpenAI in OpenAISamplingHandler
- Fix tool_choice docstring
* Fix OpenAI handler tests to use AsyncOpenAI
* Address remaining CodeRabbit review comments
- Fix message ordering in OpenAI handler: tool results now correctly
follow assistant message with tool_calls
- sample_step() now always includes assistant message in history
- Raise ValueError on JSON parse errors instead of silent {}
- Add has_sampling capability check when behavior is None
- Raise RuntimeError when structured output receives text response
- Wrap primitive result_type schemas in object wrapper
- Fix docs example using invalid SamplingMessage construction
- Add comprehensive client_sampling_test.py example
* Add return type annotation to OpenAISamplingHandler.__init__
* Use explicit 'is not None' check for sampling_capabilities defaulting
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Bill Easton <strawgate@users.noreply.github.com>