Sampling and roots
A handler can ask the connected client for two more things: a completion from the client's own model (sampling), and the client's workspace folders (roots).
Both still work, on every protocol version the SDK speaks. But read the warning before you design around them:
Deprecated by the 2026-07-28 specification
Sampling and roots are deprecated as of 2026-07-28 (SEP-2577). They remain fully functional and stay in the specification for at least twelve months before becoming eligible for removal, but new implementations should not build on them. The suggested migrations: integrate directly with your LLM provider's API instead of sampling, and pass directories via tool parameters, resource URIs, or server configuration instead of roots. The SDK-wide list is in Deprecated features.
Sampling: borrow the client's model
A resolver returns Sample(...) and the tool receives the completion, through the same dependency mechanism that runs Elicit in Dependencies:
from typing import Annotated
from mcp_types import CreateMessageResult, SamplingMessage, TextContent
from mcp.server import MCPServer
from mcp.server.mcpserver import Resolve, Sample
mcp = MCPServer("Bookshop")
def draft_blurb(title: str) -> Sample:
prompt = f"Write a one-sentence blurb for the book {title!r}."
return Sample(
[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
max_tokens=60,
)
@mcp.tool()
async def blurb(title: str, draft: Annotated[CreateMessageResult, Resolve(draft_blurb)]) -> str:
"""Draft a blurb for a book."""
return draft.content.text if draft.content.type == "text" else "No blurb."
Sample(messages, max_tokens=...)mirrors thesampling/createMessageparameters. The injected value is the client'sCreateMessageResult; passtools=[...]and it becomes aCreateMessageResultWithToolsinstead.- The client must have declared the
samplingcapability (sampling.toolsif you passtoolsortool_choice). If it didn't, the call fails with a-32021protocol error instead of sending a request the client cannot handle. A pre-2026 session with no back-channel fails with its usual no-back-channel error, since there is nothing to send on. - At
2026-07-28the request is delivered inside the multi-round-trip flow (Multi-round-trip requests); on2025-11-25it is a standalone request to the client. The code is the same either way, but mind the multi-round-trip rule: the request must render identically across retry rounds, so build it only from the tool's arguments and other stable data. - Leave
include_contextalone: values other than"none"are themselves deprecated (SEP-2596) and need a capability almost no client declares.
Roots: where should this go?
Roots are the folders the client says the server may operate on. They are informational guidance, not an access-control mechanism. A resolver returns ListRoots():
from typing import Annotated
from mcp_types import ListRootsResult
from mcp.server import MCPServer
from mcp.server.mcpserver import ListRoots, Resolve
mcp = MCPServer("Bookshop")
def workspace_roots() -> ListRoots:
return ListRoots()
@mcp.tool()
async def catalog_folder(roots: Annotated[ListRootsResult, Resolve(workspace_roots)]) -> str:
"""Pick the folder the catalog export should go to."""
if not roots.roots:
return "No workspace folders shared."
return str(roots.roots[0].uri)
- The injected
ListRootsResultcarries a list ofRoots: afile://URI and an optional display name. - The gate is the same as for sampling: without a declared
rootscapability the call fails with-32021instead of sending the request.
On the other side of the wire, the client answers both requests with the callbacks it already has: sampling_callback and list_roots_callback, covered in Client callbacks.
On 2025-era connections
ctx.session.create_message(...) and ctx.session.list_roots() still exist for code that drives the session directly. They only work where a back-channel exists (2025-era, non-stateless connections), and calling them raises a deprecation warning. The resolver markers above are the supported form: they pick the delivery from the negotiated version and don't warn.
Recap
- Return
Sample(...)orListRoots()from a resolver; the tool receives theCreateMessageResultorListRootsResultlike any other dependency. - The client must declare the matching capability, or the call fails with
-32021instead of a request being sent. - Both features are deprecated at
2026-07-28: fully functional for now, wrong for new designs. Prefer provider APIs over sampling and explicit parameters over roots.
Reporting how far along a slow tool is: Progress.