Skip to content

Commit 08afbf5

Browse files
committed
Deprecate roots, sampling, and logging per SEP-2577
SEP-2577 deprecates the Roots, Sampling, and Logging features as of the 2026-07-28 spec. The deprecation is advisory only: no wire-level changes, capability negotiation is unchanged, and every type stays functional for sessions negotiating 2025-11-25 and earlier. Mark the 4 deprecated capability fields with `Annotated[T, deprecated(...)]` (warns on access, sets the JSON-schema `deprecated` flag) and the 17 deprecated type classes with a class-level `@deprecated`. The advisory runtime warning is silenced via a scoped `filterwarnings` entry since the SDK still builds these types internally to serve older sessions; the SDK's own references carry `# pyright: ignore[reportDeprecated]`. Drop the sampling-specific example snippet and its README section rather than decorate user-facing docs with deprecation suppressions; trim the incidental sampling callback from the general stdio client example.
1 parent 0fa03ec commit 08afbf5

51 files changed

Lines changed: 565 additions & 572 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.v2.md

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
- [Context Properties and Methods](#context-properties-and-methods)
4646
- [Completions](#completions)
4747
- [Elicitation](#elicitation)
48-
- [Sampling](#sampling)
4948
- [Logging and Notifications](#logging-and-notifications)
5049
- [Authentication](#authentication)
5150
- [MCPServer Properties](#mcpserver-properties)
@@ -931,42 +930,6 @@ The `elicit()` method returns an `ElicitationResult` with:
931930

932931
If the client returns data that doesn't match the schema, `elicit()` raises a `pydantic.ValidationError`.
933932

934-
### Sampling
935-
936-
Tools can interact with LLMs through sampling (generating text):
937-
938-
<!-- snippet-source examples/snippets/servers/sampling.py -->
939-
```python
940-
from mcp.server.mcpserver import Context, MCPServer
941-
from mcp.types import SamplingMessage, TextContent
942-
943-
mcp = MCPServer(name="Sampling Example")
944-
945-
946-
@mcp.tool()
947-
async def generate_poem(topic: str, ctx: Context) -> str:
948-
"""Generate a poem using LLM sampling."""
949-
prompt = f"Write a short poem about {topic}"
950-
951-
result = await ctx.session.create_message(
952-
messages=[
953-
SamplingMessage(
954-
role="user",
955-
content=TextContent(type="text", text=prompt),
956-
)
957-
],
958-
max_tokens=100,
959-
)
960-
961-
# Since we're not passing tools param, result.content is single content
962-
if result.content.type == "text":
963-
return result.content.text
964-
return str(result.content)
965-
```
966-
967-
_Full example: [examples/snippets/servers/sampling.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/sampling.py)_
968-
<!-- /snippet-source -->
969-
970933
### Logging and Notifications
971934

972935
Tools can send logs and notifications through the context:
@@ -2108,7 +2071,6 @@ import asyncio
21082071
import os
21092072

21102073
from mcp import ClientSession, StdioServerParameters, types
2111-
from mcp.client.context import ClientRequestContext
21122074
from mcp.client.stdio import stdio_client
21132075

21142076
# Create server parameters for stdio connection
@@ -2119,25 +2081,9 @@ server_params = StdioServerParameters(
21192081
)
21202082

21212083

2122-
# Optional: create a sampling callback
2123-
async def handle_sampling_message(
2124-
context: ClientRequestContext, params: types.CreateMessageRequestParams
2125-
) -> types.CreateMessageResult:
2126-
print(f"Sampling request: {params.messages}")
2127-
return types.CreateMessageResult(
2128-
role="assistant",
2129-
content=types.TextContent(
2130-
type="text",
2131-
text="Hello, world! from model",
2132-
),
2133-
model="gpt-3.5-turbo",
2134-
stop_reason="endTurn",
2135-
)
2136-
2137-
21382084
async def run():
21392085
async with stdio_client(server_params) as (read, write):
2140-
async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session:
2086+
async with ClientSession(read, write) as session:
21412087
# Initialize the connection
21422088
await session.initialize()
21432089

docs/migration.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1199,7 +1199,13 @@ Tasks are expected to return as a separate MCP extension in a future release.
11991199

12001200
## Deprecations
12011201

1202-
<!-- Add deprecations below -->
1202+
### Roots, Sampling, and Logging deprecated (SEP-2577)
1203+
1204+
[SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) deprecates the Roots, Sampling, and Logging features as of the 2026-07-28 spec. This is advisory only: there are no wire-level changes, capability negotiation is unchanged, and every type remains fully functional for sessions negotiating 2025-11-25 and earlier.
1205+
1206+
The deprecated capability fields (`ClientCapabilities.roots`, `ClientCapabilities.sampling`, `ClientCapabilities.tasks.requests.sampling`, `ServerCapabilities.logging`) and the associated types (`Root`, `ListRootsRequest`, `ListRootsResult`, `RootsListChangedNotification`, `CreateMessageRequest`/`Params`/`Result`, `SamplingMessage`, `ToolChoice`, `ToolUseContent`, `ToolResultContent`, `ModelPreferences`, `ModelHint`, `SetLevelRequest`/`Params`, `LoggingMessageNotification`/`Params`) are marked with `typing_extensions.deprecated`. Type checkers and IDEs surface a deprecation warning where downstream code uses them; at runtime, accessing a deprecated capability field or constructing a deprecated type emits a `DeprecationWarning`.
1207+
1208+
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.
12031209

12041210
## Bug Fixes
12051211

examples/servers/everything-server/mcp_everything_server/server.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
JSONRPCMessage,
2626
PromptReference,
2727
ResourceTemplateReference,
28-
SamplingMessage,
29-
SetLevelRequestParams,
28+
SamplingMessage, # pyright: ignore[reportDeprecated]
29+
SetLevelRequestParams, # pyright: ignore[reportDeprecated]
3030
SubscribeRequestParams,
3131
TextContent,
3232
TextResourceContents,
@@ -177,7 +177,7 @@ async def test_sampling(prompt: str, ctx: Context) -> str:
177177
try:
178178
# Request sampling from client
179179
result = await ctx.session.create_message(
180-
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
180+
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))], # pyright: ignore[reportDeprecated]
181181
max_tokens=100,
182182
)
183183

@@ -397,7 +397,7 @@ def test_prompt_with_image() -> list[UserMessage]:
397397
# Custom request handlers
398398
# TODO(felix): Add public APIs to MCPServer for subscribe_resource, unsubscribe_resource,
399399
# and set_logging_level to avoid accessing protected _lowlevel_server attribute.
400-
async def handle_set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult:
400+
async def handle_set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated]
401401
"""Handle logging level changes"""
402402
logger.info(f"Log level set to: {params.level}")
403403
return EmptyResult()
@@ -418,7 +418,9 @@ async def handle_unsubscribe(ctx: ServerRequestContext, params: UnsubscribeReque
418418

419419

420420
mcp._lowlevel_server.add_request_handler( # pyright: ignore[reportPrivateUsage]
421-
"logging/setLevel", SetLevelRequestParams, handle_set_logging_level
421+
"logging/setLevel",
422+
SetLevelRequestParams, # pyright: ignore[reportDeprecated]
423+
handle_set_logging_level,
422424
)
423425
mcp._lowlevel_server.add_request_handler( # pyright: ignore[reportPrivateUsage]
424426
"resources/subscribe", SubscribeRequestParams, handle_subscribe

examples/snippets/clients/stdio_client.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import os
77

88
from mcp import ClientSession, StdioServerParameters, types
9-
from mcp.client.context import ClientRequestContext
109
from mcp.client.stdio import stdio_client
1110

1211
# Create server parameters for stdio connection
@@ -17,25 +16,9 @@
1716
)
1817

1918

20-
# Optional: create a sampling callback
21-
async def handle_sampling_message(
22-
context: ClientRequestContext, params: types.CreateMessageRequestParams
23-
) -> types.CreateMessageResult:
24-
print(f"Sampling request: {params.messages}")
25-
return types.CreateMessageResult(
26-
role="assistant",
27-
content=types.TextContent(
28-
type="text",
29-
text="Hello, world! from model",
30-
),
31-
model="gpt-3.5-turbo",
32-
stop_reason="endTurn",
33-
)
34-
35-
3619
async def run():
3720
async with stdio_client(server_params) as (read, write):
38-
async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session:
21+
async with ClientSession(read, write) as session:
3922
# Initialize the connection
4023
await session.initialize()
4124

examples/snippets/servers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def run_server():
2121
if len(sys.argv) < 2:
2222
print("Usage: server <server-name> [transport]")
2323
print("Available servers: basic_tool, basic_resource, basic_prompt, tool_progress,")
24-
print(" sampling, elicitation, completion, notifications,")
24+
print(" elicitation, completion, notifications,")
2525
print(" mcpserver_quickstart, structured_output, images")
2626
print("Available transports: stdio (default), sse, streamable-http")
2727
sys.exit(1)

examples/snippets/servers/sampling.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ filterwarnings = [
213213
"error",
214214
# pywin32 internal deprecation warning
215215
"ignore:getargs.*The 'u' format is deprecated:DeprecationWarning",
216+
# SEP-2577 deprecates roots/sampling/logging types; the SDK still builds them
217+
# internally to serve <= 2025-11-25 sessions, so the @deprecated runtime warning
218+
# is advisory only. Tests asserting it opt back in locally with pytest.warns.
219+
"ignore:`.*` is deprecated as of 2026-07-28 \\(SEP-2577\\).:DeprecationWarning",
216220
]
217221

218222
[tool.markdown.lint]

src/mcp/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
ClientRequest,
1313
ClientResult,
1414
CompleteRequest,
15-
CreateMessageRequest,
16-
CreateMessageResult,
15+
CreateMessageRequest, # pyright: ignore[reportDeprecated]
16+
CreateMessageResult, # pyright: ignore[reportDeprecated]
1717
CreateMessageResultWithTools,
1818
ErrorData,
1919
GetPromptRequest,
@@ -32,7 +32,7 @@
3232
ListResourcesResult,
3333
ListToolsResult,
3434
LoggingLevel,
35-
LoggingMessageNotification,
35+
LoggingMessageNotification, # pyright: ignore[reportDeprecated]
3636
Notification,
3737
PingRequest,
3838
ProgressNotification,
@@ -46,21 +46,21 @@
4646
SamplingCapability,
4747
SamplingContent,
4848
SamplingContextCapability,
49-
SamplingMessage,
49+
SamplingMessage, # pyright: ignore[reportDeprecated]
5050
SamplingMessageContentBlock,
5151
SamplingToolsCapability,
5252
ServerCapabilities,
5353
ServerNotification,
5454
ServerRequest,
5555
ServerResult,
56-
SetLevelRequest,
56+
SetLevelRequest, # pyright: ignore[reportDeprecated]
5757
StopReason,
5858
SubscribeRequest,
5959
Tool,
60-
ToolChoice,
61-
ToolResultContent,
60+
ToolChoice, # pyright: ignore[reportDeprecated]
61+
ToolResultContent, # pyright: ignore[reportDeprecated]
6262
ToolsCapability,
63-
ToolUseContent,
63+
ToolUseContent, # pyright: ignore[reportDeprecated]
6464
UnsubscribeRequest,
6565
)
6666
from .types import Role as SamplingRole

src/mcp/client/session.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ class SamplingFnT(Protocol):
5353
async def __call__(
5454
self,
5555
context: ClientRequestContext,
56-
params: types.CreateMessageRequestParams,
57-
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: ... # pragma: no branch
56+
params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated]
57+
) -> (
58+
types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData # pyright: ignore[reportDeprecated]
59+
): ... # pragma: no branch
5860

5961

6062
class ElicitationFnT(Protocol):
@@ -68,11 +70,14 @@ async def __call__(
6870
class ListRootsFnT(Protocol):
6971
async def __call__(
7072
self, context: ClientRequestContext
71-
) -> types.ListRootsResult | types.ErrorData: ... # pragma: no branch
73+
) -> types.ListRootsResult | types.ErrorData: ... # pragma: no branch # pyright: ignore[reportDeprecated]
7274

7375

7476
class LoggingFnT(Protocol):
75-
async def __call__(self, params: types.LoggingMessageNotificationParams) -> None: ... # pragma: no branch
77+
async def __call__(
78+
self,
79+
params: types.LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated]
80+
) -> None: ... # pragma: no branch
7681

7782

7883
class MessageHandlerFnT(Protocol):
@@ -90,8 +95,8 @@ async def _default_message_handler(
9095

9196
async def _default_sampling_callback(
9297
context: ClientRequestContext,
93-
params: types.CreateMessageRequestParams,
94-
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData:
98+
params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated]
99+
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: # pyright: ignore[reportDeprecated]
95100
return types.ErrorData(
96101
code=types.INVALID_REQUEST,
97102
message="Sampling not supported",
@@ -110,15 +115,15 @@ async def _default_elicitation_callback(
110115

111116
async def _default_list_roots_callback(
112117
context: ClientRequestContext,
113-
) -> types.ListRootsResult | types.ErrorData:
118+
) -> types.ListRootsResult | types.ErrorData: # pyright: ignore[reportDeprecated]
114119
return types.ErrorData(
115120
code=types.INVALID_REQUEST,
116121
message="List roots not supported",
117122
)
118123

119124

120125
async def _default_logging_callback(
121-
params: types.LoggingMessageNotificationParams,
126+
params: types.LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated]
122127
) -> None:
123128
pass
124129

@@ -394,7 +399,7 @@ async def set_logging_level(
394399
) -> types.EmptyResult:
395400
"""Send a logging/setLevel request."""
396401
return await self.send_request(
397-
types.SetLevelRequest(params=types.SetLevelRequestParams(level=level, _meta=meta)),
402+
types.SetLevelRequest(params=types.SetLevelRequestParams(level=level, _meta=meta)), # pyright: ignore[reportDeprecated]
398403
types.EmptyResult,
399404
)
400405

@@ -552,7 +557,7 @@ async def list_tools(self, *, params: types.PaginatedRequestParams | None = None
552557

553558
async def send_roots_list_changed(self) -> None:
554559
"""Send a roots/list_changed notification."""
555-
await self.send_notification(types.RootsListChangedNotification())
560+
await self.send_notification(types.RootsListChangedNotification()) # pyright: ignore[reportDeprecated]
556561

557562
async def _on_request(
558563
self, dctx: DispatchContext[TransportContext], method: str, params: Mapping[str, Any] | None
@@ -577,11 +582,11 @@ async def _on_request(
577582
session=self, request_id=dctx.request_id, meta=request.params.meta if request.params else None
578583
)
579584
match request:
580-
case types.CreateMessageRequest(params=sampling_params):
585+
case types.CreateMessageRequest(params=sampling_params): # pyright: ignore[reportDeprecated]
581586
response = await self._sampling_callback(ctx, sampling_params)
582587
case types.ElicitRequest(params=elicit_params):
583588
response = await self._elicitation_callback(ctx, elicit_params)
584-
case types.ListRootsRequest(): # pragma: no branch
589+
case types.ListRootsRequest(): # pragma: no branch # pyright: ignore[reportDeprecated]
585590
response = await self._list_roots_callback(ctx)
586591
client_response = ClientResponse.validate_python(response)
587592
if isinstance(client_response, types.ErrorData):
@@ -612,7 +617,7 @@ async def _on_notify(
612617
# The dispatcher already applied the cancellation; not surfaced to message_handler.
613618
return
614619
try:
615-
if isinstance(notification, types.LoggingMessageNotification):
620+
if isinstance(notification, types.LoggingMessageNotification): # pyright: ignore[reportDeprecated]
616621
await self._logging_callback(notification.params)
617622
await self._message_handler(notification)
618623
except Exception:

0 commit comments

Comments
 (0)