Skip to content

Deprecate roots, sampling, and logging methods per SEP-2577#2926

Merged
Kludex merged 7 commits into
mainfrom
deprecate-roots-sampling-logging-methods
Jun 20, 2026
Merged

Deprecate roots, sampling, and logging methods per SEP-2577#2926
Kludex merged 7 commits into
mainfrom
deprecate-roots-sampling-logging-methods

Conversation

@Kludex

@Kludex Kludex commented Jun 20, 2026

Copy link
Copy Markdown
Member

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: ignore suppressions across the SDK that builds those types to serve older sessions).

Methods marked with typing_extensions.deprecated:

  • Sampling: ServerSession.create_message(), ClientPeer.sample()
  • Roots: ServerSession.list_roots(), ClientPeer.list_roots(), ClientSession.send_roots_list_changed(), Client.send_roots_list_changed()
  • Logging: ServerSession.send_log_message(), ClientSession.set_logging_level(), Client.set_logging_level(), and the MCPServer Context helpers log() / debug() / info() / warning() / error()

Type checkers, IDEs, and the runtime now warn where these are called.

Keeping the build green

  • A scoped filterwarnings entry in pyproject.toml silences the advisory runtime DeprecationWarning, since the SDK calls these methods internally (e.g. ctx.debug -> log -> send_log_message).
  • Internal/test/example call sites carry # pyright: ignore[reportDeprecated].
  • Added a pytest.warns test 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.

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.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Kludex added 3 commits June 20, 2026 17:40
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.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 canonical ctx.debug/info/warning/error and ctx.session.create_message examples (README.v2.md logging/notifications, sampling, and progress sections, plus the synced examples/snippets), and the Context class docstring in src/mcp/server/mcpserver/context.py still leads with ctx.info/ctx.debug/ctx.warning/ctx.error as the first usage example — yet none of these surfaces mention the SEP-2577 deprecation (only docs/migration.md got 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 from examples/snippets (via the snippet-source markers checked by scripts/update_readme_snippets.py --check), those suppression comments leak verbatim into README.v2.md — the package'''s primary user-facing readme (pyproject.toml sets readme = "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, the Context class docstring in src/mcp/server/mcpserver/context.py (the "To use context in a tool function" example near the top of the class) still opens with await ctx.info(...), ctx.debug(...), ctx.warning(...), ctx.error(...) as the canonical first example, even though this same PR adds @deprecated to 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_data example, and paste await ctx.info("...") # pyright: ignore[reportDeprecated] into their server — copying a suppression comment they have no context for. (3) Alternatively, they hover Context in their IDE or open the API docs and see the class docstring telling them the first thing to do with a context is call ctx.info/ctx.debug/etc. (4) At runtime they then get an MCPDeprecationWarning (visible by default, since it subclasses UserWarning) 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 — Because typing_extensions.deprecated emits 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, and Client.set_logging_level/Client.send_roots_list_changed -> the corresponding ClientSession methods) means a single user call to ctx.info(...) emits three MCPDeprecationWarnings, two of them attributed to SDK-internal lines rather than the user's code — the # pyright: ignore[reportDeprecated] comments and the pyproject.toml filterwarnings 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, as tests/interaction/transports/_stdio_server.py already does) so users see exactly one warning at their own call site.

    Extended reasoning...

    What happens

    typing_extensions.deprecated wraps the decorated function and calls warnings.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) call self.log(...), which calls self.request_context.session.send_log_message(...) — both also decorated.
    • Client.set_logging_level calls ClientSession.set_logging_level (src/mcp/client/client.py:204).
    • Client.send_roots_list_changed calls ClientSession.send_roots_list_changed (src/mcp/client/client.py:323).

    Concrete walk-through

    A user's tool does await ctx.info(\"Starting\"):

    1. Context.info is decorated → warning #1, attributed (correctly) to the user's call site: \"info is deprecated as of 2026-07-28 (SEP-2577).\"
    2. info runs await self.log(\"info\", data, ...)log is also decorated → warning #2, attributed to mcpserver/context.py (the body of info).
    3. log runs await self.request_context.session.send_log_message(...) — also decorated → warning #3, attributed to mcpserver/context.py (the body of log).

    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 filterwarnings entry added to pyproject.toml is under [tool.pytest.ini_options], so it only configures this repo's pytest run — user applications never see it.
    • MCPDeprecationWarning deliberately subclasses UserWarning so 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 internal send_log_message call in warnings.catch_warnings() + simplefilter(\"ignore\", MCPDeprecationWarning) — confirming the cascade is real, but the same treatment is missing from the library's own internal call sites in src/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 to Client.set_logging_level and Client.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.info and Context.log both call a private _log/_send_log_message helper; Client.set_logging_level calls a private ClientSession._set_logging_level), or wrap the internal delegation calls in warnings.catch_warnings() + simplefilter(\"ignore\", MCPDeprecationWarning) exactly as tests/interaction/transports/_stdio_server.py does. Either way, a user call then emits exactly one warning, attributed to the user's own line.

Comment thread src/mcp/server/context.py Outdated
Comment thread README.v2.md
Comment on lines 948 to 954
"""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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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_message and ctx.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

  1. A new user reads the published README on PyPI, follows the Sampling section, and copies the generate_poem example at lines 948-954.
  2. They see # pyright: ignore[reportDeprecated] on the create_message call but no surrounding text mentions any deprecation; the README otherwise actively recommends this API.
  3. If they delete the comment (it looks like internal noise), pyright in strict mode flags create_message as deprecated — the first time they learn about SEP-2577, with no pointer to the migration guide.
  4. 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_message example 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.

Comment thread docs/migration.md Outdated
Comment thread src/mcp/client/client.py
Kludex added 3 commits June 20, 2026 18:11
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.
@Kludex Kludex merged commit 4573e4a into main Jun 20, 2026
31 checks passed
@Kludex Kludex deleted the deprecate-roots-sampling-logging-methods branch June 20, 2026 16:25
maxisbey added a commit that referenced this pull request Jun 20, 2026
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).
maxisbey added a commit that referenced this pull request Jun 20, 2026
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants