Era-gate client.set_logging_level on modern connections

This commit is contained in:
Jeremiah Lowin 2026-07-26 14:39:31 -04:00
commit fca339084b
No known key found for this signature in database
2 changed files with 24 additions and 15 deletions

View file

@ -1351,7 +1351,22 @@ class Client(
)
async def set_logging_level(self, level: mcp_types.LoggingLevel) -> None:
"""Send a logging/setLevel request."""
"""Send a logging/setLevel request.
Handshake-era servers only. `logging/setLevel` asks the server to
remember a level for the rest of the session, and the 2026-07-28
protocol has no session to remember it in the method is absent from
that era's registry. Log *notifications* are unaffected: they ride the
request's own stream, so a server's `ctx.info()` still reaches you.
Filter by level on the receiving side instead, in your `log_handler`.
"""
if self.protocol_version in MODERN_PROTOCOL_VERSIONS:
raise RuntimeError(
"logging/setLevel is not available on MCP 2026-07-28 "
"connections; the method requires per-session server state that "
"the modern protocol does not have. Filter incoming log "
"messages by level in your log_handler instead."
)
# Deprecated upstream in SDK v2 but deliberately kept per compat directive;
# removed with the multi-round-trip follow-up.
await self._await_with_session_monitoring(

View file

@ -353,23 +353,17 @@ async def test_session_id_access_does_not_crash_on_modern(sessionless_server, mo
@pytest.mark.parametrize("mode", MODERN_MODES)
async def test_set_logging_level_does_not_crash_on_modern(sessionless_server, mode):
"""logging/setLevel is a session-id-keyed, deprecated-at-2026 operation.
On a sessionless modern in-memory connection it must degrade cleanly (either
succeed as a no-op or raise a surfaced MCPError) rather than crash the
connection. Characterization: capture whichever the current contract is.
async def test_set_logging_level_is_era_gated_on_modern(sessionless_server, mode):
"""`logging/setLevel` asks a server to remember a level for the session, and
the modern era has no session the method is absent from its registry. The
FastMCP client says so plainly instead of no-opping or surfacing the SDK's
opaque "Method not found", and the connection stays usable afterward.
"""
async with SDKClient(_server(sessionless_server), mode=mode) as client:
outcome: str
try:
await client.set_logging_level("debug") # ty: ignore[deprecated]
outcome = "ok"
except MCPError:
outcome = "mcperror"
# Either way the connection is still usable afterward.
async with FastMCPClient(sessionless_server, mode=mode) as client:
with pytest.raises(RuntimeError, match="2026-07-28"):
await client.set_logging_level("debug")
result = await client.call_tool("read_session_id", {})
assert result.is_error is False
assert outcome in {"ok", "mcperror"}
# ---------------------------------------------------------------------------