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

This commit is contained in:
Marcelo Trylesinski 2026-06-20 17:25:41 +01:00 • committed by GitHub
commit 4573e4ac33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 206 additions and 123 deletions

View file

@ -360,7 +360,7 @@ mcp = MCPServer(name="Progress Example")
@mcp.tool() @mcp.tool()
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str: async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
"""Execute a task with progress updates.""" """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): for i in range(steps):
progress = (i + 1) / steps progress = (i + 1) / steps
@ -369,7 +369,7 @@ async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str
total=1.0, total=1.0,
message=f"Step {i + 1}/{steps}", 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" return f"Task '{task_name}' completed"
``` ```
@ -707,7 +707,7 @@ mcp = MCPServer(name="Progress Example")
@mcp.tool() @mcp.tool()
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str: async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
"""Execute a task with progress updates.""" """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): for i in range(steps):
progress = (i + 1) / steps progress = (i + 1) / steps
@ -716,7 +716,7 @@ async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str
total=1.0, total=1.0,
message=f"Step {i + 1}/{steps}", 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" return f"Task '{task_name}' completed"
``` ```
@ -948,7 +948,7 @@ async def generate_poem(topic: str, ctx: Context) -> str:
"""Generate a poem using LLM sampling.""" """Generate a poem using LLM sampling."""
prompt = f"Write a short poem about {topic}" prompt = f"Write a short poem about {topic}"
result = await ctx.session.create_message( result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[ messages=[
SamplingMessage( SamplingMessage(
role="user", role="user",
@ -982,10 +982,10 @@ mcp = MCPServer(name="Notifications Example")
async def process_data(data: str, ctx: Context) -> str: async def process_data(data: str, ctx: Context) -> str:
"""Process data with logging.""" """Process data with logging."""
# Different log levels # Different log levels
await ctx.debug(f"Debug: Processing '{data}'") await ctx.debug(f"Debug: Processing '{data}'") # pyright: ignore[reportDeprecated]
await ctx.info("Info: Starting processing") await ctx.info("Info: Starting processing") # pyright: ignore[reportDeprecated]
await ctx.warning("Warning: This is experimental") await ctx.warning("Warning: This is experimental") # pyright: ignore[reportDeprecated]
await ctx.error("Error: (This is just a demo)") await ctx.error("Error: (This is just a demo)") # pyright: ignore[reportDeprecated]
# Notify about resource changes # Notify about resource changes
await ctx.session.send_resource_list_changed() await ctx.session.send_resource_list_changed()

View file

@ -1245,7 +1245,26 @@ Tasks are expected to return as a separate MCP extension in a future release.
## Deprecations ## 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 ## Bug Fixes

View file

@ -143,13 +143,13 @@ def test_multiple_content_types() -> list[TextContent | ImageContent | EmbeddedR
@mcp.tool() @mcp.tool()
async def test_tool_with_logging(ctx: Context) -> str: async def test_tool_with_logging(ctx: Context) -> str:
"""Tests tool that emits log messages during execution""" """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 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 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" return "Tool with logging executed successfully"
@ -176,7 +176,7 @@ async def test_sampling(prompt: str, ctx: Context) -> str:
"""Tests server-initiated sampling (LLM completion request)""" """Tests server-initiated sampling (LLM completion request)"""
try: try:
# Request sampling from client # 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))], messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
max_tokens=100, max_tokens=100,
) )
@ -314,13 +314,13 @@ def test_error_handling() -> str:
@mcp.tool() @mcp.tool()
async def test_reconnection(ctx: Context) -> str: async def test_reconnection(ctx: Context) -> str:
"""Tests SSE polling by closing stream mid-call (SEP-1699)""" """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 ctx.close_sse_stream()
await asyncio.sleep(0.2) # Wait for client to reconnect 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" return "Reconnection test completed"

View file

@ -49,7 +49,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ
# Send the specified number of notifications with the given interval # Send the specified number of notifications with the given interval
for i in range(count): for i in range(count):
await ctx.session.send_log_message( await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="info", level="info",
data=f"Notification {i + 1}/{count} from caller: {caller}", data=f"Notification {i + 1}/{count} from caller: {caller}",
logger="notification_stream", logger="notification_stream",

View file

@ -54,7 +54,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ
for i in range(count): for i in range(count):
# Include more detailed message for resumability demonstration # Include more detailed message for resumability demonstration
notification_msg = f"[{i + 1}/{count}] Event from '{caller}' - Use Last-Event-ID to resume if disconnected" 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", level="info",
data=notification_msg, data=notification_msg,
logger="notification_stream", logger="notification_stream",

View file

@ -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")] 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", level="info",
data=f"Starting batch processing of {items} items...", data=f"Starting batch processing of {items} items...",
logger="process_batch", logger="process_batch",
@ -86,7 +86,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ
await anyio.sleep(0.5) await anyio.sleep(0.5)
# Report progress # Report progress
await ctx.session.send_log_message( await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="info", level="info",
data=f"[{i}/{items}] Processing item {i}", data=f"[{i}/{items}] Processing item {i}",
logger="process_batch", logger="process_batch",
@ -95,7 +95,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequ
# Checkpoint: close stream to trigger client reconnect # Checkpoint: close stream to trigger client reconnect
if i % checkpoint_every == 0 and i < items: 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", level="info",
data=f"Checkpoint at item {i} - closing SSE stream for polling", data=f"Checkpoint at item {i} - closing SSE stream for polling",
logger="process_batch", logger="process_batch",

View file

@ -7,10 +7,10 @@ mcp = MCPServer(name="Notifications Example")
async def process_data(data: str, ctx: Context) -> str: async def process_data(data: str, ctx: Context) -> str:
"""Process data with logging.""" """Process data with logging."""
# Different log levels # Different log levels
await ctx.debug(f"Debug: Processing '{data}'") await ctx.debug(f"Debug: Processing '{data}'") # pyright: ignore[reportDeprecated]
await ctx.info("Info: Starting processing") await ctx.info("Info: Starting processing") # pyright: ignore[reportDeprecated]
await ctx.warning("Warning: This is experimental") await ctx.warning("Warning: This is experimental") # pyright: ignore[reportDeprecated]
await ctx.error("Error: (This is just a demo)") await ctx.error("Error: (This is just a demo)") # pyright: ignore[reportDeprecated]
# Notify about resource changes # Notify about resource changes
await ctx.session.send_resource_list_changed() await ctx.session.send_resource_list_changed()

View file

@ -9,7 +9,7 @@ async def generate_poem(topic: str, ctx: Context) -> str:
"""Generate a poem using LLM sampling.""" """Generate a poem using LLM sampling."""
prompt = f"Write a short poem about {topic}" prompt = f"Write a short poem about {topic}"
result = await ctx.session.create_message( result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[ messages=[
SamplingMessage( SamplingMessage(
role="user", role="user",

View file

@ -6,7 +6,7 @@ mcp = MCPServer(name="Progress Example")
@mcp.tool() @mcp.tool()
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str: async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
"""Execute a task with progress updates.""" """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): for i in range(steps):
progress = (i + 1) / steps progress = (i + 1) / steps
@ -15,6 +15,6 @@ async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str
total=1.0, total=1.0,
message=f"Step {i + 1}/{steps}", 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" return f"Task '{task_name}' completed"

View file

@ -213,6 +213,10 @@ filterwarnings = [
"error", "error",
# pywin32 internal deprecation warning # pywin32 internal deprecation warning
"ignore:getargs.*The 'u' format is deprecated:DeprecationWarning", "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] [tool.markdown.lint]

View file

@ -4,7 +4,7 @@ from .client.session_group import ClientSessionGroup
from .client.stdio import StdioServerParameters, stdio_client from .client.stdio import StdioServerParameters, stdio_client
from .server.session import ServerSession from .server.session import ServerSession
from .server.stdio import stdio_server from .server.stdio import stdio_server
from .shared.exceptions import MCPError, UrlElicitationRequiredError from .shared.exceptions import MCPDeprecationWarning, MCPError, UrlElicitationRequiredError
from .types import ( from .types import (
CallToolRequest, CallToolRequest,
ClientCapabilities, ClientCapabilities,
@ -96,6 +96,7 @@ __all__ = [
"ListToolsResult", "ListToolsResult",
"LoggingLevel", "LoggingLevel",
"LoggingMessageNotification", "LoggingMessageNotification",
"MCPDeprecationWarning",
"MCPError", "MCPError",
"Notification", "Notification",
"PingRequest", "PingRequest",

View file

@ -6,6 +6,8 @@ from contextlib import AsyncExitStack
from dataclasses import KW_ONLY, dataclass, field from dataclasses import KW_ONLY, dataclass, field
from typing import Any from typing import Any
from typing_extensions import deprecated
from mcp.client._memory import InMemoryTransport from mcp.client._memory import InMemoryTransport
from mcp.client._transport import Transport from mcp.client._transport import Transport
from mcp.client.session import ClientSession, ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT from mcp.client.session import ClientSession, ElicitationFnT, ListRootsFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
@ -13,6 +15,7 @@ from mcp.client.streamable_http import streamable_http_client
from mcp.server import Server from mcp.server import Server
from mcp.server.mcpserver import MCPServer from mcp.server.mcpserver import MCPServer
from mcp.shared.dispatcher import ProgressFnT from mcp.shared.dispatcher import ProgressFnT
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.types import ( from mcp.types import (
CallToolResult, CallToolResult,
CompleteResult, CompleteResult,
@ -197,9 +200,10 @@ class Client:
message=message, 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: async def set_logging_level(self, level: LoggingLevel, *, meta: RequestParamsMeta | None = None) -> EmptyResult:
"""Set the logging level on the server.""" """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]
async def list_resources( async def list_resources(
self, self,
@ -314,7 +318,8 @@ class Client:
"""List available tools from the server.""" """List available tools from the server."""
return await self.session.list_tools(params=PaginatedRequestParams(cursor=cursor, _meta=meta)) 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: async def send_roots_list_changed(self) -> None:
"""Send a notification that the roots list has changed.""" """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. # 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]

View file

@ -10,13 +10,13 @@ import anyio
import anyio.abc import anyio.abc
import anyio.lowlevel import anyio.lowlevel
from pydantic import BaseModel, TypeAdapter, ValidationError 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 import types
from mcp.client._transport import ReadStream, WriteStream from mcp.client._transport import ReadStream, WriteStream
from mcp.shared._compat import resync_tracer from mcp.shared._compat import resync_tracer
from mcp.shared.dispatcher import CallOptions, DispatchContext, Dispatcher, ProgressFnT 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.jsonrpc_dispatcher import JSONRPCDispatcher
from mcp.shared.message import ClientMessageMetadata, SessionMessage from mcp.shared.message import ClientMessageMetadata, SessionMessage
from mcp.shared.session import RequestResponder from mcp.shared.session import RequestResponder
@ -386,6 +386,7 @@ class ClientSession:
) )
) )
@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def set_logging_level( async def set_logging_level(
self, self,
level: types.LoggingLevel, level: types.LoggingLevel,
@ -550,6 +551,7 @@ class ClientSession:
return result 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: async def send_roots_list_changed(self) -> None:
"""Send a roots/list_changed notification.""" """Send a roots/list_changed notification."""
await self.send_notification(types.RootsListChangedNotification()) await self.send_notification(types.RootsListChangedNotification())

View file

@ -20,9 +20,10 @@ from typing import Any, TypeVar, overload
import anyio import anyio
from pydantic import BaseModel from pydantic import BaseModel
from typing_extensions import deprecated
from mcp.shared.dispatcher import CallOptions, Outbound 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.shared.peer import Meta, dump_params
from mcp.types import ( from mcp.types import (
ClientCapabilities, ClientCapabilities,
@ -209,6 +210,7 @@ class Connection:
""" """
await self.send_raw_request("ping", dump_params(None, meta), opts) 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: 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.""" """Send a `notifications/message` log entry on the standalone stream. Best-effort."""
params: dict[str, Any] = {"level": level, "data": data} params: dict[str, Any] = {"level": level, "data": data}

View file

@ -3,12 +3,13 @@ from dataclasses import dataclass
from typing import Any, Generic, Protocol from typing import Any, Generic, Protocol
from pydantic import BaseModel from pydantic import BaseModel
from typing_extensions import TypeVar from typing_extensions import TypeVar, deprecated
from mcp.server.connection import Connection from mcp.server.connection import Connection
from mcp.server.session import ServerSession from mcp.server.session import ServerSession
from mcp.shared.context import BaseContext from mcp.shared.context import BaseContext
from mcp.shared.dispatcher import DispatchContext from mcp.shared.dispatcher import DispatchContext
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.shared.message import CloseSSEStreamCallback from mcp.shared.message import CloseSSEStreamCallback
from mcp.shared.peer import Meta from mcp.shared.peer import Meta
from mcp.shared.transport_context import TransportContext from mcp.shared.transport_context import TransportContext
@ -92,6 +93,7 @@ class Context(BaseContext[TransportContext], Generic[LifespanT_co]):
""" """
return self.transport.headers return self.transport.headers
@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: async def log(self, level: LoggingLevel, data: Any, logger: str | None = None, *, meta: Meta | None = None) -> None:
"""Send a request-scoped `notifications/message` log entry. """Send a request-scoped `notifications/message` log entry.

View file

@ -4,6 +4,7 @@ from collections.abc import Iterable
from typing import TYPE_CHECKING, Any, Generic from typing import TYPE_CHECKING, Any, Generic
from pydantic import AnyUrl, BaseModel from pydantic import AnyUrl, BaseModel
from typing_extensions import deprecated
from mcp.server.context import LifespanContextT, RequestT, ServerRequestContext from mcp.server.context import LifespanContextT, RequestT, ServerRequestContext
from mcp.server.elicitation import ( from mcp.server.elicitation import (
@ -14,6 +15,7 @@ from mcp.server.elicitation import (
elicit_with_validation, elicit_with_validation,
) )
from mcp.server.lowlevel.helper_types import ReadResourceContents from mcp.server.lowlevel.helper_types import ReadResourceContents
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.types import LoggingLevel from mcp.types import LoggingLevel
if TYPE_CHECKING: if TYPE_CHECKING:
@ -189,6 +191,7 @@ class Context(BaseModel, Generic[LifespanContextT, RequestT]):
related_request_id=self.request_id, related_request_id=self.request_id,
) )
@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def log( async def log(
self, self,
level: LoggingLevel, level: LoggingLevel,
@ -205,7 +208,7 @@ class Context(BaseModel, Generic[LifespanContextT, RequestT]):
(string, dict, list, number, bool, etc.) per the MCP specification. (string, dict, list, number, bool, etc.) per the MCP specification.
logger_name: Optional logger name logger_name: Optional logger name
""" """
await self.request_context.session.send_log_message( await self.request_context.session.send_log_message( # pyright: ignore[reportDeprecated]
level=level, level=level,
data=data, data=data,
logger=logger_name, logger=logger_name,
@ -265,18 +268,22 @@ class Context(BaseModel, Generic[LifespanContextT, RequestT]):
await self._request_context.close_standalone_sse_stream() await self._request_context.close_standalone_sse_stream()
# Convenience methods for common log levels # Convenience methods for common log levels
@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def debug(self, data: Any, *, logger_name: str | None = None) -> None: async def debug(self, data: Any, *, logger_name: str | None = None) -> None:
"""Send a debug log message.""" """Send a debug log message."""
await self.log("debug", data, logger_name=logger_name) await self.log("debug", data, logger_name=logger_name) # pyright: ignore[reportDeprecated]
@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def info(self, data: Any, *, logger_name: str | None = None) -> None: async def info(self, data: Any, *, logger_name: str | None = None) -> None:
"""Send an info log message.""" """Send an info log message."""
await self.log("info", data, logger_name=logger_name) await self.log("info", data, logger_name=logger_name) # pyright: ignore[reportDeprecated]
@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def warning(self, data: Any, *, logger_name: str | None = None) -> None: async def warning(self, data: Any, *, logger_name: str | None = None) -> None:
"""Send a warning log message.""" """Send a warning log message."""
await self.log("warning", data, logger_name=logger_name) await self.log("warning", data, logger_name=logger_name) # pyright: ignore[reportDeprecated]
@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def error(self, data: Any, *, logger_name: str | None = None) -> None: async def error(self, data: Any, *, logger_name: str | None = None) -> None:
"""Send an error log message.""" """Send an error log message."""
await self.log("error", data, logger_name=logger_name) await self.log("error", data, logger_name=logger_name) # pyright: ignore[reportDeprecated]

View file

@ -12,12 +12,13 @@ used to live here are now owned by `JSONRPCDispatcher` and `ServerRunner`.
from typing import Any, TypeVar, cast, overload from typing import Any, TypeVar, cast, overload
from pydantic import AnyUrl, BaseModel from pydantic import AnyUrl, BaseModel
from typing_extensions import deprecated
from mcp import types from mcp import types
from mcp.server.connection import Connection from mcp.server.connection import Connection
from mcp.server.validation import validate_sampling_tools, validate_tool_use_result_messages from mcp.server.validation import validate_sampling_tools, validate_tool_use_result_messages
from mcp.shared.dispatcher import CallOptions, Dispatcher, ProgressFnT from mcp.shared.dispatcher import CallOptions, Dispatcher, ProgressFnT
from mcp.shared.exceptions import NoBackChannelError, StatelessModeNotSupported from mcp.shared.exceptions import MCPDeprecationWarning, NoBackChannelError, StatelessModeNotSupported
from mcp.shared.message import ServerMessageMetadata from mcp.shared.message import ServerMessageMetadata
from mcp.types import methods as _methods from mcp.types import methods as _methods
@ -126,6 +127,7 @@ class ServerSession:
"""Check if the client supports a specific capability.""" """Check if the client supports a specific capability."""
return self._connection.check_capability(capability) return self._connection.check_capability(capability)
@deprecated("The logging capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def send_log_message( async def send_log_message(
self, self,
level: types.LoggingLevel, level: types.LoggingLevel,
@ -154,6 +156,7 @@ class ServerSession:
) )
@overload @overload
@deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def create_message( async def create_message(
self, self,
messages: list[types.SamplingMessage], messages: list[types.SamplingMessage],
@ -173,6 +176,7 @@ class ServerSession:
... ...
@overload @overload
@deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def create_message( async def create_message(
self, self,
messages: list[types.SamplingMessage], messages: list[types.SamplingMessage],
@ -191,6 +195,7 @@ class ServerSession:
"""Overload: With tools, returns array-capable content.""" """Overload: With tools, returns array-capable content."""
... ...
@deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def create_message( async def create_message(
self, self,
messages: list[types.SamplingMessage], messages: list[types.SamplingMessage],
@ -267,6 +272,7 @@ class ServerSession:
metadata=metadata_obj, metadata=metadata_obj,
) )
@deprecated("The roots capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def list_roots(self) -> types.ListRootsResult: async def list_roots(self) -> types.ListRootsResult:
"""Send a roots/list request.""" """Send a roots/list request."""
if self._stateless: if self._stateless:

View file

@ -5,6 +5,17 @@ from typing import Any, cast
from mcp.types import INVALID_REQUEST, URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData, JSONRPCError from mcp.types import INVALID_REQUEST, URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData, JSONRPCError
class MCPDeprecationWarning(UserWarning):
"""A custom deprecation warning for the MCP SDK.
Unlike the built-in `DeprecationWarning`, this inherits from `UserWarning` so
it is shown by default, helping users discover deprecated features without
enabling warnings explicitly.
Reference: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries
"""
class MCPError(Exception): class MCPError(Exception):
"""Exception type raised when an error arrives over an MCP connection.""" """Exception type raised when an error arrives over an MCP connection."""

View file

@ -13,8 +13,10 @@ from collections.abc import Mapping
from typing import Any, cast, overload from typing import Any, cast, overload
from pydantic import BaseModel from pydantic import BaseModel
from typing_extensions import deprecated
from mcp.shared.dispatcher import CallOptions, Outbound from mcp.shared.dispatcher import CallOptions, Outbound
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.types import ( from mcp.types import (
CreateMessageRequestParams, CreateMessageRequestParams,
CreateMessageResult, CreateMessageResult,
@ -83,6 +85,7 @@ class ClientPeer:
await self._outbound.notify(method, params) await self._outbound.notify(method, params)
@overload @overload
@deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def sample( async def sample(
self, self,
messages: list[SamplingMessage], messages: list[SamplingMessage],
@ -100,6 +103,7 @@ class ClientPeer:
opts: CallOptions | None = None, opts: CallOptions | None = None,
) -> CreateMessageResult: ... ) -> CreateMessageResult: ...
@overload @overload
@deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def sample( async def sample(
self, self,
messages: list[SamplingMessage], messages: list[SamplingMessage],
@ -116,6 +120,7 @@ class ClientPeer:
meta: Meta | None = None, meta: Meta | None = None,
opts: CallOptions | None = None, opts: CallOptions | None = None,
) -> CreateMessageResultWithTools: ... ) -> CreateMessageResultWithTools: ...
@deprecated("The sampling capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def sample( async def sample(
self, self,
messages: list[SamplingMessage], messages: list[SamplingMessage],
@ -195,6 +200,7 @@ class ClientPeer:
result = await self.send_raw_request("elicitation/create", dump_params(params, meta), opts) result = await self.send_raw_request("elicitation/create", dump_params(params, meta), opts)
return ElicitResult.model_validate(result, by_name=False) return ElicitResult.model_validate(result, by_name=False)
@deprecated("The roots capability is deprecated as of 2026-07-28 (SEP-2577).", category=MCPDeprecationWarning)
async def list_roots(self, *, meta: Meta | None = None, opts: CallOptions | None = None) -> ListRootsResult: async def list_roots(self, *, meta: Meta | None = None, opts: CallOptions | None = None) -> ListRootsResult:
"""Send a `roots/list` request. """Send a `roots/list` request.

View file

@ -259,7 +259,7 @@ async def test_client_unsubscribe_resource(simple_server: Server):
async def test_client_set_logging_level(simple_server: Server): async def test_client_set_logging_level(simple_server: Server):
"""Test setting logging level.""" """Test setting logging level."""
async with Client(simple_server) as client: async with Client(simple_server) as client:
result = await client.set_logging_level("debug") result = await client.set_logging_level("debug") # pyright: ignore[reportDeprecated]
assert result == snapshot(EmptyResult()) assert result == snapshot(EmptyResult())

Some files were not shown because too many files have changed in this diff Show more