49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
sampling
Deprecated in the 2026-07-28 protocol (SEP-2577); functional through the deprecation window. Migration: call your LLM provider directly from the server instead of requesting completions through the client. TODO(maxisbey): revisit before beta.
A tool that asks the client's LLM for a completion mid-call — the inverted
MCP direction. The server holds no model API key; it awaits
ctx.session.create_message(...) and the client's sampling_callback answers.
Registering the callback is what makes the client advertise the sampling
capability — there is no separate flag.
Run it
# stdio (default — the client spawns the server as a subprocess)
uv run python -m stories.sampling.client
# HTTP — the client self-hosts the server on a free port, runs, then tears it down
uv run python -m stories.sampling.client --http --legacy
# same, against the lowlevel-API server variant
uv run python -m stories.sampling.client --http --legacy --server server_lowlevel
What to look at
client.pymain—async with Client(target, mode=mode, sampling_callback=on_sample) as client:. The callback is an ordinary constructor kwarg; registering it is the whole opt-in.client.pyon_sample— takes(ClientRequestContext, CreateMessageRequestParams)and returns aCreateMessageResult. A real host calls its LLM provider here; the example returns a canned answer so the round-trip is assertable.server.py—await ctx.session.create_message(...)inside the tool body: a server→client request that blocks until the callback answers. There is noContext.sample()sugar; reachingctx.sessionis the public path.server_lowlevel.py— the same call fromServerRequestContext.session, with theCallToolResultbuilt by hand.
Caveats
- Legacy-era only.
sampling/createMessageis a server-initiated request with no 2026-07-28 wire carrier, so this story runs withera = "legacy"and the harness pins the handshake path. ctx.session.create_message()is@deprecated; the# pyright: ignore[reportDeprecated]is deliberate. The non-deprecated replacement is to call your LLM provider directly from the server (see the banner above) — there is no successor server→client call.ctx.session.*is the interim 2-hop path; a later release will shorten it.Clienthas nosampling_capabilities=kwarg, so thesampling.toolssub-capability (tools-in-sampling) is unreachable from the high-level client. Drop toClientSessionif you need it.
Spec
See also
legacy_elicitation/, roots/ — sibling stories that exercise the same legacy
server→client request shape.