Deprecate roots, sampling, and logging per SEP-2577#2922
Conversation
SEP-2577 deprecates the Roots, Sampling, and Logging features as of the 2026-07-28 spec. The deprecation is advisory only: no wire-level changes, capability negotiation is unchanged, and every type stays functional for sessions negotiating 2025-11-25 and earlier. Mark the 4 deprecated capability fields with `Annotated[T, deprecated(...)]` (warns on access, sets the JSON-schema `deprecated` flag) and the 17 deprecated type classes with a class-level `@deprecated`. The advisory runtime warning is silenced via a scoped `filterwarnings` entry since the SDK still builds these types internally to serve older sessions; the SDK's own references carry `# pyright: ignore[reportDeprecated]`. Drop the sampling-specific example snippet and its README section rather than decorate user-facing docs with deprecation suppressions; trim the incidental sampling callback from the general stdio client example.
| @mcp.tool() | ||
| async def test_sampling(prompt: str, ctx: Context) -> str: | ||
| """Tests server-initiated sampling (LLM completion request)""" | ||
| try: | ||
| # Request sampling from client | ||
| result = await ctx.session.create_message( | ||
| messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))], | ||
| messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))], # pyright: ignore[reportDeprecated] | ||
| max_tokens=100, | ||
| ) |
There was a problem hiding this comment.
Can't we just delete this test?
| mcp._lowlevel_server.add_request_handler( # pyright: ignore[reportPrivateUsage] | ||
| "logging/setLevel", SetLevelRequestParams, handle_set_logging_level | ||
| "logging/setLevel", | ||
| SetLevelRequestParams, # pyright: ignore[reportDeprecated] | ||
| handle_set_logging_level, | ||
| ) |
There was a problem hiding this comment.
Can't we just delete this?
| ) -> None: | ||
| """Send a log message notification.""" | ||
| await self.send_notification( | ||
| types.LoggingMessageNotification( | ||
| params=types.LoggingMessageNotificationParams( | ||
| types.LoggingMessageNotification( # pyright: ignore[reportDeprecated] | ||
| params=types.LoggingMessageNotificationParams( # pyright: ignore[reportDeprecated] | ||
| level=level, | ||
| data=data, | ||
| logger=logger, |
There was a problem hiding this comment.
🔴 Calling non-deprecated SDK convenience APIs (e.g. ctx.info() / send_log_message, create_message, list_roots, set_logging_level, send_roots_list_changed) now emits a runtime DeprecationWarning from inside the SDK, because these methods construct the newly-@deprecated types internally — the PR only silences this for this repo's own test suite via the new pyproject.toml filterwarnings entry. Downstream test suites using filterwarnings = ["error"] will start failing on upgrade just by calling these wrappers, and the warning's stack frame points at SDK internals rather than user code. Consider making this a deliberate choice: either decorate the convenience methods themselves (so the warning is attributed to user code), use deprecated(..., category=None) for type-checker-only deprecation matching the "advisory only" framing, or suppress the warning at the internal construction sites.
Extended reasoning...
What happens. typing_extensions.deprecated with the default category emits a DeprecationWarning when the decorated class is instantiated (the PR's own test_sep_2577_deprecated_class_warns_on_instantiation pins this). The SDK's convenience wrappers construct these classes on every call: ServerSession.send_log_message builds LoggingMessageNotification(Params) (src/mcp/server/session.py:135-142), and Context.debug/info/warning/error all funnel into it; ServerSession.create_message builds CreateMessageRequest(Params); ServerSession.list_roots builds ListRootsRequest/ListRootsResult; ClientSession.set_logging_level builds SetLevelRequest(Params); ClientSession.send_roots_list_changed builds RootsListChangedNotification; ClientPeer.sample/list_roots build CreateMessageRequestParams and validate CreateMessageResult/ListRootsResult. None of these wrapper methods are themselves marked @deprecated.\n\nConcrete walkthrough. A downstream MCP server has a tool that calls await ctx.info("processing"). After upgrading to this release: (1) Context.info → Context.log → ServerSession.send_log_message; (2) send_log_message executes types.LoggingMessageNotification(params=types.LoggingMessageNotificationParams(...)); (3) both constructors are wrapped by @deprecated(...), so warnings.warn(..., DeprecationWarning, stacklevel=2) fires twice, with the triggering frame inside mcp/server/session.py; (4) if the downstream project's pytest config has filterwarnings = ["error"] (the configuration this repo's own AGENTS.md prescribes, and a common downstream setup), every test that exercises that tool now errors with "DeprecationWarning: LoggingMessageNotificationParams is deprecated as of 2026-07-28 (SEP-2577)." — naming an internal type the user never wrote. Outside pytest the default CPython filters hide library-triggered DeprecationWarnings, but pytest surfaces them by default and -W error users hit them too.\n\nWhy nothing in the PR prevents it. The PR adds a scoped filterwarnings ignore to this repo's pyproject.toml precisely because the SDK's own internals trip the warning — but that suppression does not ship to downstream consumers. The # pyright: ignore[reportDeprecated] comments only silence the static checker, not the runtime warning.\n\nOn the counter-argument that this is intentional. It's true these wrappers correspond to the very features SEP-2577 deprecates, so one can argue warning on their use is the desired advisory signal. But three things suggest this wasn't a fully deliberate UX choice: the PR description frames SEP-2577 as "advisory only"; the wrapper methods themselves carry no @deprecated decoration or docstring note, so the user-facing API gives no static signal while the runtime warning fires from SDK-internal frames (poor attribution — the warning's stacklevel points at session.py, not the user's call site); and the PR had to add a repo-wide warning filter just to keep its own suite green, which is exactly the breakage downstream suites will experience without that shield. If warning on use of these features is intended, the cleaner mechanism is to decorate send_log_message/create_message/list_roots/set_logging_level/send_roots_list_changed (and the Context logging helpers) themselves, so type checkers flag user code and the runtime warning is attributed to the caller.\n\nSuggested fixes (any one resolves it): (a) use deprecated(..., category=None) on the type classes so the deprecation is type-checker/IDE/JSON-schema-only, matching the "advisory only" framing, and optionally decorate the convenience methods for the runtime signal; (b) keep the class-level runtime warning but wrap the SDK's internal constructions in warnings.catch_warnings() (or construct via model_construct/an undecorated internal alias) so only direct downstream construction of the types warns; or (c) explicitly document that calling these wrappers emits a DeprecationWarning and that downstream filterwarnings=["error"] suites need their own scoped ignore — making the breakage an acknowledged, documented trade-off rather than a surprise.
|
|
||
| If the client returns data that doesn't match the schema, `elicit()` raises a `pydantic.ValidationError`. | ||
|
|
||
| ### Sampling | ||
|
|
||
| Tools can interact with LLMs through sampling (generating text): | ||
|
|
||
| <!-- snippet-source examples/snippets/servers/sampling.py --> | ||
| ```python | ||
| from mcp.server.mcpserver import Context, MCPServer | ||
| from mcp.types import SamplingMessage, TextContent | ||
|
|
||
| mcp = MCPServer(name="Sampling Example") | ||
|
|
||
|
|
||
| @mcp.tool() | ||
| async def generate_poem(topic: str, ctx: Context) -> str: | ||
| """Generate a poem using LLM sampling.""" | ||
| prompt = f"Write a short poem about {topic}" | ||
|
|
||
| result = await ctx.session.create_message( | ||
| messages=[ | ||
| SamplingMessage( | ||
| role="user", | ||
| content=TextContent(type="text", text=prompt), | ||
| ) | ||
| ], | ||
| max_tokens=100, | ||
| ) | ||
|
|
||
| # Since we're not passing tools param, result.content is single content | ||
| if result.content.type == "text": | ||
| return result.content.text | ||
| return str(result.content) | ||
| ``` | ||
|
|
||
| _Full example: [examples/snippets/servers/sampling.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/sampling.py)_ | ||
| <!-- /snippet-source --> | ||
|
|
||
| ### Logging and Notifications | ||
|
|
||
| Tools can send logs and notifications through the context: |
There was a problem hiding this comment.
🟡 After this PR removes the Sampling section, README.v2.md still lists await ctx.session.create_message(messages, max_tokens=...) in the "Context Properties and Methods" list (line 1068) with no deprecation note, leaving an orphaned pointer to a SEP-2577-deprecated feature, and the retained "Logging and Notifications" section likewise carries no deprecation note. Consider removing or annotating the create_message bullet and adding a brief "Deprecated as of 2026-07-28 (SEP-2577)" note to the Logging section for consistency.
Extended reasoning...
What the issue is. This PR implements SEP-2577 by deprecating roots, sampling, and logging, and its documentation strategy (per the PR description) is to drop the sampling-specific docs rather than decorate them with suppressions: the README's entire ### Sampling section, its TOC entry, and examples/snippets/servers/sampling.py are all removed. However, the "Context Properties and Methods" list further down still contains the bullet await ctx.session.create_message(messages, max_tokens=...) - Request LLM sampling/completion (README.v2.md line 1068), and a search of the post-PR README for "deprecat"/"SEP-2577" returns no hits.
How it manifests. A reader scanning the Context API list now finds create_message recommended as a normal context capability, but the section that previously explained sampling (and showed the example it linked to) no longer exists anywhere in the README, and nothing tells the reader the feature is deprecated as of 2026-07-28. Similarly, the ### Logging and Notifications section (line 933) is kept in full even though logging is equally deprecated by SEP-2577, with no note. The only place the deprecation is documented is docs/migration.md.
Step-by-step. (1) Pre-PR, README.v2.md had a ### Sampling section with a full create_message example, plus the line-1068 bullet pointing at the same feature. (2) This PR deletes the section and TOC entry but leaves the bullet untouched. (3) Post-PR, line 1068 is the only sampling documentation left in the README, it carries no deprecation context, and grepping the file for "SEP-2577" or "deprecat" finds nothing. (4) The Logging section at line 933 remains fully documented with the same absence of any note.
Why this is worth flagging despite the PR's stated rationale. One verifier argued this follows the PR's deliberate "drop the snippet rather than add suppressions" approach and that nothing left in the README is factually wrong (the features remain functional for ≤ 2025-11-25 sessions). That's fair as far as the code-snippet docs go — the Logging section's backing snippet uses ctx.debug/info/... and needs no suppression. But the leftover create_message bullet is not consistent with that rationale either way: if the policy is "remove sampling docs," the bullet was missed; if the policy is "keep accurate references," it now lacks the context the deleted section used to provide. Either way the inconsistency is introduced by this PR's own doc edits and is trivially cheap to fix.
How to fix. Either delete the create_message bullet from the Context Properties and Methods list, or annotate it (and optionally the ### Logging and Notifications heading) with a one-line "Deprecated as of 2026-07-28 (SEP-2577); still functional for sessions negotiating ≤ 2025-11-25" note, mirroring the wording already used in docs/migration.md.
Severity. Documentation-only and non-blocking — filed as a nit.
Closes #2805. Implements SEP-2577 for the 2026-07-28 spec.
Summary
The deprecation is advisory only - no wire-level changes, capability negotiation is unchanged, and every type stays fully functional for sessions negotiating 2025-11-25 and earlier.
ClientCapabilities.roots,.sampling,.tasks.requests.sampling,ServerCapabilities.logging) marked withAnnotated[T, deprecated(...)]- warns on attribute access (not on construction/validation) and sets the JSON-schemadeprecatedflag.@deprecated.SamplingMessageContentBlockandLoggingLevelare type aliases, so they carry docstring notes only. The SEP's*ResultResponsewrappers have no counterpart in this SDK.Keeping the build green
filterwarningsentry inpyproject.tomlsilences the advisory runtimeDeprecationWarning, since the SDK still builds these types internally to serve older sessions.# pyright: ignore[reportDeprecated].Conformance
SEP-2577 is advisory only with no observable wire behavior, so there is nothing for the conformance suite to assert - this is the expected "no matching test" case rather than a gap.
AI Disclaimer
This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.