mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 04:54:17 +02:00
fix: preserve exception propagation through transport cleanup (#2591)
anyio task groups suppress exceptions when cancel_scope.cancel() is called during cleanup. Capture exceptions before cleanup and re-raise after task group exits cleanly. Also preserve McpError type in client _connect() so callers can catch protocol-level errors specifically.
This commit is contained in:
parent
855e01ed21
commit
95e58e87aa
2 changed files with 14 additions and 3 deletions
|
|
@ -16,7 +16,7 @@ import httpx
|
|||
import mcp.types
|
||||
import pydantic_core
|
||||
from exceptiongroup import catch
|
||||
from mcp import ClientSession
|
||||
from mcp import ClientSession, McpError
|
||||
from mcp.types import (
|
||||
CancelTaskRequest,
|
||||
CancelTaskRequestParams,
|
||||
|
|
@ -514,7 +514,8 @@ class Client(Generic[ClientTransportT]):
|
|||
raise RuntimeError(
|
||||
"Session task completed without exception but connection failed"
|
||||
)
|
||||
if isinstance(exception, httpx.HTTPStatusError):
|
||||
# Preserve specific exception types that clients may want to handle
|
||||
if isinstance(exception, httpx.HTTPStatusError | McpError):
|
||||
raise exception
|
||||
raise RuntimeError(
|
||||
f"Client failed to connect: {exception}"
|
||||
|
|
|
|||
|
|
@ -866,7 +866,11 @@ class FastMCPTransport(ClientTransport):
|
|||
client_read, client_write = client_streams
|
||||
server_read, server_write = server_streams
|
||||
|
||||
# Create a cancel scope for the server task
|
||||
# Capture exceptions to re-raise after task group cleanup.
|
||||
# anyio task groups can suppress exceptions when cancel_scope.cancel()
|
||||
# is called during cleanup, so we capture and re-raise manually.
|
||||
exception_to_raise: BaseException | None = None
|
||||
|
||||
async with (
|
||||
anyio.create_task_group() as tg,
|
||||
_enter_server_lifespan(server=self.server),
|
||||
|
|
@ -892,9 +896,15 @@ class FastMCPTransport(ClientTransport):
|
|||
**session_kwargs,
|
||||
) as client_session:
|
||||
yield client_session
|
||||
except BaseException as e:
|
||||
exception_to_raise = e
|
||||
finally:
|
||||
tg.cancel_scope.cancel()
|
||||
|
||||
# Re-raise after task group has exited cleanly
|
||||
if exception_to_raise is not None:
|
||||
raise exception_to_raise
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<FastMCPTransport(server='{self.server.name}')>"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue