From 1e1882851d3f66138cffa0351ac9d535a717189b Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:46:34 -0400 Subject: [PATCH] Keep the CLI URL proxy era-agnostic; monitor claimed-result resolution --- fastmcp_slim/fastmcp/cli/run.py | 13 +++++++++---- fastmcp_slim/fastmcp/client/mixins/tools.py | 9 +++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/fastmcp_slim/fastmcp/cli/run.py b/fastmcp_slim/fastmcp/cli/run.py index 476c0267d..0d12f14c2 100644 --- a/fastmcp_slim/fastmcp/cli/run.py +++ b/fastmcp_slim/fastmcp/cli/run.py @@ -97,10 +97,15 @@ def create_client_server(url: str) -> Any: A FastMCP server instance """ try: - import fastmcp - - client = fastmcp.Client(url) - server = create_proxy(client) + # Hand `create_proxy` the URL rather than a pre-built `Client`. A Client + # target is treated as caller-configured and pinned, so its era would be + # fixed at construction — and since `Client` now defaults to `"auto"`, + # that would pin this proxy's upstream to the modern era and break + # handshake-era clients connecting to it (`ping`, server-initiated + # forwarding). Passing the URL lets the proxy mirror each front + # connection's negotiated era instead, so `fastmcp run ` serves + # both eras. + server = create_proxy(url) return server except Exception as e: logger.error(f"Failed to create client for URL {url}: {e}") diff --git a/fastmcp_slim/fastmcp/client/mixins/tools.py b/fastmcp_slim/fastmcp/client/mixins/tools.py index 418976f69..d52e90cb9 100644 --- a/fastmcp_slim/fastmcp/client/mixins/tools.py +++ b/fastmcp_slim/fastmcp/client/mixins/tools.py @@ -230,8 +230,13 @@ class ClientToolsMixin: else: # A claimed extension result (SEP-2133): resolve it through the # owning extension's resolver into an ordinary CallToolResult. - result = await self._resolve_claimed_result( - name, driven, read_timeout_seconds + # Resolution issues further session requests of its own (result + # validation lists tools; a resolver may make more), so it needs + # the same session monitoring as the calls above — otherwise a + # transport-level failure can kill the session runner while this + # await waits forever. + result = await self._await_with_session_monitoring( + self._resolve_claimed_result(name, driven, read_timeout_seconds) ) # Reflect tool-level errors on the span so callers see ERROR