You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adopt the resolver framework for the interactive tools
Rewrite clear_done, prioritize, and brainstorm_tasks from hand-rolled
InputRequiredResult state machines onto Resolve dependencies, now that
Sample landed alongside Elicit: the framework carries the rounds on
2026-07-28 connections and push-style requests on pre-2026 ones, seals
the multi-round state, validates answers against the form schemas, and
gates on client capabilities. This deletes the example's local legacy
fulfilment driver, literal schema dicts, and JSON step state.
The elicitation forms render byte-identical to the TypeScript
reference's literals; the count-form theme field advertises its default
via json_schema_extra while staying nullable, so an omitted answer
still falls back to the tool argument like the reference. Resolver
outcomes, not board recounts, drive the empty-board paths so concurrent
mutations cannot desync the resolver from the tool body.
| Multi-round input_required |`brainstorm_tasks`| theme+count form → optional custom-amount round → sampling round; state rides `request_state`as a step-discriminated JSON object, sealed by the SDK|
47
+
| Multi-round input_required |`brainstorm_tasks`| theme+count form → optional custom-amount round → sampling round, as a resolver chain; the framework carries the recorded answers in sealed `request_state`|
48
48
| Progress |`work_through_tasks`, `add_tasks`| paced per-task progress notifications via `ctx.report_progress`|
49
49
| Logging | every mutating tool, via `log_info`| honours `logging/setLevel` on 2025 connections and the per-request log-level `_meta` opt-in on 2026-07-28 |
50
50
| Resources |`todos://board`, `todos://tasks/{id}`| one concrete resource + a URI template; every task also appears in `resources/list`|
51
51
| Subscriptions | the board |`resources/subscribe`/`unsubscribe` handlers for 2025-era clients; `subscriptions/listen` streams (over HTTP) for 2026-07-28; every mutation notifies |
52
52
| list_changed | every mutation | resource list + resource updated notifications on both eras |
53
53
| Prompts + completions |`plan-my-day`, `seed-board`| argument completion (project names, themes, task ids) wired to `completion/complete` via `@mcp.completion()`|
54
54
55
-
The two protocol eras differ in how interactive conversations travel: on 2025-era connections the wire carries _pushed_`elicitation/create` / `sampling/createMessage` requests; on 2026-07-28 the server returns `input_required` results and the client retries the call with the answers. The interactive tools (`brainstorm_tasks`, `clear_done`, `prioritize`) are written **once**, as state machines over `input_required` rounds — on 2025-era connections the example's small `run_interactive` driver fulfils the same rounds as real push-style requests (the job the TypeScript SDK's built-in legacy shim does), so there is no era branch in any handler. For single-question preconditions, the SDK's own era-agnostic form is a `Resolve(...)` dependency that returns `Elicit(...)` — see the [Dependencies tutorial](https://py.sdk.modelcontextprotocol.io/v2/tutorial/dependencies/); this example hand-rolls the rounds instead so the multi-round flow, the sampling rounds, and the carried state are all visible in one place.
55
+
The two protocol eras differ in how interactive conversations travel: on 2025-era connections the wire carries _pushed_`elicitation/create` / `sampling/createMessage` requests; on 2026-07-28 the server returns `input_required` results and the client retries the call with the answers. The interactive tools (`brainstorm_tasks`, `clear_done`, `prioritize`) are written **once**, as [resolver dependencies](https://py.sdk.modelcontextprotocol.io/v2/handlers/dependencies/): a parameter annotated `Annotated[T, Resolve(fn)]` is filled before the tool body, and a resolver that returns `Elicit(...)` or `Sample(...)` has the framework put the question to the client over whichever transport the connection negotiated — so there is no era branch in any handler, and no round bookkeeping in the example. `brainstorm_tasks` shows the multi-round shape as a chain: a count/theme form, a conditional custom-amount form that only asks when the first answer was "custom", and a sampling round derived from the recorded answers.
56
56
57
57
## Configuration
58
58
@@ -78,4 +78,5 @@ This port is verified against the TypeScript `todos-server` by driving both over
78
78
-**`subscriptions/listen` over stdio.** The Python SDK serves 2026-era listen streams on streamable HTTP only; over stdio a listen request is rejected. Board-change notifications over stdio therefore reach 2025-era subscribers only.
79
79
-**Legacy HTTP interactivity.** The TypeScript server's per-request HTTP posture refuses push-style sampling/elicitation for 2025-era HTTP clients; the Python server's default Streamable HTTP mode is stateful, so those tools work on that leg here.
80
80
-**Legacy HTTP fan-out.** Pre-2026 board-change notifications go to the session that made the mutating call. Over stdio that is every subscriber; with several concurrent 2025-era HTTP sessions, the others don't hear about it (the TypeScript entry broadcasts via its handler notifier). Pre-2026 HTTP handshakes also advertise `listChanged: false` — the SDK exposes no seam to change that on the HTTP path (stdio is patched, see `serve_stdio`).
81
-
-**Cancellation granularity.** When a 2025-era client cancels `work_through_tasks`, this SDK interrupts the handler at its next `await` (the in-flight pretend task stays open); the TypeScript server checks between tasks and finishes the in-flight one.
81
+
-**Cancellation granularity.** When a client cancels `work_through_tasks`, this SDK interrupts the handler at its next `await` (the in-flight pretend task stays open); the TypeScript server checks between tasks and finishes the in-flight one.
82
+
-**Elicited answers are schema-validated.** The resolver framework validates every accepted form answer against the requested schema, so a non-conforming answer (an out-of-range custom count, a value outside the count enum) fails the call with a validation error; the TypeScript handlers parse manually and report those as polite "Nothing added" declines. Similarly, a client that never declared the sampling/elicitation capability gets a structured `-32021` protocol error naming the missing capability, where the TypeScript server returns an `isError` tool result.
0 commit comments