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
parent 08b62308d4
commit ecdf09d44f
12 changed files with 221 additions and 16 deletions
+2
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()`
- 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:
```python
@@ -57,7 +57,7 @@ def build_server() -> Server[Any]:
"""Registered so the server advertises the `logging` capability; never called."""
raise NotImplementedError
return Server(
return Server( # pyright: ignore[reportDeprecated]
"streaming-example",
on_list_tools=list_tools,
on_call_tool=call_tool,
+198 -3
View File
@@ -37,11 +37,12 @@ handler callables by method string.
from __future__ import annotations
import logging
import warnings
from collections.abc import AsyncIterator, Awaitable, Callable
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from dataclasses import dataclass
from importlib.metadata import version as importlib_version
from typing import Any, Generic
from typing import Any, Generic, overload
import mcp_types as types
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.authentication import AuthenticationMiddleware
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.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.transport_security import TransportSecuritySettings
from mcp.shared._stream_protocols import ReadStream, WriteStream
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.shared.message import SessionMessage
logger = logging.getLogger(__name__)
@@ -127,6 +129,89 @@ def _package_version(package: str) -> str:
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__(
self,
name: str,
@@ -217,7 +302,117 @@ class Server(Generic[LifespanResultT]):
Awaitable[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.version = version
self.title = title
+2 -2
View File
@@ -75,7 +75,7 @@ def simple_server() -> Server:
async def handle_completion(ctx: ServerRequestContext, params: types.CompleteRequestParams) -> types.CompleteResult:
return types.CompleteResult(completion=types.Completion(values=[]))
return Server(
return Server( # pyright: ignore[reportDeprecated]
name="test_server",
on_list_resources=handle_list_resources,
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}
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):
async with Client(server, mode="legacy") as client:
+1 -1
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."""
raise NotImplementedError
server = Server(
server = Server( # pyright: ignore[reportDeprecated]
"gatekeeper",
on_list_tools=_list_tools("read_files"),
on_call_tool=call_tool,
@@ -127,7 +127,7 @@ async def test_initialize_capabilities_reflect_registered_handlers(connect: Conn
"""Registered only so the completions capability is advertised; never called."""
raise NotImplementedError
server = Server(
server = Server( # pyright: ignore[reportDeprecated]
"full",
on_list_tools=list_tools,
on_list_resources=list_resources,
+7 -3
View File
@@ -36,7 +36,7 @@ async def test_set_logging_level_reaches_handler(connect: Connect) -> None:
assert params.level == "warning"
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:
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."""
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:
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."""
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:
await client.call_tool("siren", {})
+1 -1
View File
@@ -118,7 +118,7 @@ async def test_client_progress_notification_reaches_server_handler(connect: Conn
received.append(params)
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:
await client.send_progress_notification("upload-1", 0.5, total=1.0, message="halfway") # pyright: ignore[reportDeprecated]
+1 -1
View File
@@ -153,7 +153,7 @@ async def test_roots_list_changed_reaches_server_handler(connect: Connect) -> No
received.append(params)
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:
"""Registered so the client declares the roots capability; the server never asks for roots."""
+1 -1
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."""
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] = []
async with create_client_server_memory_streams() as (client_streams, server_streams):
@@ -53,7 +53,11 @@ async def set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestPa
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:
@@ -73,7 +73,7 @@ def _server() -> Server:
"""Registered so the resources subscribe sub-capability is advertised; the client never subscribes."""
raise NotImplementedError
return Server(
return Server( # pyright: ignore[reportDeprecated]
"hosted",
on_list_tools=list_tools,
on_call_tool=call_tool,