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
2 changes: 2 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,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
Expand Down
2 changes: 1 addition & 1 deletion examples/stories/streaming/server_lowlevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async def set_logging_level(
"""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,
Expand Down
201 changes: 198 additions & 3 deletions src/mcp/server/lowlevel/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ async def main():
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
Expand All @@ -50,7 +51,7 @@ async def main():
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
Expand All @@ -65,6 +66,7 @@ async def main():
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__)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -217,7 +302,117 @@ def __init__(
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
Expand Down
4 changes: 2 additions & 2 deletions tests/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ async def handle_set_logging_level(ctx: ServerRequestContext, params: types.SetL
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,
Expand Down Expand Up @@ -277,7 +277,7 @@ async def handle_progress(ctx: ServerRequestContext, params: types.ProgressNotif
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:
Expand Down
2 changes: 1 addition & 1 deletion tests/interaction/lowlevel/test_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelReq
"""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,
Expand Down
2 changes: 1 addition & 1 deletion tests/interaction/lowlevel/test_initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ async def completion(ctx: ServerRequestContext, params: types.CompleteRequestPar
"""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,
Expand Down
10 changes: 7 additions & 3 deletions tests/interaction/lowlevel/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelReq
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]
Expand Down Expand Up @@ -76,7 +76,9 @@ async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelReq
"""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", {})
Expand Down Expand Up @@ -115,7 +117,9 @@ async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelReq
"""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", {})
Expand Down
2 changes: 1 addition & 1 deletion tests/interaction/lowlevel/test_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ async def on_progress(ctx: ServerRequestContext, params: ProgressNotificationPar
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]
Expand Down
2 changes: 1 addition & 1 deletion tests/interaction/lowlevel/test_roots.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ async def roots_list_changed(ctx: ServerRequestContext, params: types.Notificati
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."""
Expand Down
2 changes: 1 addition & 1 deletion tests/interaction/lowlevel/test_wire.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelReq
"""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):
Expand Down
6 changes: 5 additions & 1 deletion tests/interaction/transports/_stdio_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion tests/interaction/transports/test_hosting_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ async def subscribe_resource(ctx: ServerRequestContext, params: SubscribeRequest
"""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,
Expand Down
Loading