Skip to content
Closed
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
56 changes: 1 addition & 55 deletions README.v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
- [Context Properties and Methods](#context-properties-and-methods)
- [Completions](#completions)
- [Elicitation](#elicitation)
- [Sampling](#sampling)
- [Logging and Notifications](#logging-and-notifications)
- [Authentication](#authentication)
- [MCPServer Properties](#mcpserver-properties)
Expand Down Expand Up @@ -928,48 +927,12 @@

- `action`: "accept", "decline", or "cancel"
- `data`: The validated response (only when accepted)

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

### Sampling

Tools can interact with LLMs through sampling (generating text):

<!-- snippet-source examples/snippets/servers/sampling.py -->
```python
from mcp.server.mcpserver import Context, MCPServer
from mcp.types import SamplingMessage, TextContent

mcp = MCPServer(name="Sampling Example")


@mcp.tool()
async def generate_poem(topic: str, ctx: Context) -> str:
"""Generate a poem using LLM sampling."""
prompt = f"Write a short poem about {topic}"

result = await ctx.session.create_message(
messages=[
SamplingMessage(
role="user",
content=TextContent(type="text", text=prompt),
)
],
max_tokens=100,
)

# Since we're not passing tools param, result.content is single content
if result.content.type == "text":
return result.content.text
return str(result.content)
```

_Full example: [examples/snippets/servers/sampling.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/sampling.py)_
<!-- /snippet-source -->

### Logging and Notifications

Tools can send logs and notifications through the context:

Check warning on line 935 in README.v2.md

View check run for this annotation

Claude / Claude Code Review

Inconsistent README treatment of SEP-2577 deprecated features after removing the Sampling section

After this PR removes the Sampling section, README.v2.md still lists `await ctx.session.create_message(messages, max_tokens=...)` in the "Context Properties and Methods" list (line 1068) with no deprecation note, leaving an orphaned pointer to a SEP-2577-deprecated feature, and the retained "Logging and Notifications" section likewise carries no deprecation note. Consider removing or annotating the `create_message` bullet and adding a brief "Deprecated as of 2026-07-28 (SEP-2577)" note to the Lo
Comment on lines 930 to 935

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 After this PR removes the Sampling section, README.v2.md still lists await ctx.session.create_message(messages, max_tokens=...) in the "Context Properties and Methods" list (line 1068) with no deprecation note, leaving an orphaned pointer to a SEP-2577-deprecated feature, and the retained "Logging and Notifications" section likewise carries no deprecation note. Consider removing or annotating the create_message bullet and adding a brief "Deprecated as of 2026-07-28 (SEP-2577)" note to the Logging section for consistency.

Extended reasoning...

What the issue is. This PR implements SEP-2577 by deprecating roots, sampling, and logging, and its documentation strategy (per the PR description) is to drop the sampling-specific docs rather than decorate them with suppressions: the README's entire ### Sampling section, its TOC entry, and examples/snippets/servers/sampling.py are all removed. However, the "Context Properties and Methods" list further down still contains the bullet await ctx.session.create_message(messages, max_tokens=...) - Request LLM sampling/completion (README.v2.md line 1068), and a search of the post-PR README for "deprecat"/"SEP-2577" returns no hits.

How it manifests. A reader scanning the Context API list now finds create_message recommended as a normal context capability, but the section that previously explained sampling (and showed the example it linked to) no longer exists anywhere in the README, and nothing tells the reader the feature is deprecated as of 2026-07-28. Similarly, the ### Logging and Notifications section (line 933) is kept in full even though logging is equally deprecated by SEP-2577, with no note. The only place the deprecation is documented is docs/migration.md.

Step-by-step. (1) Pre-PR, README.v2.md had a ### Sampling section with a full create_message example, plus the line-1068 bullet pointing at the same feature. (2) This PR deletes the section and TOC entry but leaves the bullet untouched. (3) Post-PR, line 1068 is the only sampling documentation left in the README, it carries no deprecation context, and grepping the file for "SEP-2577" or "deprecat" finds nothing. (4) The Logging section at line 933 remains fully documented with the same absence of any note.

Why this is worth flagging despite the PR's stated rationale. One verifier argued this follows the PR's deliberate "drop the snippet rather than add suppressions" approach and that nothing left in the README is factually wrong (the features remain functional for ≤ 2025-11-25 sessions). That's fair as far as the code-snippet docs go — the Logging section's backing snippet uses ctx.debug/info/... and needs no suppression. But the leftover create_message bullet is not consistent with that rationale either way: if the policy is "remove sampling docs," the bullet was missed; if the policy is "keep accurate references," it now lacks the context the deleted section used to provide. Either way the inconsistency is introduced by this PR's own doc edits and is trivially cheap to fix.

How to fix. Either delete the create_message bullet from the Context Properties and Methods list, or annotate it (and optionally the ### Logging and Notifications heading) with a one-line "Deprecated as of 2026-07-28 (SEP-2577); still functional for sessions negotiating ≤ 2025-11-25" note, mirroring the wording already used in docs/migration.md.

Severity. Documentation-only and non-blocking — filed as a nit.


<!-- snippet-source examples/snippets/servers/notifications.py -->
```python
Expand Down Expand Up @@ -2108,7 +2071,6 @@
import os

from mcp import ClientSession, StdioServerParameters, types
from mcp.client.context import ClientRequestContext
from mcp.client.stdio import stdio_client

# Create server parameters for stdio connection
Expand All @@ -2119,25 +2081,9 @@
)


# Optional: create a sampling callback
async def handle_sampling_message(
context: ClientRequestContext, params: types.CreateMessageRequestParams
) -> types.CreateMessageResult:
print(f"Sampling request: {params.messages}")
return types.CreateMessageResult(
role="assistant",
content=types.TextContent(
type="text",
text="Hello, world! from model",
),
model="gpt-3.5-turbo",
stop_reason="endTurn",
)


async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session:
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()

Expand Down
8 changes: 7 additions & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,13 @@ Tasks are expected to return as a separate MCP extension in a future release.

## Deprecations

<!-- Add deprecations below -->
### Roots, Sampling, and Logging 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. 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.

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`.

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
JSONRPCMessage,
PromptReference,
ResourceTemplateReference,
SamplingMessage,
SetLevelRequestParams,
SamplingMessage, # pyright: ignore[reportDeprecated]
SetLevelRequestParams, # pyright: ignore[reportDeprecated]
SubscribeRequestParams,
TextContent,
TextResourceContents,
Expand Down Expand Up @@ -177,7 +177,7 @@ async def test_sampling(prompt: str, ctx: Context) -> str:
try:
# Request sampling from client
result = await ctx.session.create_message(
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))], # pyright: ignore[reportDeprecated]
max_tokens=100,
)

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


mcp._lowlevel_server.add_request_handler( # pyright: ignore[reportPrivateUsage]
"logging/setLevel", SetLevelRequestParams, handle_set_logging_level
"logging/setLevel",
SetLevelRequestParams, # pyright: ignore[reportDeprecated]
handle_set_logging_level,
)
Comment on lines 420 to 424

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just delete this?

mcp._lowlevel_server.add_request_handler( # pyright: ignore[reportPrivateUsage]
"resources/subscribe", SubscribeRequestParams, handle_subscribe
Expand Down
19 changes: 1 addition & 18 deletions examples/snippets/clients/stdio_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import os

from mcp import ClientSession, StdioServerParameters, types
from mcp.client.context import ClientRequestContext
from mcp.client.stdio import stdio_client

# Create server parameters for stdio connection
Expand All @@ -17,25 +16,9 @@
)


# Optional: create a sampling callback
async def handle_sampling_message(
context: ClientRequestContext, params: types.CreateMessageRequestParams
) -> types.CreateMessageResult:
print(f"Sampling request: {params.messages}")
return types.CreateMessageResult(
role="assistant",
content=types.TextContent(
type="text",
text="Hello, world! from model",
),
model="gpt-3.5-turbo",
stop_reason="endTurn",
)


async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session:
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()

Expand Down
2 changes: 1 addition & 1 deletion examples/snippets/servers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def run_server():
if len(sys.argv) < 2:
print("Usage: server <server-name> [transport]")
print("Available servers: basic_tool, basic_resource, basic_prompt, tool_progress,")
print(" sampling, elicitation, completion, notifications,")
print(" elicitation, completion, notifications,")
print(" mcpserver_quickstart, structured_output, images")
print("Available transports: stdio (default), sse, streamable-http")
sys.exit(1)
Expand Down
25 changes: 0 additions & 25 deletions examples/snippets/servers/sampling.py

This file was deleted.

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

[tool.markdown.lint]
Expand Down
16 changes: 8 additions & 8 deletions src/mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
ClientRequest,
ClientResult,
CompleteRequest,
CreateMessageRequest,
CreateMessageResult,
CreateMessageRequest, # pyright: ignore[reportDeprecated]
CreateMessageResult, # pyright: ignore[reportDeprecated]
CreateMessageResultWithTools,
ErrorData,
GetPromptRequest,
Expand All @@ -32,7 +32,7 @@
ListResourcesResult,
ListToolsResult,
LoggingLevel,
LoggingMessageNotification,
LoggingMessageNotification, # pyright: ignore[reportDeprecated]
Notification,
PingRequest,
ProgressNotification,
Expand All @@ -46,21 +46,21 @@
SamplingCapability,
SamplingContent,
SamplingContextCapability,
SamplingMessage,
SamplingMessage, # pyright: ignore[reportDeprecated]
SamplingMessageContentBlock,
SamplingToolsCapability,
ServerCapabilities,
ServerNotification,
ServerRequest,
ServerResult,
SetLevelRequest,
SetLevelRequest, # pyright: ignore[reportDeprecated]
StopReason,
SubscribeRequest,
Tool,
ToolChoice,
ToolResultContent,
ToolChoice, # pyright: ignore[reportDeprecated]
ToolResultContent, # pyright: ignore[reportDeprecated]
ToolsCapability,
ToolUseContent,
ToolUseContent, # pyright: ignore[reportDeprecated]
UnsubscribeRequest,
)
from .types import Role as SamplingRole
Expand Down
31 changes: 18 additions & 13 deletions src/mcp/client/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ class SamplingFnT(Protocol):
async def __call__(
self,
context: ClientRequestContext,
params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: ... # pragma: no branch
params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated]
) -> (
types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData # pyright: ignore[reportDeprecated]
): ... # pragma: no branch


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


class LoggingFnT(Protocol):
async def __call__(self, params: types.LoggingMessageNotificationParams) -> None: ... # pragma: no branch
async def __call__(
self,
params: types.LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated]
) -> None: ... # pragma: no branch


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

async def _default_sampling_callback(
context: ClientRequestContext,
params: types.CreateMessageRequestParams,
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData:
params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated]
) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: # pyright: ignore[reportDeprecated]
return types.ErrorData(
code=types.INVALID_REQUEST,
message="Sampling not supported",
Expand All @@ -110,15 +115,15 @@ async def _default_elicitation_callback(

async def _default_list_roots_callback(
context: ClientRequestContext,
) -> types.ListRootsResult | types.ErrorData:
) -> types.ListRootsResult | types.ErrorData: # pyright: ignore[reportDeprecated]
return types.ErrorData(
code=types.INVALID_REQUEST,
message="List roots not supported",
)


async def _default_logging_callback(
params: types.LoggingMessageNotificationParams,
params: types.LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated]
) -> None:
pass

Expand Down Expand Up @@ -394,7 +399,7 @@ async def set_logging_level(
) -> types.EmptyResult:
"""Send a logging/setLevel request."""
return await self.send_request(
types.SetLevelRequest(params=types.SetLevelRequestParams(level=level, _meta=meta)),
types.SetLevelRequest(params=types.SetLevelRequestParams(level=level, _meta=meta)), # pyright: ignore[reportDeprecated]
types.EmptyResult,
)

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

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

async def _on_request(
self, dctx: DispatchContext[TransportContext], method: str, params: Mapping[str, Any] | None
Expand All @@ -577,11 +582,11 @@ async def _on_request(
session=self, request_id=dctx.request_id, meta=request.params.meta if request.params else None
)
match request:
case types.CreateMessageRequest(params=sampling_params):
case types.CreateMessageRequest(params=sampling_params): # pyright: ignore[reportDeprecated]
response = await self._sampling_callback(ctx, sampling_params)
case types.ElicitRequest(params=elicit_params):
response = await self._elicitation_callback(ctx, elicit_params)
case types.ListRootsRequest(): # pragma: no branch
case types.ListRootsRequest(): # pragma: no branch # pyright: ignore[reportDeprecated]
response = await self._list_roots_callback(ctx)
client_response = ClientResponse.validate_python(response)
if isinstance(client_response, types.ErrorData):
Expand Down Expand Up @@ -612,7 +617,7 @@ async def _on_notify(
# The dispatcher already applied the cancellation; not surfaced to message_handler.
return
try:
if isinstance(notification, types.LoggingMessageNotification):
if isinstance(notification, types.LoggingMessageNotification): # pyright: ignore[reportDeprecated]
await self._logging_callback(notification.params)
await self._message_handler(notification)
except Exception:
Expand Down
Loading
Loading