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) Has been cancelled
63 lines
2.7 KiB
Markdown
63 lines
2.7 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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.py` `main` — `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.py` `on_sample` — takes `(ClientRequestContext,
|
|
CreateMessageRequestParams)` and returns a `CreateMessageResult`. 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 no
|
|
`Context.sample()` sugar; reaching `ctx.session` is the public path.
|
|
- `server_lowlevel.py` — the same call from `ServerRequestContext.session`,
|
|
with the `CallToolResult` built by hand.
|
|
|
|
## Caveats
|
|
|
|
- **Legacy-era only.** `sampling/createMessage` is a server-initiated request
|
|
with no 2026-07-28 wire carrier, so this story runs with `era = "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.
|
|
- `Client` has no `sampling_capabilities=` kwarg, so the `sampling.tools`
|
|
sub-capability (tools-in-sampling) is unreachable from the high-level client.
|
|
Drop to `ClientSession` if you need it.
|
|
|
|
## Spec
|
|
|
|
[Sampling — client features](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling)
|
|
|
|
## See also
|
|
|
|
`legacy_elicitation/`, `roots/` — sibling stories that exercise the same legacy
|
|
server→client request shape.
|