python-sdk/examples/stories/mrtr
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Marcelo Trylesinski f664db8952
Add resolver dependency injection for MCPServer tools (#2969)
Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
2026-06-29 11:51:46 +01:00
..
__init__.py Client auto-resolves InputRequiredResult via existing callbacks (SEP-2322) (#2998) 2026-06-26 17:35:23 +02:00
client.py Client auto-resolves InputRequiredResult via existing callbacks (SEP-2322) (#2998) 2026-06-26 17:35:23 +02:00
README.md Add resolver dependency injection for MCPServer tools (#2969) 2026-06-29 11:51:46 +01:00
server.py Client auto-resolves InputRequiredResult via existing callbacks (SEP-2322) (#2998) 2026-06-26 17:35:23 +02:00
server_lowlevel.py Client auto-resolves InputRequiredResult via existing callbacks (SEP-2322) (#2998) 2026-06-26 17:35:23 +02:00

mrtr

Multi-round tool result: on the 2026-07-28 protocol a tool that needs user input mid-call returns resultType: "input_required" with embedded inputRequests and an opaque requestState, instead of pushing a server→client request. The client fulfils the embedded requests and retries the original tools/call carrying inputResponses and the echoed requestState. The story shows both the Client auto-loop (one await call_tool, callbacks fired transparently) and a manual client.session loop (the persistable form).

Run it

# HTTP — the client self-hosts the server on a free port, runs, then tears it
# down (the InputRequiredResult round-trip is 2026-era only)
uv run python -m stories.mrtr.client --http
# same, against the lowlevel-API server variant
uv run python -m stories.mrtr.client --http --server server_lowlevel

What to look at

  • client.py main — the auto-loop is invisible at the call site: Client(target, mode=mode, elicitation_callback=on_elicit) then await client.call_tool("deploy", ...). The same on_elicit callback the legacy push path uses is dispatched for each embedded inputRequests entry.
  • client.py manual block — client.session.call_tool(..., allow_input_required=True) returns the raw InputRequiredResult so request_state can be persisted between rounds; the retry is just another tools/call with input_responses= / request_state=.
  • server.py deploy — ctx.input_responses / ctx.request_state read the retry payload; the first round returns InputRequiredResult(input_requests={...}, request_state=...), the second returns the final string.
  • server_lowlevel.py — same wire contract via params.input_responses / params.request_state and a hand-built InputRequiredResult.

Caveats

  • Loop bound. The auto-loop gives up after input_required_max_rounds (default 10) with InputRequiredRoundsExceededError; raise it on the Client ctor or drop to the manual loop.
  • requestState integrity is the server's job. The client echoes it byte-exact and never inspects it; the server MUST treat it as attacker-controlled. The SDK ships no signing helper yet.

Spec

Multi-round results — server features

See also

legacy_elicitation/ and sampling/ — the handshake-era push equivalents this mechanism replaces on the 2026 protocol. refund_desk/ — resolver DI at the MCPServer tier: the questions a tool can declare instead of pushing by hand.