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
Copy file name to clipboardExpand all lines: docs/advanced/multi-round-trip.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,7 +19,7 @@ That's the whole protocol. Every leg is an ordinary request from the client to t
19
19
20
20
## The server side
21
21
22
-
The high-level `@mcp.tool()`decorator has no sugar for this yet. Today you write it on the **low-level**`Server`, whose `on_call_tool` handler is allowed to return either result type:
22
+
On `@mcp.tool()`you rarely build this by hand: declare a dependency that asks the user and the SDK returns the `InputRequiredResult`for you - that form is the **[Dependencies](../tutorial/dependencies.md)** tutorial. The manual form is the **low-level**`Server`, whose `on_call_tool` handler is allowed to return either result type:
23
23
24
24
```python title="server.py" hl_lines="44-47"
25
25
--8<--"docs_src/mrtr/tutorial001.py"
@@ -93,6 +93,6 @@ Drop to the underlying session, where `allow_input_required=True` hands you the
93
93
*`input_requests` is what it needs. `request_state` is an opaque resume token only the server reads.
94
94
*`Client` runs the retry loop for you: register `elicitation_callback` / `sampling_callback` / `list_roots_callback` and `call_tool` returns a plain `CallToolResult`. `input_required_max_rounds` (default 10) bounds it.
95
95
* To inspect or persist rounds, use `client.session.call_tool(..., allow_input_required=True)` and own the `while isinstance(result, InputRequiredResult)` loop yourself.
96
-
*The server side is the **low-level**`Server`only; `@mcp.tool()` has no sugar for this yet.
96
+
*On `@mcp.tool()`, a dependency that asks the user produces this result for you (**[Dependencies](../tutorial/dependencies.md)**); the **low-level**`Server`is the manual form.
97
97
98
98
This is the mechanism that replaces server-initiated sampling and the rest of the push-style back-channel; see **[Deprecated features](deprecated.md)**.
Copy file name to clipboardExpand all lines: docs/migration.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -786,6 +786,8 @@ Positional calls (`await ctx.info("hello")`) are unaffected.
786
786
787
787
`Context.elicit()` (and `elicit_with_validation()`) now render the schema first and validate each property against the spec's `PrimitiveSchemaDefinition`, raising `TypeError` at the call site for anything outside it. `Optional[T]` fields render as `{"type": ...}` with the field omitted from `required` (previously the non-spec `anyOf` shape). A bare `list[str]` field is rejected because it renders without the required enum items; use `list[Literal[...]]` or `list[str]` with `json_schema_extra` supplying the items. Unions of multiple primitives (e.g. `int | str`) and nested models are rejected.
788
788
789
+
A schema-mismatched *accepted* answer also fails differently: the call now raises `ValueError` with a stable message ("Received an accepted elicitation whose content does not match the requested schema") instead of letting pydantic's `ValidationError` escape with its internals. Code that caught `ValidationError` around `ctx.elicit()` should catch `ValueError` (or rely on the tool's error result).
790
+
789
791
### Replace `RootModel` by union types with `TypeAdapter` validation
790
792
791
793
The following union types are no longer `RootModel` subclasses:
Copy file name to clipboardExpand all lines: docs/tutorial/dependencies.md
+16-1Lines changed: 16 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -116,11 +116,26 @@ And if the user won't answer at all - declines the question, or cancels it?
116
116
117
117
That's the right default for a precondition: no answer, no order. When declining is an outcome your tool wants to handle - skip the backorder but still suggest another title - annotate `ElicitationResult[Backorder]` instead and the tool receives the full accept/decline/cancel outcome to branch on. **[Elicitation](elicitation.md)** shows that form, and everything else about asking: the schema rules, the three answers, the client's side of the conversation.
118
118
119
+
!!! info
120
+
The framework picks the question's transport from the negotiated protocol version; the code
121
+
above is identical on both. On **2026-07-28** and later the question rides inside a
122
+
multi-round-trip `tools/call` - the server returns it, the client's `elicitation_callback`
123
+
answers it, and the `Client` retries the call for you (**[Multi-round-trip requests](../advanced/multi-round-trip.md)**). On
124
+
**2025-11-25** and earlier it is a synchronous elicitation request mid-call. Each question is
125
+
asked exactly once per call - a guarantee about the question, not the resolver. In the
126
+
multi-round-trip form an eliciting resolver runs again to consume its answer, so code before
127
+
its `return Elicit(...)` runs on the asking round and again on the answering one; a resolver
128
+
that answered *without* asking, like `check_stock`, may run again whenever the call resumes
129
+
after a question. When it resumes, each answer is matched back to its question, so an
130
+
eliciting resolver must derive its question deterministically from the tool's arguments and
131
+
earlier answers - a per-call generated value (a `default_factory` id, a timestamp) is
132
+
re-derived on each round and must not appear in a question the answer is meant to bind to.
133
+
119
134
## Recap
120
135
121
136
*`Annotated[T, Resolve(fn)]` on a tool parameter: the SDK runs `fn` and injects its return value.
122
137
* A resolved parameter is invisible to the model and cannot be supplied by a client. Values the model must not invent - prices, identities, permissions - belong here.
123
-
* A resolver's parameters are resolved the same way: the `Context`, another `Resolve(...)`, or a tool argument by name. The graph runs each resolver at most once per call.
138
+
* A resolver's parameters are resolved the same way: the `Context`, another `Resolve(...)`, or a tool argument by name. The graph runs each resolver at most once per round, however many consumers it has; each question is asked exactly once, an eliciting resolver runs again to consume its answer, and a resolver that never asked may run again when a call resumes.
124
139
* Bad graphs fail at registration with `InvalidSignature`, not mid-call.
125
140
* Return `Elicit(message, Model)` to ask the user, only when you have to. Unwrapped annotations abort on decline; `ElicitationResult[T]` lets the tool branch.
0 commit comments