Title: Extend resolver DI to sampling and roots requests by maxisbey · Pull Request #3049 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Extend resolver DI to sampling and roots requests by maxisbey · Pull Request #3049 · modelcontextprotocol/python-sdk
X Title: Extend resolver DI to sampling and roots requests by maxisbey · Pull Request #3049 · modelcontextprotocol/python-sdk
Description: Resolvers can now return Sample(...) or ListRoots() in addition to Elicit, covering all three request kinds the multi-round-trip flow allows (SEP-2322): elicitation, sampling, and roots. Motivation and Context The resolver dependency-injection API (#2969, #2986) only supported asking the user via Elicit. The multi-round-trip inputRequests union is a closed set of three request kinds, and the client half already dispatches all three to the standard callbacks — this fills in the server half so a tool dependency can also sample the client's LLM or fetch its roots: def suggest_title(genre: str) -> Sample: prompt = f"Suggest one {genre} book title." return Sample([SamplingMessage(role="user", content=TextContent(type="text", text=prompt))], max_tokens=50) @mcp.tool() async def recommend_book(genre: str, suggestion: Annotated[CreateMessageResult, Resolve(suggest_title)]) -> str: ... Design notes: One rendering, both eras. A single _render_request produces the wire request used both as the 2026-07-28 inputRequests entry and as the pre-2026 back-channel payload, so the two transports send identical shapes by construction. The legacy legs for sampling/roots call send_request directly rather than the @deprecated session wrappers: the deprecated thing (SEP-2577) is the standalone feature, and marker-routed compatibility sends shouldn't warn — direct ctx.session.create_message() still does. No decline arm. Only elicitation has an accept/decline/cancel union; a client refuses sampling/roots by erroring. Consumers annotate the result type directly (CreateMessageResult, CreateMessageResultWithTools when the request carries tools or tool_choice, ListRootsResult). Results persist across rounds. Sampling/roots results ride requestState like elicited answers, pinned to the exact rendered request, so the client pays for an LLM call once per tool call rather than once per retry round. The state encoding is unchanged and byte-compatible with in-flight state. Answers are validated against the expected model, not the response union. The InputResponses union cannot discriminate a no-tool-use answer to a tools request (a single content block parses as the plain result shape), so trusting the union member would reject spec-valid responses. Per-kind capability gate. The existing elicitation-only check generalizes: before sending any of the three kinds, on either transport, the server verifies the client declared the matching capability (elicitation form, sampling — plus sampling.tools when the request carries tools/tool_choice — or roots) and refuses with -32021 MISSING_REQUIRED_CLIENT_CAPABILITY carrying the full requiredCapabilities payload. On 2026-07-28 an absent declaration is meaningful by contract (capabilities arrive per-request, and servers must not infer them from prior requests), so the gate fires unconditionally; on pre-2026 sessions the gate applies wherever the request could actually be sent (it reads the request channel's own sendability), which covers sessions initialized with a bare notifications/initialized, while a session that cannot carry a server-initiated request at all keeps failing with the usual no-back-channel error. Client gains sampling_capabilities so sampling sub-capabilities like tools support can finally be declared from the high-level client (ClientSession already accepted it). How Has This Been Tested? Beyond the unit/e2e suite (all three kinds batched in one round, cross-kind resolver chains over three rounds, capability refusals on both eras, state restore, no-tool-use answers to tools requests), the branch was exercised as a real application: an MCPServer process on streamable HTTP with a separate client process — 2026-07-28 auto negotiation with elicit+sample+roots fulfilled through the retry loop, 2025-11-25 legacy over the back-channel with MCPDeprecationWarning promoted to error (none fired), a stdio subprocess negotiating 2026-07-28, and live -32021 probes verifying the payloads and that the session stays usable after a refusal. Wire compatibility: no new wire shapes — the conformance everything-server already exercises all three embedded kinds, and the suite is unaffected. One gap worth noting: the conformance suite covers -32021 mechanics elsewhere but has no dedicated scenario for the "server MUST NOT send an inputRequests entry the client has not declared support for" egress rule specifically; happy to raise that on the conformance repo. Breaking Changes Resolver-routed requests now enforce the capability egress rule on pre-2026 sessions too: a 2025-11-25 client that answered elicitations without declaring the elicitation capability now gets -32021 instead of being asked. Documented in docs/migration.md (declare the capability — the SDK client does this automatically when the callback is set — or drop the asking dependency). Direct ctx.elicit() / ctx.session.* calls outside resolvers are unaffected. Types of changes New feature (non-breaking change which adds functionality) Breaking change (fix or feature that would cause existing functionality to change) Documentation update Checklist I have read the MCP Documentation My code follows the repository's style guidelines New and existing tests pass locally I have added appropriate error handling I have added or updated documentation as needed Additional context Out of scope, noted for follow-ups: ClientSessionGroup cannot declare sampling sub-capabilities (the same pre-existing gap Client had before this PR), and the elicitation legacy leg's validation still lives in elicit_with_validation while sampling/roots go through send_request — kept as-is to leave the shipped elicitation path untouched. AI Disclaimer
Open Graph Description: Resolvers can now return Sample(...) or ListRoots() in addition to Elicit, covering all three request kinds the multi-round-trip flow allows (SEP-2322): elicitation, sampling, and roots. Motivation...
X Description: Resolvers can now return Sample(...) or ListRoots() in addition to Elicit, covering all three request kinds the multi-round-trip flow allows (SEP-2322): elicitation, sampling, and roots. Motivation...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/3049
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:def0860f-f4d4-1d76-63f6-74468a03cb92 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | 9D98:3E413F:62E86D:843B28:6A5B35D4 |
| html-safe-nonce | 1da1a3b3aab4316836408e7f55b1b5ad4625f5912a6c2b8905c2188965bbb835 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5RDk4OjNFNDEzRjo2MkU4NkQ6ODQzQjI4OjZBNUIzNUQ0IiwidmlzaXRvcl9pZCI6IjI3NTk5MDcwNzM3OTAyNjA2OTIiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | a111aeccc0a18276e67ae3e8d733dc476e8d7be64689a78201224b2a8ab9739b |
| hovercard-subject-tag | pull_request:3974326218 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/python-sdk/pull/3049/files |
| twitter:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| og:image:alt | Resolvers can now return Sample(...) or ListRoots() in addition to Elicit, covering all three request kinds the multi-round-trip flow allows (SEP-2322): elicitation, sampling, and roots. Motivation... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5290d7e14309ad1e76106a9c4237bd1041517e83ea182c8ab756752cb0c6940b |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c975978430e9ad293956f2bbdaf153b1bd84a99 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width