1b74b06753
Cut comment and docstring volume roughly in half across src, tests, examples, and docs_src: removed comments that restate the adjacent code, leftover development narration, section banners, and self-evident Args/Returns blocks, and compressed the remaining docstrings to a Google-style summary line plus only the detail that earns its place. Kept (and tightened) the load-bearing content: Raises sections, deprecation and version-availability notes, spec/RFC/issue references, why-comments for non-obvious decisions, and all coverage pragmas. The generated mcp_types.v* wire modules are untouched.
119 lines
4.9 KiB
Python
119 lines
4.9 KiB
Python
"""`docs/advanced/deprecated.md`: the page's behavioural claims, executed against the live SDK.
|
|
|
|
The chapter has no `docs_src/` example by design — a runnable one would teach exactly what
|
|
the page tells readers not to build — so each test runs a claim the page states in prose,
|
|
keeping the prose from drifting away from what the SDK does.
|
|
"""
|
|
|
|
import warnings
|
|
|
|
import pytest
|
|
from mcp_types import CreateMessageRequestParams, CreateMessageResult, SamplingMessage, TextContent
|
|
|
|
from mcp import Client, MCPDeprecationWarning, MCPError
|
|
from mcp.client import ClientRequestContext
|
|
from mcp.server import MCPServer
|
|
from mcp.server.mcpserver import Context
|
|
from mcp.shared.exceptions import NoBackChannelError
|
|
|
|
pytestmark = pytest.mark.anyio
|
|
|
|
mcp = MCPServer("Deprecated")
|
|
|
|
|
|
@mcp.tool()
|
|
async def ask_model(prompt: str, ctx: Context) -> str:
|
|
"""A tool still built on server-initiated sampling."""
|
|
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
|
|
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
|
|
max_tokens=8,
|
|
)
|
|
return str(result.content)
|
|
|
|
|
|
@mcp.tool()
|
|
async def old_log(ctx: Context) -> str:
|
|
"""A tool still built on protocol logging."""
|
|
await ctx.info("hello") # pyright: ignore[reportDeprecated]
|
|
return "ok"
|
|
|
|
|
|
async def test_create_message_warns_and_then_raises_on_a_modern_connection() -> None:
|
|
"""`@deprecated` warns the moment the method is called; only afterwards does the channel refuse the send."""
|
|
async with Client(mcp) as client:
|
|
with (
|
|
pytest.warns(
|
|
MCPDeprecationWarning,
|
|
match=r"^The sampling capability is deprecated as of 2026-07-28 \(SEP-2577\)\.$",
|
|
),
|
|
pytest.raises(NoBackChannelError) as exc,
|
|
):
|
|
await client.call_tool("ask_model", {"prompt": "hi"})
|
|
assert str(exc.value) == (
|
|
"Cannot send 'sampling/createMessage': "
|
|
"this transport context has no back-channel for server-initiated requests."
|
|
)
|
|
|
|
|
|
async def test_a_deprecated_feature_still_works_on_a_legacy_session() -> None:
|
|
"""The deprecation is advisory: under mode='legacy' the same tool completes, with only the warning."""
|
|
|
|
async def canned_sampling(context: ClientRequestContext, params: CreateMessageRequestParams) -> CreateMessageResult:
|
|
return CreateMessageResult(
|
|
role="assistant",
|
|
content=TextContent(type="text", text="four"),
|
|
model="canned",
|
|
stop_reason="endTurn",
|
|
)
|
|
|
|
async with Client(mcp, mode="legacy", sampling_callback=canned_sampling) as client:
|
|
with pytest.warns(MCPDeprecationWarning, match=r"The sampling capability is deprecated"):
|
|
result = await client.call_tool("ask_model", {"prompt": "What is 2 + 2?"})
|
|
assert not result.is_error
|
|
[content] = result.content
|
|
assert isinstance(content, TextContent)
|
|
assert "four" in content.text
|
|
|
|
|
|
async def test_send_ping_still_carries_the_deprecation_warning() -> None:
|
|
"""`ping` is removed outright in 2026-07-28 (no deprecation window), yet the method is still decorated."""
|
|
async with Client(mcp) as client:
|
|
with (
|
|
pytest.warns(
|
|
MCPDeprecationWarning,
|
|
match=r"^ping is removed as of 2026-07-28; the method only works under mode='legacy'\.$",
|
|
),
|
|
pytest.raises(MCPError, match="^Method not found$"),
|
|
):
|
|
await client.send_ping() # pyright: ignore[reportDeprecated]
|
|
|
|
|
|
def test_mcp_deprecation_warning_is_a_user_warning() -> None:
|
|
"""Deriving from `UserWarning` keeps the warning visible with no `-W` flag;
|
|
Python's default filter hides `DeprecationWarning` outside `__main__`."""
|
|
assert issubclass(MCPDeprecationWarning, UserWarning)
|
|
assert not issubclass(MCPDeprecationWarning, DeprecationWarning)
|
|
|
|
|
|
@pytest.mark.filterwarnings("error::mcp.MCPDeprecationWarning")
|
|
async def test_error_filter_turns_the_deprecated_call_into_the_documented_tool_error() -> None:
|
|
"""Under the error filter the warning is raised, wrapped, and surfaces as the tool error the page quotes."""
|
|
async with Client(mcp) as client:
|
|
result = await client.call_tool("old_log", {})
|
|
assert result.is_error
|
|
[content] = result.content
|
|
assert isinstance(content, TextContent)
|
|
assert content.text == (
|
|
"Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577)."
|
|
)
|
|
|
|
|
|
async def test_filterwarnings_ignore_silences_the_whole_category() -> None:
|
|
async with Client(mcp) as client:
|
|
with warnings.catch_warnings(record=True) as caught:
|
|
warnings.simplefilter("always")
|
|
warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
|
|
result = await client.call_tool("old_log", {})
|
|
assert not result.is_error
|
|
assert not any(issubclass(w.category, MCPDeprecationWarning) for w in caught)
|