Skip to content

Deprecate roots, sampling, and logging per SEP-2577#2922

Closed
Kludex wants to merge 1 commit into
mainfrom
deprecate-roots-sampling-logging-sep-2577
Closed

Deprecate roots, sampling, and logging per SEP-2577#2922
Kludex wants to merge 1 commit into
mainfrom
deprecate-roots-sampling-logging-sep-2577

Conversation

@Kludex

@Kludex Kludex commented Jun 20, 2026

Copy link
Copy Markdown
Member

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.

  • 4 capability fields (ClientCapabilities.roots, .sampling, .tasks.requests.sampling, ServerCapabilities.logging) marked with Annotated[T, deprecated(...)] - warns on attribute access (not on construction/validation) and sets the JSON-schema deprecated flag.
  • 17 type classes (the Roots/Sampling/Logging request/result/notification/param types) marked with a class-level @deprecated.
  • SamplingMessageContentBlock and LoggingLevel are type aliases, so they carry docstring notes only. The SEP's *ResultResponse wrappers have no counterpart in this SDK.

Keeping the build green

  • A scoped filterwarnings entry in pyproject.toml silences the advisory runtime DeprecationWarning, since the SDK still builds these types internally to serve older sessions.
  • The SDK's own references carry # pyright: ignore[reportDeprecated].
  • Rather than decorate user-facing docs with suppressions, the sampling-specific example snippet and its README section are dropped, and the incidental sampling callback is trimmed from the general stdio client example.

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.

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.
Comment on lines 174 to 182
@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,
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Can't we just delete this test?

Comment on lines 420 to 424
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,
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Can't we just delete this?

Comment thread src/mcp/server/session.py
Comment on lines 135 to 142
) -> 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,

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.

🔴 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.infoContext.logServerSession.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.

Comment thread README.v2.md
Comment on lines 930 to 935

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:

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.

🟡 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.

@Kludex Kludex closed this Jun 20, 2026
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.

Implement SEP-2577: Deprecate Roots, Sampling, and Logging

1 participant