Deprecate roots, sampling, and logging methods per SEP-2577 by Kludex · Pull Request #2926 · modelcontextprotocol/python-sdk · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.v2.md
21 changes: 20 additions & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,26 @@ Tasks are expected to return as a separate MCP extension in a future release.

## Deprecations

<!-- Add deprecations below -->
### Roots, Sampling, and Logging methods deprecated (SEP-2577)

[SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) deprecates the Roots, Sampling, and Logging features as of the 2026-07-28 spec. The deprecation is advisory only: there are no wire-level changes, capability negotiation is unchanged, and every method keeps working for sessions negotiating 2025-11-25 and earlier.

The user-facing methods for these features now carry `typing_extensions.deprecated`, so type checkers, IDEs, and the runtime surface a deprecation warning where they are called:

- 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()`, `Connection.log()`, `ClientSession.set_logging_level()`, `Client.set_logging_level()`, `mcp.server.context.Context.log()` (the lowlevel `Context`), and the `MCPServer` `Context` helpers `log()`, `debug()`, `info()`, `warning()`, `error()`

The runtime warning is emitted as `mcp.MCPDeprecationWarning`, which subclasses `UserWarning` (not `DeprecationWarning`) so it is visible by default. To silence it, filter that category:

```python
import warnings
from mcp import MCPDeprecationWarning

warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
```

No migration is required during the deprecation window. New code should avoid building on these features, since they may be removed in a future spec version.

## Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,13 @@ def test_multiple_content_types() -> list[TextContent | ImageContent | EmbeddedR
@mcp.tool()
async def test_tool_with_logging(ctx: Context) -> str:
"""Tests tool that emits log messages during execution"""
await ctx.info("Tool execution started")
await ctx.info("Tool execution started") # pyright: ignore[reportDeprecated]
await asyncio.sleep(0.05)

await ctx.info("Tool processing data")
await ctx.info("Tool processing data") # pyright: ignore[reportDeprecated]
await asyncio.sleep(0.05)

await ctx.info("Tool execution completed")
await ctx.info("Tool execution completed") # pyright: ignore[reportDeprecated]
return "Tool with logging executed successfully"


Expand All @@ -176,7 +176,7 @@ 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(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
max_tokens=100,
)
Expand Down Expand Up @@ -314,13 +314,13 @@ def test_error_handling() -> str:
@mcp.tool()
async def test_reconnection(ctx: Context) -> str:
"""Tests SSE polling by closing stream mid-call (SEP-1699)"""
await ctx.info("Before disconnect")
await ctx.info("Before disconnect") # pyright: ignore[reportDeprecated]

await ctx.close_sse_stream()

await asyncio.sleep(0.2) # Wait for client to reconnect

await ctx.info("After reconnect")
await ctx.info("After reconnect") # pyright: ignore[reportDeprecated]
return "Reconnection test completed"


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ

# Send the specified number of notifications with the given interval
for i in range(count):
await ctx.session.send_log_message(
await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="info",
data=f"Notification {i + 1}/{count} from caller: {caller}",
logger="notification_stream",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ
for i in range(count):
# Include more detailed message for resumability demonstration
notification_msg = f"[{i + 1}/{count}] Event from '{caller}' - Use Last-Event-ID to resume if disconnected"
await ctx.session.send_log_message(
await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="info",
data=notification_msg,
logger="notification_stream",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ
content=[types.TextContent(type="text", text="Error: checkpoint_every must be between 1 and 20")]
)

await ctx.session.send_log_message(
await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="info",
data=f"Starting batch processing of {items} items...",
logger="process_batch",
Expand All @@ -86,7 +86,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ
await anyio.sleep(0.5)

# Report progress
await ctx.session.send_log_message(
await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="info",
data=f"[{i}/{items}] Processing item {i}",
logger="process_batch",
Expand All @@ -95,7 +95,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ

# Checkpoint: close stream to trigger client reconnect
if i % checkpoint_every == 0 and i < items:
await ctx.session.send_log_message(
await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="info",
data=f"Checkpoint at item {i} - closing SSE stream for polling",
logger="process_batch",
Expand Down
8 changes: 4 additions & 4 deletions examples/snippets/servers/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
async def process_data(data: str, ctx: Context) -> str:
"""Process data with logging."""
# Different log levels
await ctx.debug(f"Debug: Processing '{data}'")
await ctx.info("Info: Starting processing")
await ctx.warning("Warning: This is experimental")
await ctx.error("Error: (This is just a demo)")
await ctx.debug(f"Debug: Processing '{data}'") # pyright: ignore[reportDeprecated]
await ctx.info("Info: Starting processing") # pyright: ignore[reportDeprecated]
await ctx.warning("Warning: This is experimental") # pyright: ignore[reportDeprecated]
await ctx.error("Error: (This is just a demo)") # pyright: ignore[reportDeprecated]

# Notify about resource changes
await ctx.session.send_resource_list_changed()
Expand Down
2 changes: 1 addition & 1 deletion examples/snippets/servers/sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[
SamplingMessage(
role="user",
Expand Down
4 changes: 2 additions & 2 deletions examples/snippets/servers/tool_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@mcp.tool()
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
"""Execute a task with progress updates."""
await ctx.info(f"Starting: {task_name}")
await ctx.info(f"Starting: {task_name}") # pyright: ignore[reportDeprecated]

for i in range(steps):
progress = (i + 1) / steps
Expand All @@ -15,6 +15,6 @@ async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str
total=1.0,
message=f"Step {i + 1}/{steps}",
)
await ctx.debug(f"Completed step {i + 1}")
await ctx.debug(f"Completed step {i + 1}") # pyright: ignore[reportDeprecated]

return f"Task '{task_name}' completed"
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ filterwarnings = [
"error",
# pywin32 internal deprecation warning
"ignore:getargs.*The 'u' format is deprecated:DeprecationWarning",
# SEP-2577 deprecates the roots/sampling/logging methods; the SDK still calls
# them internally (e.g. `ctx.debug` -> `log` -> `send_log_message`), so the
# advisory warning is silenced. Tests asserting it opt back in with pytest.warns.
"ignore:.*is deprecated as of 2026-07-28 \\(SEP-2577\\).:mcp.MCPDeprecationWarning",
]

[tool.markdown.lint]
Expand Down
3 changes: 2 additions & 1 deletion src/mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .client.stdio import StdioServerParameters, stdio_client
from .server.session import ServerSession
from .server.stdio import stdio_server
from .shared.exceptions import MCPError, UrlElicitationRequiredError
from .shared.exceptions import MCPDeprecationWarning, MCPError, UrlElicitationRequiredError
from .types import (
CallToolRequest,
ClientCapabilities,
Expand Down Expand Up @@ -96,6 +96,7 @@
"ListToolsResult",
"LoggingLevel",
"LoggingMessageNotification",
"MCPDeprecationWarning",
"MCPError",
"Notification",
"PingRequest",
Expand Down
9 changes: 7 additions & 2 deletions src/mcp/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
from dataclasses import KW_ONLY, dataclass, field
from typing import Any

from typing_extensions import deprecated

from mcp.client._memory import InMemoryTransport
from mcp.client._transport import Transport
from mcp.client.session import ClientSession, ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
from mcp.client.streamable_http import streamable_http_client
from mcp.server import Server
from mcp.server.mcpserver import MCPServer
from mcp.shared.dispatcher import ProgressFnT
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.types import (
CallToolResult,
CompleteResult,
Expand Down Expand Up @@ -195,9 +198,10 @@ async def send_progress_notification(
message=message,
)

@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def set_logging_level(self, level: LoggingLevel, *, meta: RequestParamsMeta | None = None) -> EmptyResult:
"""Set the logging level on the server."""
return await self.session.set_logging_level(level=level, meta=meta)
return await self.session.set_logging_level(level=level, meta=meta) # pyright: ignore[reportDeprecated]
Comment thread
Kludex marked this conversation as resolved.

async def list_resources(
self,
Expand Down Expand Up @@ -312,7 +316,8 @@ async def list_tools(self, *, cursor: str | None = None, meta: RequestParamsMeta
"""List available tools from the server."""
return await self.session.list_tools(params=PaginatedRequestParams(cursor=cursor, _meta=meta))

@deprecated("The roots capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def send_roots_list_changed(self) -> None:
"""Send a notification that the roots list has changed."""
# TODO(Marcelo): Currently, there is no way for the server to handle this. We should add support.
await self.session.send_roots_list_changed()
await self.session.send_roots_list_changed() # pyright: ignore[reportDeprecated]
6 changes: 4 additions & 2 deletions src/mcp/client/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
import anyio.abc
import anyio.lowlevel
from pydantic import BaseModel, TypeAdapter, ValidationError
from typing_extensions import Self, TypeVar
from typing_extensions import Self, TypeVar, deprecated

from mcp import types
from mcp.client._transport import ReadStream, WriteStream
from mcp.shared._compat import resync_tracer
from mcp.shared.dispatcher import CallOptions, DispatchContext, Dispatcher, ProgressFnT
from mcp.shared.exceptions import MCPError
from mcp.shared.exceptions import MCPDeprecationWarning, MCPError
from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher
from mcp.shared.message import ClientMessageMetadata, SessionMessage
from mcp.shared.session import RequestResponder
Expand Down Expand Up @@ -386,6 +386,7 @@ async def send_progress_notification(
)
)

@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def set_logging_level(
self,
level: types.LoggingLevel,
Expand Down Expand Up @@ -550,6 +551,7 @@ async def list_tools(self, *, params: types.PaginatedRequestParams | None = None

return result

@deprecated("The roots capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def send_roots_list_changed(self) -> None:
"""Send a roots/list_changed notification."""
await self.send_notification(types.RootsListChangedNotification())
Expand Down
4 changes: 3 additions & 1 deletion src/mcp/server/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@

import anyio
from pydantic import BaseModel
from typing_extensions import deprecated

from mcp.shared.dispatcher import CallOptions, Outbound
from mcp.shared.exceptions import NoBackChannelError
from mcp.shared.exceptions import MCPDeprecationWarning, NoBackChannelError
from mcp.shared.peer import Meta, dump_params
from mcp.types import (
ClientCapabilities,
Expand Down Expand Up @@ -209,6 +210,7 @@ async def ping(self, *, meta: Meta | None = None, opts: CallOptions | None = Non
"""
await self.send_raw_request("ping", dump_params(None, meta), opts)

@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def log(self, level: LoggingLevel, data: Any, logger: str | None = None, *, meta: Meta | None = None) -> None:
"""Send a `notifications/message` log entry on the standalone stream. Best-effort."""
params: dict[str, Any] = {"level": level, "data": data}
Expand Down
4 changes: 3 additions & 1 deletion src/mcp/server/context.py
Loading
Loading