Fix StatefulProxyClient reconnection after session failure (#4829)

This commit is contained in:
Jeremiah Lowin 2026-08-13 16:01:19 -04:00 committed by GitHub
commit 3dd0886156
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 33 additions and 4 deletions

View file

@ -1804,10 +1804,12 @@ class StatefulProxyClient(ProxyClient[ClientTransportT]):
return cast(StatefulProxyClient[ClientTransportT], super().new())
async def __aexit__(self, exc_type, exc_value, traceback) -> None: # type: ignore[override] # ty:ignore[invalid-method-override]
"""The stateful proxy client will be forced disconnected when the session is exited.
So we do nothing here.
"""
"""Release this context without disconnecting the persistent session."""
with anyio.CancelScope(shield=True):
async with self._session_state.lock:
self._session_state.nesting_counter = max(
0, self._session_state.nesting_counter - 1
)
async def clear(self):
"""Clear all cached clients and force disconnect them."""

View file

@ -1,4 +1,5 @@
import asyncio
import contextlib
import weakref
from dataclasses import dataclass
from unittest.mock import MagicMock
@ -89,6 +90,32 @@ async def stateless_server(stateful_proxy_server: FastMCP):
class TestStatefulProxyClient:
async def test_reconnects_after_persistent_session_ends(self):
"""A completed request must not prevent a dead session from reconnecting."""
backend = FastMCP("backend")
@backend.tool
def echo(value: str) -> str:
return value
client = StatefulProxyClient(backend)
try:
async with client:
result = await client.call_tool("echo", {"value": "first"})
assert result.data == "first"
session_task = client._session_state.session_task
assert session_task is not None
session_task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await session_task
async with client:
result = await client.call_tool("echo", {"value": "second"})
assert result.data == "second"
finally:
await client.close()
async def test_concurrent_log_requests_no_mixing(
self, stateful_proxy_server: FastMCP
):