Deprecate roots, sampling, and logging methods per SEP-2577#2926
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 method keeps working for sessions negotiating 2025-11-25 and earlier. Mark the user-facing methods for these features with `typing_extensions.deprecated` so type checkers, IDEs, and the runtime warn at the call site: `create_message`/`sample` (sampling), `list_roots` / `send_roots_list_changed` (roots), `send_log_message` / `set_logging_level` and the `MCPServer` `Context` log helpers (logging). Unlike decorating the schema types, this keeps the footprint small while still making users aware. The advisory runtime warning is silenced via a scoped `filterwarnings` entry since the SDK calls these methods internally (e.g. `ctx.debug` -> `log` -> `send_log_message`) to serve older sessions.
There was a problem hiding this comment.
I didn't find any bugs — the deprecation decorators, pyright suppressions, and the scoped filterwarnings entry all look consistent — but this deliberately changes the public API surface (deprecating ctx logging helpers, create_message, list_roots, etc. across the SDK), so it deserves a maintainer's sign-off rather than an auto-approval.
Extended reasoning...
Overview
This PR implements SEP-2577 by marking the user-facing Roots, Sampling, and Logging methods with typing_extensions.deprecated across src/mcp/client/client.py, client/session.py, server/session.py, server/context.py, server/mcpserver/context.py, and shared/peer.py. The remaining ~34 changed files are mechanical: # pyright: ignore[reportDeprecated] suppressions in tests/examples/docs, a scoped filterwarnings ignore in pyproject.toml, a new pytest.warns test pinning the runtime warning, and a migration-guide entry.
Security risks
None. The change is advisory-only metadata (decorators, comments, docs); there are no wire-level, auth, or behavioral changes. The filterwarnings ignore is narrowly scoped to the SEP-2577 message pattern.
Level of scrutiny
The mechanical portions (suppression comments, docs) need little scrutiny, and I verified the decorator placement (including on the @overload stacks in ServerSession.create_message and ClientPeer.sample) and the warning-filter regex are consistent with the new test. However, deprecating widely-used public methods (ctx.info/debug/warning/error, create_message, list_roots, send_log_message, set_logging_level) is an SDK API-policy decision — including the choice to scope deprecation to user-facing methods only and to globally silence the runtime warning in the SDK's own test suite — which warrants maintainer judgment rather than automated approval.
Other factors
The bug hunting system reported no issues, CI-relevant concerns (strict pyright, filterwarnings = error) are addressed by the suppressions and the scoped ignore, and there is a regression test asserting the DeprecationWarning fires. There are no outstanding reviewer comments on the PR.
The SEP-2577 method deprecations now warn via `mcp.MCPDeprecationWarning`, a `UserWarning` subclass, instead of the built-in `DeprecationWarning`. Python silences `DeprecationWarning` by default outside `__main__`, so most users never see it; inheriting from `UserWarning` makes the deprecation visible by default. Pass `category=MCPDeprecationWarning` to every `@deprecated` decorator, export the class from `mcp`, and document how to silence it.
…sage
The `debug`/`info`/`warning`/`error` convenience helpers delegate to
`Context.log`, so deprecating `log` alone covers them - they warn through it
instead of each carrying their own marker. Drop their `@deprecated` decorators
and the now-stale `# pyright: ignore[reportDeprecated]` on their call sites.
Reword every logging deprecation ("The logging capability is deprecated as of
...") so the message names the feature rather than the individual method, and
widen the `filterwarnings` pattern to match the new wording.
The `with anyio.fail_after(...)` block was the last statement in the test and nested inside `pytest.warns`, so coverage.py flagged its `->exit` arc as a partial branch on 3.14. Move `fail_after` to the outer `with` and add a trailing assertion (the call reached the wire), matching the sibling `list_roots` tests.
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🟡
README.v2.md:982-991— These user-facing docs now show bare# pyright: ignore[reportDeprecated]comments on the canonicalctx.debug/info/warning/errorandctx.session.create_messageexamples (README.v2.md logging/notifications, sampling, and progress sections, plus the syncedexamples/snippets), and theContextclass docstring insrc/mcp/server/mcpserver/context.pystill leads withctx.info/ctx.debug/ctx.warning/ctx.erroras the first usage example — yet none of these surfaces mention the SEP-2577 deprecation (onlydocs/migration.mdgot a note). Consider adding a brief deprecation note to the affected README sections and the Context docstring, or stripping the suppression comments from the rendered examples so readers don'''t copy an unexplained pyright ignore.Extended reasoning...
What the issue is. This PR deprecates the Roots/Sampling/Logging user-facing methods per SEP-2577 and adds
# pyright: ignore[reportDeprecated]to internal, test, and example call sites to keep the build green. Because the README code blocks are mechanically synced fromexamples/snippets(via the snippet-source markers checked byscripts/update_readme_snippets.py --check), those suppression comments leak verbatim intoREADME.v2.md— the package'''s primary user-facing readme (pyproject.tomlsetsreadme = "README.v2.md").Where it shows up. The Progress example (~lines 360-372 and the duplicate at ~707-719), the Sampling example (~line 951,
ctx.session.create_message), and the Logging and Notifications example (~lines 985-988,ctx.debug/info/warning/error) all now display bare# pyright: ignore[reportDeprecated]comments while the surrounding prose still presents these APIs as the recommended way to log, notify, and sample — with no mention that they were just deprecated. Separately, theContextclass docstring insrc/mcp/server/mcpserver/context.py(the "To use context in a tool function" example near the top of the class) still opens withawait ctx.info(...),ctx.debug(...),ctx.warning(...),ctx.error(...)as the canonical first example, even though this same PR adds@deprecatedto exactly those helpers a few hundred lines below (lines 268-289). That docstring is rendered by mkdocstrings into the API reference.Why existing changes don'''t cover it. The PR adds a thorough deprecation entry to
docs/migration.md, but neither the README sections nor the in-file Context docstring were updated, so the two surfaces a new user is most likely to read first continue to advertise the deprecated APIs without any notice.Concrete walk-through of the reader experience. (1) A new user installs the SDK and reads README.v2.md (or the PyPI page, which renders the same file). (2) They reach "Logging and Notifications", copy the
process_dataexample, and pasteawait ctx.info("...") # pyright: ignore[reportDeprecated]into their server — copying a suppression comment they have no context for. (3) Alternatively, they hoverContextin their IDE or open the API docs and see the class docstring telling them the first thing to do with a context is callctx.info/ctx.debug/etc. (4) At runtime they then get anMCPDeprecationWarning(visible by default, since it subclassesUserWarning) for code the official docs just told them to write, with no doc-side explanation of why.Impact and fix. This is documentation-only — no functional or wire-level behavior is affected, and the deprecation itself is advisory. A small fix suffices: add a one- or two-sentence SEP-2577 deprecation note to the affected README sections (and ideally the Context class docstring example), and/or have the snippet-sync step strip
# pyright: ignore[reportDeprecated]comments from the rendered README so user-facing examples stay clean. Either approach keeps the snippet-sync check green.Why this is a nit. All verifiers agreed this is real but non-blocking: the methods keep working through the deprecation window, the migration guide does document the change, and leaving the examples on the deprecated APIs is arguably consistent with the PR'''s advisory-only stance. It'''s an easy polish item rather than something that should hold up the merge.
-
🔴
src/mcp/server/mcpserver/context.py:268-289— Becausetyping_extensions.deprecatedemits its warning at runtime on every call, the SDK's own internal delegation to the deprecated methods (e.g.Context.info->Context.log->ServerSession.send_log_message, andClient.set_logging_level/Client.send_roots_list_changed-> the correspondingClientSessionmethods) means a single user call toctx.info(...)emits threeMCPDeprecationWarnings, two of them attributed to SDK-internal lines rather than the user's code — the# pyright: ignore[reportDeprecated]comments and thepyproject.tomlfilterwarnings entry only help statically / in the SDK's own pytest run, not in end-user applications. Have the deprecated public methods delegate to a non-deprecated private implementation (or suppress the warning at internal call sites, astests/interaction/transports/_stdio_server.pyalready does) so users see exactly one warning at their own call site.Extended reasoning...
What happens
typing_extensions.deprecatedwraps the decorated function and callswarnings.warn(..., category=MCPDeprecationWarning, stacklevel=2)every time the function is invoked. Several of the newly deprecated methods are themselves implemented by calling other newly deprecated methods, so the warning fires once per hop in the chain and the inner hops are attributed to SDK-internal source lines:Context.debug/info/warning/error(src/mcp/server/mcpserver/context.py:268-289) callself.log(...), which callsself.request_context.session.send_log_message(...)— both also decorated.Client.set_logging_levelcallsClientSession.set_logging_level(src/mcp/client/client.py:204).Client.send_roots_list_changedcallsClientSession.send_roots_list_changed(src/mcp/client/client.py:323).
Concrete walk-through
A user's tool does
await ctx.info(\"Starting\"):Context.infois decorated → warning #1, attributed (correctly) to the user's call site:\"infois deprecated as of 2026-07-28 (SEP-2577).\"inforunsawait self.log(\"info\", data, ...)—logis also decorated → warning #2, attributed tomcpserver/context.py(the body ofinfo).logrunsawait self.request_context.session.send_log_message(...)— also decorated → warning #3, attributed tomcpserver/context.py(the body oflog).
Verifiers reproduced this empirically: under default warning filters, three visible
MCPDeprecationWarnings are emitted, two of which point at SDK-internal lines.Why the existing mitigations don't cover end users
# pyright: ignore[reportDeprecated]is purely a static-analysis suppression; it has no runtime effect.- The
filterwarningsentry added topyproject.tomlis under[tool.pytest.ini_options], so it only configures this repo's pytest run — user applications never see it. MCPDeprecationWarningdeliberately subclassesUserWarningso it is shown by default — which is exactly why the cascaded, misattributed warnings will surface in real applications.- The PR itself works around the runtime cascade in
tests/interaction/transports/_stdio_server.py(a subprocess that doesn't inherit the pytest filter) by wrapping the internalsend_log_messagecall inwarnings.catch_warnings()+simplefilter(\"ignore\", MCPDeprecationWarning)— confirming the cascade is real, but the same treatment is missing from the library's own internal call sites insrc/mcp/.
Impact
The PR's stated goal is that users "get a signal at their own call sites." Instead, the most common documented pattern (
ctx.info/ctx.debug/etc. in tools) produces duplicated warnings whose filenames point inside the SDK, which reads to users as a bug in the SDK rather than a deprecation of their own code. The same applies toClient.set_logging_levelandClient.send_roots_list_changed(two warnings each). One mitigating factor: Python's default "default" filter dedupes by (message, category, location), so the extra warnings appear once per location per process rather than on every call — but the noise and misattribution remain, and they only ever fire alongside a legitimate warning the user is already getting.How to fix
Have the deprecated public methods delegate to an undecorated private implementation (e.g.
Context.infoandContext.logboth call a private_log/_send_log_messagehelper;Client.set_logging_levelcalls a privateClientSession._set_logging_level), or wrap the internal delegation calls inwarnings.catch_warnings()+simplefilter(\"ignore\", MCPDeprecationWarning)exactly astests/interaction/transports/_stdio_server.pydoes. Either way, a user call then emits exactly one warning, attributed to the user's own line.
| """Generate a poem using LLM sampling.""" | ||
| prompt = f"Write a short poem about {topic}" | ||
|
|
||
| result = await ctx.session.create_message( | ||
| result = await ctx.session.create_message( # pyright: ignore[reportDeprecated] | ||
| messages=[ | ||
| SamplingMessage( | ||
| role="user", |
There was a problem hiding this comment.
🟡 README.v2.md (the package's published readme) still presents the freshly deprecated Sampling/Logging surface as the recommended API with no SEP-2577 note, and the only README change in this PR injects a bare # pyright: ignore[reportDeprecated] into the copy-paste sampling example with no explanation of what is being suppressed or why. Consider adding a brief SEP-2577 deprecation note next to the Sampling and Logging README sections (or an explanatory comment in the snippet) so readers aren't steered into deprecated APIs with zero signal.
Extended reasoning...
What the issue is
This PR deprecates the user-facing Roots/Sampling/Logging methods per SEP-2577 (ServerSession.create_message, ServerSession.send_log_message, the Context logging helpers, etc.) and adds a migration-guide entry — but README.v2.md, which is the package's published readme (readme = "README.v2.md" in pyproject.toml), still presents these as the recommended APIs with no deprecation signal at all:
- The tutorial examples around lines 363/372 and 710/719 use
ctx.info()/ctx.debug(). - The Context logging API list at ~691-695 documents
ctx.debug/info/warning/error/log. - The advanced session methods list at ~1104-1105 includes
ctx.session.send_log_messageandctx.session.create_message.
The string "SEP-2577" / "deprecat" appears nowhere in the README except in one place — the only README change in this PR.
The PR-introduced part
The README's code blocks are auto-synced from examples/snippets via scripts/update_readme_snippets.py, so the # pyright: ignore[reportDeprecated] added to examples/snippets/servers/sampling.py necessarily leaks into the published sampling tutorial at line 951. This is the only pyright suppression comment anywhere in README.v2.md, i.e. this PR is the first to put an unexplained type-checker suppression into user-facing copy-paste tutorial code. A reader pasting the snippet either keeps a mystery suppression or removes it and gets an unexplained reportDeprecated error from strict pyright — with no hint anywhere in the README about why.
Concrete walkthrough
- A new user reads the published README on PyPI, follows the Sampling section, and copies the
generate_poemexample at lines 948-954. - They see
# pyright: ignore[reportDeprecated]on thecreate_messagecall but no surrounding text mentions any deprecation; the README otherwise actively recommends this API. - If they delete the comment (it looks like internal noise), pyright in strict mode flags
create_messageas deprecated — the first time they learn about SEP-2577, with no pointer to the migration guide. - If they keep it, they ship code suppressing a warning they were never told about, and the same applies to every
ctx.info()/send_log_messageexample in the README that carries no annotation at all.
Why existing changes don't cover this
Only docs/migration.md was updated with the SEP-2577 entry. The README — which is what users actually see on PyPI/GitHub and what teaches these features — was touched only by the mechanical snippet sync, so the deprecation messaging and the published tutorial content are now inconsistent.
Mitigations / why this is a nit
The deprecation is advisory only: the methods keep working, capability negotiation is unchanged, and the migration guide explicitly says no migration is required during the deprecation window. So the README is not functionally wrong, and the suppression in the snippet is forced by the repo's strict pyright config plus the snippet-sync mechanism. This is a documentation/UX completeness gap rather than a functional bug.
How to fix
Add a one- or two-line SEP-2577 deprecation note (with a link to the migration-guide entry this PR adds) next to the README's Sampling and Logging sections, and/or replace the bare suppression in examples/snippets/servers/sampling.py with a short explanatory comment (e.g. # Sampling is deprecated as of 2026-07-28 (SEP-2577) but still supported) so the synced README snippet is self-explanatory.
Address PR review: `Connection.log()` sends the same `notifications/message` the logging capability covers and `Context.log`'s docstring steers users to it, so it now carries the same SEP-2577 `@deprecated` marker. Its two test call sites get `# pyright: ignore[reportDeprecated]`, and the migration guide lists it among the deprecated logging entry points.
The migration guide's Logging bullet named only the `MCPServer` `Context.log()`, but the lowlevel `mcp.server.context.Context.log()` is a distinct public class that this PR also decorates. Add it so users of that Context find their method.
The logging deprecations name the feature ("The logging capability is
deprecated as of ..."); roots and sampling still named the individual method.
Switch `list_roots`, `send_roots_list_changed`, `sample`, and `create_message`
to the same capability-level wording so every SEP-2577 warning is consistent.
After rebasing onto #2926: - Re-add # pyright: ignore[reportDeprecated] on the deprecated method calls in the reworked test_connection.py / test_server_context.py / test_stateless_mode.py (the rewrites had dropped main's suppressions) Fix to the client 4xx body-parsing patch (bb9b134): - A server's 4xx JSON-RPC error body may carry id:null (request rejected before its id was parsed). Surfacing that verbatim broke response correlation (client waits for id=N, gets id=null, hangs). Now use the parsed error data with the client's own request id. - Only surface the body when it's a JSONRPCError; anything else falls through to the status-derived stand-in. - Relax the two 'Session terminated' message-text assertions in test_streamable_http.py (the client now surfaces the server's actual message).
After rebasing onto #2926: - Re-add # pyright: ignore[reportDeprecated] on the deprecated method calls in the reworked test_connection.py / test_server_context.py / test_stateless_mode.py (the rewrites had dropped main's suppressions) Fix to the client 4xx body-parsing patch (bb9b134): - A server's 4xx JSON-RPC error body may carry id:null (request rejected before its id was parsed). Surfacing that verbatim broke response correlation (client waits for id=N, gets id=null, hangs). Now use the parsed error data with the client's own request id. - Only surface the body when it's a JSONRPCError; anything else falls through to the status-derived stand-in. - Relax the two 'Session terminated' message-text assertions in test_streamable_http.py (the client now surfaces the server's actual message).
Implements SEP-2577 for the 2026-07-28 spec (tracked in #2805).
Summary
The deprecation is advisory only - no wire-level changes, capability negotiation is unchanged, and every method keeps working for sessions negotiating 2025-11-25 and earlier.
This PR scopes the deprecation to the user-facing methods for Roots/Sampling/Logging, so users get a signal at their own call sites, without decorating every schema type and capability field (which would have required ~450 internal
# pyright: ignoresuppressions across the SDK that builds those types to serve older sessions).Methods marked with
typing_extensions.deprecated:ServerSession.create_message(),ClientPeer.sample()ServerSession.list_roots(),ClientPeer.list_roots(),ClientSession.send_roots_list_changed(),Client.send_roots_list_changed()ServerSession.send_log_message(),ClientSession.set_logging_level(),Client.set_logging_level(), and theMCPServerContexthelperslog()/debug()/info()/warning()/error()Type checkers, IDEs, and the runtime now warn where these are called.
Keeping the build green
filterwarningsentry inpyproject.tomlsilences the advisory runtimeDeprecationWarning, since the SDK calls these methods internally (e.g.ctx.debug->log->send_log_message).# pyright: ignore[reportDeprecated].pytest.warnstest asserting the deprecation fires, and a migration-guide entry.Conformance
SEP-2577 is advisory only with no observable wire behavior, so there is nothing for the conformance suite to assert.
AI Disclaimer
This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.