Deprecate roots, sampling, and logging methods per SEP-2577 (#2926)

This commit is contained in:
Marcelo Trylesinski 2026-06-20 17:25:41 +01:00 • committed by GitHub
commit 4573e4ac33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 206 additions and 123 deletions

View file

@ -259,7 +259,7 @@ async def test_client_unsubscribe_resource(simple_server: Server):
async def test_client_set_logging_level(simple_server: Server):
"""Test setting logging level."""
async with Client(simple_server) as client:
result = await client.set_logging_level("debug")
result = await client.set_logging_level("debug") # pyright: ignore[reportDeprecated]
assert result == snapshot(EmptyResult())

View file

@ -25,7 +25,7 @@ async def test_list_roots_callback():
@server.tool("test_list_roots")
async def test_list_roots(context: Context, message: str):
roots = await context.session.list_roots()
roots = await context.session.list_roots() # pyright: ignore[reportDeprecated]
assert roots == callback_return
return True

View file

@ -36,7 +36,7 @@ async def test_logging_callback():
message: str, level: Literal["debug", "info", "warning", "error"], logger: str, ctx: Context
) -> bool:
"""Send a log notification to the client."""
await ctx.log(level=level, data=message, logger_name=logger)
await ctx.log(level=level, data=message, logger_name=logger) # pyright: ignore[reportDeprecated]
return True
@server.tool("test_tool_with_log_dict")
@ -46,7 +46,7 @@ async def test_logging_callback():
ctx: Context,
) -> bool:
"""Send a log notification with a dict payload."""
await ctx.log(
await ctx.log( # pyright: ignore[reportDeprecated]
level=level,
data={"message": "Test log message", "extra_string": "example", "extra_dict": {"a": 1, "b": 2, "c": 3}},
logger_name=logger,

View file

@ -32,7 +32,7 @@ async def test_sampling_callback():
@server.tool("test_sampling")
async def test_sampling_tool(message: str, ctx: Context) -> bool:
value = await ctx.session.create_message(
value = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=message))],
max_tokens=100,
)
@ -78,7 +78,7 @@ async def test_create_message_backwards_compat_single_content():
@server.tool("test_backwards_compat")
async def test_tool(message: str, ctx: Context) -> bool:
# Call create_message WITHOUT tools
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(type="text", text=message))],
max_tokens=100,
)

View file

@ -95,7 +95,7 @@ async def test_overlapping_sampling_requests_are_serviced_concurrently_by_the_cl
echoes: dict[str, str] = {}
async def sample(tag: str) -> None:
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text=tag))],
max_tokens=10,
)

View file

@ -156,7 +156,7 @@ async def test_a_tool_rejected_with_url_elicitation_required_succeeds_on_retry_a
if not authorised[0]:
# The log line gives the message handler a non-completion notification, so the test's
# filtering branch is exercised in both directions and the wait remains specific.
await ctx.session.send_log_message(level="warning", data="authorisation required", logger="gate")
await ctx.session.send_log_message(level="warning", data="authorisation required", logger="gate") # pyright: ignore[reportDeprecated]
raise UrlElicitationRequiredError(
[
ElicitRequestURLParams(

View file

@ -39,7 +39,7 @@ async def test_set_logging_level_reaches_handler(connect: Connect) -> None:
server = Server("logger", on_set_logging_level=set_logging_level)
async with connect(server) as client:
result = await client.set_logging_level("warning")
result = await client.set_logging_level("warning") # pyright: ignore[reportDeprecated]
assert result == snapshot(EmptyResult())
@ -64,10 +64,10 @@ async def test_log_messages_reach_logging_callback_in_order(connect: Connect) ->
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "chatty"
await ctx.session.send_log_message(
await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="info", data="starting up", logger="app.lifecycle", related_request_id=ctx.request_id
)
await ctx.session.send_log_message(
await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level="error", data={"code": 502, "retryable": True}, related_request_id=ctx.request_id
)
return CallToolResult(content=[TextContent(text="done")])
@ -106,7 +106,7 @@ async def test_log_messages_at_every_severity_level(connect: Connect) -> None:
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "siren"
for level in ALL_LEVELS:
await ctx.session.send_log_message(
await ctx.session.send_log_message( # pyright: ignore[reportDeprecated]
level=level, data=f"a {level} message", related_request_id=ctx.request_id
)
return CallToolResult(content=[TextContent(text="logged")])

View file

@ -29,7 +29,7 @@ async def test_list_roots_round_trip(connect: Connect) -> None:
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "show_roots"
result = await ctx.session.list_roots()
result = await ctx.session.list_roots() # pyright: ignore[reportDeprecated]
lines = [f"{root.uri} name={root.name}" for root in result.roots]
return CallToolResult(content=[TextContent(text="\n".join(lines))])
@ -64,7 +64,7 @@ async def test_list_roots_empty(connect: Connect) -> None:
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "count_roots"
result = await ctx.session.list_roots()
result = await ctx.session.list_roots() # pyright: ignore[reportDeprecated]
return CallToolResult(content=[TextContent(text=str(len(result.roots)))])
server = Server("rooted", on_list_tools=list_tools, on_call_tool=call_tool)
@ -94,7 +94,7 @@ async def test_list_roots_without_callback_is_error(connect: Connect) -> None:
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "show_roots"
try:
await ctx.session.list_roots()
await ctx.session.list_roots() # pyright: ignore[reportDeprecated]
except MCPError as exc:
return CallToolResult(content=[TextContent(text=f"{exc.error.code}: {exc.error.message}")])
raise NotImplementedError # list_roots cannot succeed without a client callback
@ -122,7 +122,7 @@ async def test_list_roots_callback_error_surfaces_to_the_handler(connect: Connec
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "show_roots"
try:
await ctx.session.list_roots()
await ctx.session.list_roots() # pyright: ignore[reportDeprecated]
except MCPError as exc:
return CallToolResult(content=[TextContent(text=f"{exc.error.code}: {exc.error.message}")])
raise NotImplementedError # the callback always answers with an error
@ -159,7 +159,7 @@ async def test_roots_list_changed_reaches_server_handler(connect: Connect) -> No
raise NotImplementedError
async with connect(server, list_roots_callback=list_roots) as client:
await client.send_roots_list_changed()
await client.send_roots_list_changed() # pyright: ignore[reportDeprecated]
with anyio.fail_after(5):
await delivered.wait()

View file

@ -49,7 +49,7 @@ async def test_create_message_round_trip(connect: Connect) -> None:
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "ask_model"
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))],
max_tokens=100,
)
@ -104,7 +104,7 @@ async def test_create_message_params_reach_callback(connect: Connect) -> None:
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "ask_model"
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text="Pick a model."))],
max_tokens=50,
system_prompt="You are terse.",
@ -170,7 +170,7 @@ async def test_create_message_request_with_image_content_reaches_callback(connec
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "describe_image"
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=ImageContent(data="aW1n", mime_type="image/png"))],
max_tokens=100,
)
@ -220,7 +220,7 @@ async def test_create_message_result_with_image_content_returns_to_handler(conne
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "draw"
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text="Draw a cat."))],
max_tokens=100,
)
@ -261,7 +261,7 @@ async def test_create_message_callback_error(connect: Connect) -> None:
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "ask_model"
try:
await ctx.session.create_message(
await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))],
max_tokens=100,
)
@ -292,7 +292,7 @@ async def test_create_message_without_callback_is_error(connect: Connect) -> Non
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "ask_model"
try:
await ctx.session.create_message(
await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))],
max_tokens=100,
)
@ -324,7 +324,7 @@ async def test_create_message_with_tools_is_rejected_for_unsupporting_client(con
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "ask_model"
try:
await ctx.session.create_message(
await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text="What is the weather?"))],
max_tokens=100,
tools=[types.Tool(name="get_weather", input_schema={"type": "object"})],
@ -366,7 +366,7 @@ async def test_create_message_with_mixed_tool_result_content_is_rejected(connect
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "summarise_tools"
try:
await ctx.session.create_message(
await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[
SamplingMessage(
role="user",
@ -452,7 +452,7 @@ async def test_create_message_request_with_audio_content_reaches_callback(connec
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "transcribe"
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=AudioContent(data="c25k", mime_type="audio/wav"))],
max_tokens=100,
)
@ -502,7 +502,7 @@ async def test_create_message_result_with_audio_content_returns_to_handler(conne
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "speak"
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text="Say hello, aloud."))],
max_tokens=100,
)
@ -539,7 +539,7 @@ async def test_create_message_with_list_valued_message_content_reaches_callback(
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "caption"
result = await ctx.session.create_message(
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[
SamplingMessage(
role="user",
@ -606,7 +606,7 @@ async def test_create_message_with_mismatched_tool_use_and_result_ids_is_rejecte
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "continue_tools"
try:
await ctx.session.create_message(
await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[
SamplingMessage(
role="assistant",
@ -662,7 +662,7 @@ async def test_array_content_result_for_a_tool_free_request_surfaces_as_a_valida
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult:
assert params.name == "ask_model"
try:
await ctx.session.create_message(
await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[SamplingMessage(role="user", content=TextContent(text="Two thoughts, please."))],
max_tokens=100,
)

View file

@ -96,7 +96,7 @@ async def test_notifications_are_never_answered() -> None:
recording = RecordingTransport(InMemoryTransport(_echo_server()))
async with Client(recording, list_roots_callback=list_roots) as client:
await client.send_roots_list_changed()
await client.send_roots_list_changed() # pyright: ignore[reportDeprecated]
await client.send_ping()
sent = [message.message for message in recording.sent]

View file

@ -41,10 +41,10 @@ async def test_context_logging_helpers_send_log_notifications(connect: Connect)
@mcp.tool()
async def narrate(ctx: Context) -> str:
await ctx.debug("d")
await ctx.info("i")
await ctx.warning("w")
await ctx.error("e")
await ctx.debug("d") # pyright: ignore[reportDeprecated]
await ctx.info("i") # pyright: ignore[reportDeprecated]
await ctx.warning("w") # pyright: ignore[reportDeprecated]
await ctx.error("e") # pyright: ignore[reportDeprecated]
return "done"
async def collect(params: LoggingMessageNotificationParams) -> None:
@ -136,7 +136,7 @@ async def test_report_progress_without_a_progress_token_sends_nothing(connect: C
@mcp.tool()
async def mill(ctx: Context) -> str:
await ctx.report_progress(1, 3)
await ctx.info("milling done")
await ctx.info("milling done") # pyright: ignore[reportDeprecated]
return "milled"
async def collect(message: IncomingMessage) -> None:
@ -250,8 +250,8 @@ async def test_set_logging_level_is_rejected_and_messages_are_never_filtered(con
@mcp.tool()
async def chatter(ctx: Context) -> str:
await ctx.debug("noise")
await ctx.error("signal")
await ctx.debug("noise") # pyright: ignore[reportDeprecated]
await ctx.error("signal") # pyright: ignore[reportDeprecated]
return "done"
async def collect(params: LoggingMessageNotificationParams) -> None:
@ -259,7 +259,7 @@ async def test_set_logging_level_is_rejected_and_messages_are_never_filtered(con
async with connect(mcp, logging_callback=collect) as client:
with pytest.raises(MCPError) as exc_info:
await client.set_logging_level("error")
await client.set_logging_level("error") # pyright: ignore[reportDeprecated]
await client.call_tool("chatter", {})

View file

@ -414,7 +414,7 @@ async def test_adding_and_removing_tools_does_not_notify_connected_clients(conne
async def grow(ctx: Context) -> str:
mcp.add_tool(extra, name="extra")
mcp.remove_tool("doomed")
await ctx.info("tool set changed")
await ctx.info("tool set changed") # pyright: ignore[reportDeprecated]
return "mutated"
async def collect(message: IncomingMessage) -> None:

View file

@ -7,12 +7,14 @@ test-only-functions convention.
"""
import sys
import warnings
import anyio
import coverage
from mcp.server import Server, ServerRequestContext
from mcp.server.stdio import stdio_server
from mcp.shared.exceptions import MCPDeprecationWarning
from mcp.types import (
CallToolRequestParams,
CallToolResult,
@ -40,7 +42,9 @@ async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) ->
assert params.name == "echo"
assert params.arguments is not None
text = params.arguments["text"]
await ctx.session.send_log_message(level="info", data=f"echoing {text}", logger="echo")
with warnings.catch_warnings():
warnings.simplefilter("ignore", MCPDeprecationWarning)
await ctx.session.send_log_message(level="info", data=f"echoing {text}", logger="echo") # pyright: ignore[reportDeprecated]
return CallToolResult(content=[TextContent(text=text)])

View file

@ -32,7 +32,7 @@ async def test_concurrent_clients_on_one_stateful_server_receive_only_their_own_
@mcp.tool()
async def announce(label: str, ctx: Context) -> str:
"""Emit one info-level log carrying the caller's label, then return it."""
await ctx.info(label)
await ctx.info(label) # pyright: ignore[reportDeprecated]
return label
received_a: list[object] = []

View file

@ -52,7 +52,7 @@ def _server() -> Server:
async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
assert params.name == "narrate"
await ctx.session.send_log_message(level="info", data="related", logger=None, related_request_id=ctx.request_id)
await ctx.session.send_log_message(level="info", data="related", logger=None, related_request_id=ctx.request_id) # pyright: ignore[reportDeprecated]
await ctx.session.send_resource_updated("file:///watched.txt")
return CallToolResult(content=[TextContent(text="done")])

View file

@ -55,7 +55,7 @@ def _counting_server() -> MCPServer:
async def count(ctx: Context, n: int) -> str:
"""Emit n log notifications related to this call, plus one unrelated resource update."""
for i in range(1, n + 1):
await ctx.info(f"tick {i}")
await ctx.info(f"tick {i}") # pyright: ignore[reportDeprecated]
await ctx.session.send_resource_updated("file:///elsewhere.txt")
return f"counted to {n}"
@ -138,10 +138,10 @@ async def test_get_with_last_event_id_replays_only_that_streams_missed_events()
@mcp.tool()
async def count(ctx: Context) -> str:
"""Emit one related notification, wait for the test, then emit two more plus an unrelated one."""
await ctx.info("tick 1")
await ctx.info("tick 1") # pyright: ignore[reportDeprecated]
await release.wait()
await ctx.info("tick 2")
await ctx.info("tick 3")
await ctx.info("tick 2") # pyright: ignore[reportDeprecated]
await ctx.info("tick 3") # pyright: ignore[reportDeprecated]
await ctx.session.send_resource_updated("file:///elsewhere.txt")
return "counted"
@ -219,7 +219,7 @@ async def test_dropping_the_connection_mid_request_does_not_cancel_the_handler()
"""Signal start, wait for the test, signal completion."""
started.set()
await release.wait()
await ctx.info("released")
await ctx.info("released") # pyright: ignore[reportDeprecated]
finished.set()
return "held"
@ -263,10 +263,10 @@ async def test_a_call_whose_stream_the_server_closes_is_resumed_by_the_client()
@mcp.tool()
async def interrupt(ctx: Context) -> str:
"""Emit, close this call's SSE stream, then emit again after the test releases the gate."""
await ctx.info("before close")
await ctx.info("before close") # pyright: ignore[reportDeprecated]
await ctx.close_sse_stream()
await gate.wait()
await ctx.info("after close")
await ctx.info("after close") # pyright: ignore[reportDeprecated]
done.set()
return "resumed"
@ -321,9 +321,9 @@ async def test_a_captured_resumption_token_replays_missed_messages_on_a_new_conn
@mcp.tool()
async def hold(ctx: Context) -> str:
"""Emit one notification, wait for the test, emit another, return."""
await ctx.info("first")
await ctx.info("first") # pyright: ignore[reportDeprecated]
await release.wait()
await ctx.info("second")
await ctx.info("second") # pyright: ignore[reportDeprecated]
return "done"
async def on_token(token: str) -> None:

View file

@ -55,7 +55,7 @@ def _smoke_server() -> MCPServer:
@mcp.tool()
async def announce(ctx: Context) -> str:
"""Send one notification related to this request and one that is not."""
await ctx.info("about to announce")
await ctx.info("about to announce") # pyright: ignore[reportDeprecated]
await ctx.session.send_resource_updated("file:///watched.txt")
return "announced"

View file

@ -1110,10 +1110,10 @@ class TestContextInjection:
mcp = MCPServer()
async def logging_tool(msg: str, ctx: Context) -> str:
await ctx.debug("Debug message")
await ctx.info("Info message")
await ctx.warning("Warning message")
await ctx.error("Error message")
await ctx.debug("Debug message") # pyright: ignore[reportDeprecated]
await ctx.info("Info message") # pyright: ignore[reportDeprecated]
await ctx.warning("Warning message") # pyright: ignore[reportDeprecated]
await ctx.error("Error message") # pyright: ignore[reportDeprecated]
return f"Logged messages for {msg}"
mcp.add_tool(logging_tool)

View file

@ -195,7 +195,7 @@ async def test_server_handles_transport_close_with_pending_server_to_client_requ
both_started.set()
# Blocks on send_request waiting for a client response that never comes.
# _receive_loop's finally will wake this with CONNECTION_CLOSED.
await ctx.session.list_roots()
await ctx.session.list_roots() # pyright: ignore[reportDeprecated]
raise AssertionError # pragma: no cover
server = Server("test", on_call_tool=handle_call_tool)

View file

@ -206,7 +206,7 @@ async def test_connection_ping_sends_ping_on_standalone():
async def test_connection_log_sends_logging_message_notification():
out = StubOutbound()
conn = Connection(out, has_standalone_channel=True)
await conn.log("info", {"k": "v"}, logger="my.logger")
await conn.log("info", {"k": "v"}, logger="my.logger") # pyright: ignore[reportDeprecated]
method, params = out.notifications[0]
assert method == "notifications/message"
assert params is not None
@ -219,7 +219,7 @@ async def test_connection_log_sends_logging_message_notification():
async def test_connection_log_with_meta_includes_meta_in_params():
out = StubOutbound()
conn = Connection(out, has_standalone_channel=True)
await conn.log("info", "x", meta={"traceId": "abc"})
await conn.log("info", "x", meta={"traceId": "abc"}) # pyright: ignore[reportDeprecated]
_, params = out.notifications[0]
assert params is not None
assert params["_meta"] == {"traceId": "abc"}

Some files were not shown because too many files have changed in this diff Show more