Deprecate Server.__init__ handlers for removed capabilities (#3002)

This commit is contained in:
Marcelo Trylesinski 2026-06-26 17:51:13 +02:00 • committed by GitHub
commit ecdf09d44f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 221 additions and 16 deletions

View file

@ -1345,6 +1345,8 @@ The user-facing methods for these features now carry `typing_extensions.deprecat
- Roots: `ServerSession.list_roots()`, `ClientPeer.list_roots()`, `ClientSession.send_roots_list_changed()`, `Client.send_roots_list_changed()` - 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()` - 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()`
Registering a handler for a deprecated capability is deprecated too. The `Server.__init__` parameters `on_set_logging_level` (Logging) and `on_roots_list_changed` (Roots) are now split out into a `typing_extensions.deprecated` overload, so passing either is flagged by type checkers and emits `mcp.MCPDeprecationWarning` at construction time. `on_progress` follows the same pattern (see below). The non-deprecated overload omits these parameters, so the common case stays warning-free.
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: 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 ```python

View file

@ -57,7 +57,7 @@ def build_server() -> Server[Any]:
"""Registered so the server advertises the `logging` capability; never called.""" """Registered so the server advertises the `logging` capability; never called."""
raise NotImplementedError raise NotImplementedError
return Server( return Server( # pyright: ignore[reportDeprecated]
"streaming-example", "streaming-example",
on_list_tools=list_tools, on_list_tools=list_tools,
on_call_tool=call_tool, on_call_tool=call_tool,

View file

@ -37,11 +37,12 @@ handler callables by method string.
from __future__ import annotations from __future__ import annotations
import logging import logging
import warnings
from collections.abc import AsyncIterator, Awaitable, Callable from collections.abc import AsyncIterator, Awaitable, Callable
from contextlib import AbstractAsyncContextManager, asynccontextmanager from contextlib import AbstractAsyncContextManager, asynccontextmanager
from dataclasses import dataclass from dataclasses import dataclass
from importlib.metadata import version as importlib_version from importlib.metadata import version as importlib_version
from typing import Any, Generic from typing import Any, Generic, overload
import mcp_types as types import mcp_types as types
from mcp_types.version import MODERN_PROTOCOL_VERSIONS from mcp_types.version import MODERN_PROTOCOL_VERSIONS
@ -50,7 +51,7 @@ from starlette.applications import Starlette
from starlette.middleware import Middleware from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.routing import Mount, Route from starlette.routing import Mount, Route
from typing_extensions import TypeVar from typing_extensions import TypeVar, deprecated
from mcp.server._otel import OpenTelemetryMiddleware from mcp.server._otel import OpenTelemetryMiddleware
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
@ -65,6 +66,7 @@ from mcp.server.streamable_http import EventStore
from mcp.server.streamable_http_manager import StreamableHTTPASGIApp, StreamableHTTPSessionManager from mcp.server.streamable_http_manager import StreamableHTTPASGIApp, StreamableHTTPSessionManager
from mcp.server.transport_security import TransportSecuritySettings from mcp.server.transport_security import TransportSecuritySettings
from mcp.shared._stream_protocols import ReadStream, WriteStream from mcp.shared._stream_protocols import ReadStream, WriteStream
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.shared.message import SessionMessage from mcp.shared.message import SessionMessage
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -127,6 +129,89 @@ def _package_version(package: str) -> str:
class Server(Generic[LifespanResultT]): class Server(Generic[LifespanResultT]):
@overload
def __init__(
self,
name: str,
*,
version: str | None = None,
title: str | None = None,
description: str | None = None,
instructions: str | None = None,
website_url: str | None = None,
icons: list[types.Icon] | None = None,
lifespan: Callable[
[Server[LifespanResultT]],
AbstractAsyncContextManager[LifespanResultT],
] = lifespan,
# Request handlers
on_list_tools: Callable[
[ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
Awaitable[types.ListToolsResult],
]
| None = None,
on_call_tool: Callable[
[ServerRequestContext[LifespanResultT], types.CallToolRequestParams],
Awaitable[types.CallToolResult | types.InputRequiredResult],
]
| None = None,
on_list_resources: Callable[
[ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
Awaitable[types.ListResourcesResult],
]
| None = None,
on_list_resource_templates: Callable[
[ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
Awaitable[types.ListResourceTemplatesResult],
]
| None = None,
on_read_resource: Callable[
[ServerRequestContext[LifespanResultT], types.ReadResourceRequestParams],
Awaitable[types.ReadResourceResult | types.InputRequiredResult],
]
| None = None,
on_subscribe_resource: Callable[
[ServerRequestContext[LifespanResultT], types.SubscribeRequestParams],
Awaitable[types.EmptyResult],
]
| None = None,
on_unsubscribe_resource: Callable[
[ServerRequestContext[LifespanResultT], types.UnsubscribeRequestParams],
Awaitable[types.EmptyResult],
]
| None = None,
on_subscriptions_listen: Callable[
[ServerRequestContext[LifespanResultT], types.SubscriptionsListenRequestParams],
Awaitable[types.EmptyResult],
]
| None = None,
on_list_prompts: Callable[
[ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
Awaitable[types.ListPromptsResult],
]
| None = None,
on_get_prompt: Callable[
[ServerRequestContext[LifespanResultT], types.GetPromptRequestParams],
Awaitable[types.GetPromptResult | types.InputRequiredResult],
]
| None = None,
on_completion: Callable[
[ServerRequestContext[LifespanResultT], types.CompleteRequestParams],
Awaitable[types.CompleteResult],
]
| None = None,
on_ping: Callable[
[ServerRequestContext[LifespanResultT], types.RequestParams | None],
Awaitable[types.EmptyResult],
] = _ping_handler,
) -> None: ...
@overload
@deprecated(
"on_set_logging_level (Logging) and on_roots_list_changed (Roots) are deprecated as of 2026-07-28 "
"(SEP-2577); on_progress (client-to-server progress) is deprecated as of 2026-07-28. Passing any of "
"them emits an MCPDeprecationWarning at runtime.",
category=MCPDeprecationWarning,
)
def __init__( def __init__(
self, self,
name: str, name: str,
@ -217,7 +302,117 @@ class Server(Generic[LifespanResultT]):
Awaitable[None], Awaitable[None],
] ]
| None = None, | None = None,
): ) -> None: ...
def __init__(
self,
name: str,
*,
version: str | None = None,
title: str | None = None,
description: str | None = None,
instructions: str | None = None,
website_url: str | None = None,
icons: list[types.Icon] | None = None,
lifespan: Callable[
[Server[LifespanResultT]],
AbstractAsyncContextManager[LifespanResultT],
] = lifespan,
# Request handlers
on_list_tools: Callable[
[ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
Awaitable[types.ListToolsResult],
]
| None = None,
on_call_tool: Callable[
[ServerRequestContext[LifespanResultT], types.CallToolRequestParams],
Awaitable[types.CallToolResult | types.InputRequiredResult],
]
| None = None,
on_list_resources: Callable[
[ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
Awaitable[types.ListResourcesResult],
]
| None = None,
on_list_resource_templates: Callable[
[ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
Awaitable[types.ListResourceTemplatesResult],
]
| None = None,
on_read_resource: Callable[
[ServerRequestContext[LifespanResultT], types.ReadResourceRequestParams],
Awaitable[types.ReadResourceResult | types.InputRequiredResult],
]
| None = None,
on_subscribe_resource: Callable[
[ServerRequestContext[LifespanResultT], types.SubscribeRequestParams],
Awaitable[types.EmptyResult],
]
| None = None,
on_unsubscribe_resource: Callable[
[ServerRequestContext[LifespanResultT], types.UnsubscribeRequestParams],
Awaitable[types.EmptyResult],
]
| None = None,
on_subscriptions_listen: Callable[
[ServerRequestContext[LifespanResultT], types.SubscriptionsListenRequestParams],
Awaitable[types.EmptyResult],
]
| None = None,
on_list_prompts: Callable[
[ServerRequestContext[LifespanResultT], types.PaginatedRequestParams | None],
Awaitable[types.ListPromptsResult],
]
| None = None,
on_get_prompt: Callable[
[ServerRequestContext[LifespanResultT], types.GetPromptRequestParams],
Awaitable[types.GetPromptResult | types.InputRequiredResult],
]
| None = None,
on_completion: Callable[
[ServerRequestContext[LifespanResultT], types.CompleteRequestParams],
Awaitable[types.CompleteResult],
]
| None = None,
on_set_logging_level: Callable[
[ServerRequestContext[LifespanResultT], types.SetLevelRequestParams],
Awaitable[types.EmptyResult],
]
| None = None,
on_ping: Callable[
[ServerRequestContext[LifespanResultT], types.RequestParams | None],
Awaitable[types.EmptyResult],
] = _ping_handler,
# Notification handlers
on_roots_list_changed: Callable[
[ServerRequestContext[LifespanResultT], types.NotificationParams | None],
Awaitable[None],
]
| None = None,
on_progress: Callable[
[ServerRequestContext[LifespanResultT], types.ProgressNotificationParams],
Awaitable[None],
]
| None = None,
) -> None:
if on_set_logging_level is not None:
warnings.warn(
"The logging capability is deprecated as of 2026-07-28 (SEP-2577).",
MCPDeprecationWarning,
stacklevel=2,
)
if on_roots_list_changed is not None:
warnings.warn(
"The roots capability is deprecated as of 2026-07-28 (SEP-2577).",
MCPDeprecationWarning,
stacklevel=2,
)
if on_progress is not None:
warnings.warn(
"Client-to-server progress is deprecated as of 2026-07-28.",
MCPDeprecationWarning,
stacklevel=2,
)
self.name = name self.name = name
self.version = version self.version = version
self.title = title self.title = title

View file

@ -75,7 +75,7 @@ def simple_server() -> Server:
async def handle_completion(ctx: ServerRequestContext, params: types.CompleteRequestParams) -> types.CompleteResult: async def handle_completion(ctx: ServerRequestContext, params: types.CompleteRequestParams) -> types.CompleteResult:
return types.CompleteResult(completion=types.Completion(values=[])) return types.CompleteResult(completion=types.Completion(values=[]))
return Server( return Server( # pyright: ignore[reportDeprecated]
name="test_server", name="test_server",
on_list_resources=handle_list_resources, on_list_resources=handle_list_resources,
on_subscribe_resource=handle_subscribe_resource, on_subscribe_resource=handle_subscribe_resource,
@ -279,7 +279,7 @@ async def test_client_send_progress_notification():
received_from_client = {"progress_token": params.progress_token, "progress": params.progress} received_from_client = {"progress_token": params.progress_token, "progress": params.progress}
event.set() event.set()
server = Server(name="test_server", on_progress=handle_progress) server = Server(name="test_server", on_progress=handle_progress) # pyright: ignore[reportDeprecated]
with anyio.fail_after(5): with anyio.fail_after(5):
async with Client(server, mode="legacy") as client: async with Client(server, mode="legacy") as client:

View file

@ -173,7 +173,7 @@ async def test_a_tool_rejected_with_url_elicitation_required_succeeds_on_retry_a
"""Registered so the logging capability is advertised; the client never sets a level.""" """Registered so the logging capability is advertised; the client never sets a level."""
raise NotImplementedError raise NotImplementedError
server = Server( server = Server( # pyright: ignore[reportDeprecated]
"gatekeeper", "gatekeeper",
on_list_tools=_list_tools("read_files"), on_list_tools=_list_tools("read_files"),
on_call_tool=call_tool, on_call_tool=call_tool,

View file

@ -127,7 +127,7 @@ async def test_initialize_capabilities_reflect_registered_handlers(connect: Conn
"""Registered only so the completions capability is advertised; never called.""" """Registered only so the completions capability is advertised; never called."""
raise NotImplementedError raise NotImplementedError
server = Server( server = Server( # pyright: ignore[reportDeprecated]
"full", "full",
on_list_tools=list_tools, on_list_tools=list_tools,
on_list_resources=list_resources, on_list_resources=list_resources,

View file

@ -36,7 +36,7 @@ async def test_set_logging_level_reaches_handler(connect: Connect) -> None:
assert params.level == "warning" assert params.level == "warning"
return EmptyResult() return EmptyResult()
server = Server("logger", on_set_logging_level=set_logging_level) server = Server("logger", on_set_logging_level=set_logging_level) # pyright: ignore[reportDeprecated]
async with connect(server) as client: async with connect(server) as client:
result = await client.set_logging_level("warning") # pyright: ignore[reportDeprecated] result = await client.set_logging_level("warning") # pyright: ignore[reportDeprecated]
@ -76,7 +76,9 @@ async def test_log_messages_reach_logging_callback_in_order(connect: Connect) ->
"""Registered so the logging capability is advertised; the client never sets a level.""" """Registered so the logging capability is advertised; the client never sets a level."""
raise NotImplementedError raise NotImplementedError
server = Server("logger", on_list_tools=list_tools, on_call_tool=call_tool, on_set_logging_level=set_logging_level) server = Server( # pyright: ignore[reportDeprecated]
"logger", on_list_tools=list_tools, on_call_tool=call_tool, on_set_logging_level=set_logging_level
)
async with connect(server, logging_callback=collect) as client: async with connect(server, logging_callback=collect) as client:
result = await client.call_tool("chatty", {}) result = await client.call_tool("chatty", {})
@ -115,7 +117,9 @@ async def test_log_messages_at_every_severity_level(connect: Connect) -> None:
"""Registered so the logging capability is advertised; the client never sets a level.""" """Registered so the logging capability is advertised; the client never sets a level."""
raise NotImplementedError raise NotImplementedError
server = Server("logger", on_list_tools=list_tools, on_call_tool=call_tool, on_set_logging_level=set_logging_level) server = Server( # pyright: ignore[reportDeprecated]
"logger", on_list_tools=list_tools, on_call_tool=call_tool, on_set_logging_level=set_logging_level
)
async with connect(server, logging_callback=collect) as client: async with connect(server, logging_callback=collect) as client:
await client.call_tool("siren", {}) await client.call_tool("siren", {})

View file

@ -118,7 +118,7 @@ async def test_client_progress_notification_reaches_server_handler(connect: Conn
received.append(params) received.append(params)
delivered.set() delivered.set()
server = Server("observer", on_progress=on_progress) server = Server("observer", on_progress=on_progress) # pyright: ignore[reportDeprecated]
async with connect(server) as client: async with connect(server) as client:
await client.send_progress_notification("upload-1", 0.5, total=1.0, message="halfway") # pyright: ignore[reportDeprecated] await client.send_progress_notification("upload-1", 0.5, total=1.0, message="halfway") # pyright: ignore[reportDeprecated]

View file

@ -153,7 +153,7 @@ async def test_roots_list_changed_reaches_server_handler(connect: Connect) -> No
received.append(params) received.append(params)
delivered.set() delivered.set()
server = Server("rooted", on_roots_list_changed=roots_list_changed) server = Server("rooted", on_roots_list_changed=roots_list_changed) # pyright: ignore[reportDeprecated]
async def list_roots(context: ClientRequestContext) -> ListRootsResult: async def list_roots(context: ClientRequestContext) -> ListRootsResult:
"""Registered so the client declares the roots capability; the server never asks for roots.""" """Registered so the client declares the roots capability; the server never asks for roots."""

View file

@ -262,7 +262,7 @@ async def test_set_level_with_an_unrecognized_value_is_answered_with_invalid_par
"""Registered so the logging capability is advertised; never called -- params validation fails first.""" """Registered so the logging capability is advertised; never called -- params validation fails first."""
raise NotImplementedError raise NotImplementedError
server = Server("logger", on_set_logging_level=set_logging_level) server = Server("logger", on_set_logging_level=set_logging_level) # pyright: ignore[reportDeprecated]
errors: list[ErrorData] = [] errors: list[ErrorData] = []
async with create_client_server_memory_streams() as (client_streams, server_streams): async with create_client_server_memory_streams() as (client_streams, server_streams):

View file

@ -53,7 +53,11 @@ async def set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestPa
raise NotImplementedError raise NotImplementedError
server = Server("stdio-echo", on_list_tools=list_tools, on_call_tool=call_tool, on_set_logging_level=set_logging_level) with warnings.catch_warnings():
warnings.simplefilter("ignore", MCPDeprecationWarning)
server = Server( # pyright: ignore[reportDeprecated]
"stdio-echo", on_list_tools=list_tools, on_call_tool=call_tool, on_set_logging_level=set_logging_level
)
async def main() -> None: async def main() -> None:

View file

@ -73,7 +73,7 @@ def _server() -> Server:
"""Registered so the resources subscribe sub-capability is advertised; the client never subscribes.""" """Registered so the resources subscribe sub-capability is advertised; the client never subscribes."""
raise NotImplementedError raise NotImplementedError
return Server( return Server( # pyright: ignore[reportDeprecated]
"hosted", "hosted",
on_list_tools=list_tools, on_list_tools=list_tools,
on_call_tool=call_tool, on_call_tool=call_tool,