Skip to content

Commit 1821703

Browse files
committed
Run refund_desk on the dual era
The manifest pinned the story to era="legacy" until resolver elicitation could ride the 2026-07-28 input_required round-trip; that is now the case, so the story runs the full transport x era matrix with identical author code. README caveats describe the per-era transport and what persists across rounds; the spec links point at the real anchor (#input-required-tool-results), fixing the same stale fragment in the mrtr story; legacy_elicitation's cross-reference no longer claims resolver DI rides push elicitation everywhere.
1 parent 49e3b5f commit 1821703

5 files changed

Lines changed: 37 additions & 20 deletions

File tree

examples/stories/legacy_elicitation/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,6 @@ uv run python -m stories.legacy_elicitation.client --http --legacy --server serv
6868
## See also
6969

7070
`sampling/` (same push-request shape, deprecated per SEP-2577), `mrtr/`
71-
(planned — the 2026-era carrier), `error_handling/`
71+
(the 2026-era carrier), `error_handling/`
7272
(`UrlElicitationRequiredError`), `refund_desk/` (resolver DI rides this push
73-
mechanism today).
73+
mechanism on handshake-era connections).

examples/stories/manifest.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@ era = "legacy"
4040
status = "legacy"
4141

4242
[story.refund_desk]
43-
# Resolver DI rides push elicitation (ctx.elicit) today; era flips to "dual" once
44-
# the SDK carries resolver elicitation over the 2026 input_required round-trip.
45-
era = "legacy"
43+
# Resolver elicitation picks its transport per era: input_required round-trips on
44+
# the modern leg, push elicitation (ctx.elicit) on the legacy one.
4645
lowlevel = false
4746

4847
[story.sampling]

examples/stories/mrtr/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ uv run python -m stories.mrtr.client --http --server server_lowlevel
4646

4747
## Spec
4848

49-
[Multi-round results — server features](https://modelcontextprotocol.io/specification/draft/server/tools#multi-round-results)
49+
[Input required tool results — server features](https://modelcontextprotocol.io/specification/draft/server/tools#input-required-tool-results)
5050

5151
## See also
5252

examples/stories/refund_desk/README.md

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ reason)` refunds what the order record says — `cents` is resolver-computed and
77
does not appear in the input schema at all, so the model cannot supply or
88
inflate the amount. Resolvers form a DAG (`load_order``refund_scope`
99
`refund_amount` / `ask_restock`), may return `Elicit[...]` to ask the human,
10-
and run at most once per call. A resolver's own plain parameters are filled
11-
from the tool's arguments by name — `load_order(order_id)` receives the
12-
`order_id` the model passed to `refund_order`.
10+
and ask each question at most once per call. A resolver's own plain
11+
parameters are filled from the tool's arguments by name —
12+
`load_order(order_id)` receives the `order_id` the model passed to
13+
`refund_order`.
1314

1415
## Run it
1516

@@ -18,9 +19,9 @@ from the tool's arguments by name — `load_order(order_id)` receives the
1819
uv run python -m stories.refund_desk.client
1920

2021
# HTTP — the client self-hosts the server on a free port, runs, then tears it
21-
# down (--legacy: resolver elicitation rides the push request today; the
22-
# manifest pins this era, so bare --http runs the same leg)
23-
uv run python -m stories.refund_desk.client --http --legacy
22+
# down (2026 protocol: the questions ride embedded input_required round-trips;
23+
# add --legacy to ride synchronous push elicitation instead)
24+
uv run python -m stories.refund_desk.client --http
2425
```
2526

2627
## What to look at
@@ -47,21 +48,36 @@ uv run python -m stories.refund_desk.client --http --legacy
4748

4849
## Caveats
4950

51+
- **Transport per era.** The framework picks the elicitation transport from
52+
the negotiated protocol: at >= 2026-07-28 the questions ride embedded
53+
`input_required` round-trips (a resolver that depends on another's answer is
54+
asked in a later round); at <= 2025-11-25 each is a synchronous
55+
`elicitation/create` push request mid-call. Author code is identical on
56+
both — this client runs unchanged on either era.
5057
- **Decline order.** A declined unwrapped dependency aborts resolution in
5158
tool-signature order — `cents` resolves before `restock`, so `ask_restock`
5259
never runs. Don't rely on a later resolver's side effects after an earlier
5360
consumer can abort.
54-
- **Memoization scope.** Each resolver runs at most once per `tools/call`,
55-
keyed by function identity; nothing is cached across calls or connections.
61+
- **Memoization scope.** Each question is asked at most once per call, and
62+
within a round each resolver runs at most once, keyed by function identity.
63+
Across 2026 rounds only *elicited* outcomes persist (in `requestState`); a
64+
resolver that resolves without eliciting is pure and may re-run each round.
65+
An answer is matched back to its question when the call resumes, so an
66+
eliciting resolver must derive its question deterministically from the
67+
tool's arguments and earlier answers; a per-call generated value (a
68+
`default_factory` id, a timestamp) is re-derived each round and must not
69+
appear in a question the answer is meant to bind to. Nothing is cached
70+
across calls or connections.
5671
- **Validate elicited values.** Elicited answers are human-typed; check them
5772
against your records (as `_scoped` does) before acting on them.
5873

5974
## Spec
6075

61-
[Elicitation — client features](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation)
76+
[Elicitation — client features](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation),
77+
[Input required tool results — server features](https://modelcontextprotocol.io/specification/draft/server/tools#input-required-tool-results)
6278

6379
## See also
6480

65-
`legacy_elicitation/` (the push mechanism resolver elicitation rides on today),
66-
`mrtr/` (the 2026 `input_required` carrier; resolver DI will ride it once the
67-
SDK wires them together).
81+
`mrtr/` (the 2026 `input_required` carrier these questions ride at
82+
>= 2026-07-28), `legacy_elicitation/` (the push mechanism they ride on
83+
handshake-era connections).

examples/stories/refund_desk/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ async def on_elicit(context: ClientRequestContext, params: types.ElicitRequestPa
4141
assert counts == {"scope": 0, "restock": 0}, counts
4242

4343
# Full refund of a three-line order. The scope question fires exactly ONCE even though
44-
# both refund_amount and ask_restock consume it — memoized within the call.
44+
# both refund_amount and ask_restock consume it — asked at most once per call on either
45+
# era. ask_restock needs the scope ANSWER, so at 2026 the two questions land in
46+
# successive rounds, never one concurrent batch: counts and order are era-independent.
4547
receipt = await client.call_tool("refund_order", {"order_id": "ORD-7002", "reason": "arrived broken"})
4648
assert receipt.structured_content == {
4749
"order_id": "ORD-7002",
@@ -53,7 +55,7 @@ async def on_elicit(context: ClientRequestContext, params: types.ElicitRequestPa
5355

5456
# Declining restock still refunds: the tool keeps the ElicitationResult union for
5557
# `restock`, sees the decline, and just skips the restock. The scope counter moves
56-
# again — the memo cache is per tools/call, not per connection.
58+
# again — questions are deduped per call, not per connection.
5759
declines.add("restock")
5860
answers["scope"] = {"full": False, "sku": "canvas-tote"}
5961
receipt = await client.call_tool("refund_order", {"order_id": "ORD-7002", "reason": "wrong colour"})

0 commit comments

Comments
 (0)