mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 20:44:17 +02:00
Fix self-referential connection error causes (#4720)
🤖 Generated with OpenAI Codex
This commit is contained in:
parent
bcef61d806
commit
bc07264529
3 changed files with 59 additions and 5 deletions
|
|
@ -1022,7 +1022,10 @@ class Client(
|
|||
raise RuntimeError(
|
||||
"Session task completed without exception but connection failed"
|
||||
)
|
||||
raise _connection_failure(exception) from exception
|
||||
failure = _connection_failure(exception)
|
||||
if failure is exception:
|
||||
raise exception
|
||||
raise failure from exception
|
||||
|
||||
self._session_state.nesting_counter += 1
|
||||
|
||||
|
|
|
|||
|
|
@ -96,10 +96,12 @@ async def test_unauthorized(client_unauthorized: Client):
|
|||
SDK v2 surfaces the server's 401 as an MCPError ("Server returned an error
|
||||
response") rather than re-raising the raw httpx2.HTTPStatusError.
|
||||
"""
|
||||
with pytest.raises(MCPError, match="error response"):
|
||||
with pytest.raises(MCPError, match="error response") as exc_info:
|
||||
async with client_unauthorized:
|
||||
pass
|
||||
|
||||
assert exc_info.value.__cause__ is not exc_info.value
|
||||
|
||||
|
||||
async def test_ping(streamable_http_server: str):
|
||||
"""Test that we can ping the server.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,33 @@
|
|||
"""Client session and task error propagation tests."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
|
||||
import httpx2
|
||||
import pytest
|
||||
from mcp import ClientSession
|
||||
from mcp_types import TextContent
|
||||
from mcp import ClientSession, MCPError
|
||||
from mcp_types import INTERNAL_ERROR, TextContent
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.client.transports import PythonStdioTransport
|
||||
from fastmcp.client.transports import ClientTransport, PythonStdioTransport
|
||||
from fastmcp.client.transports.base import TransportOptions
|
||||
|
||||
|
||||
class _FailingTransport(ClientTransport):
|
||||
def __init__(self, exception: Exception) -> None:
|
||||
self._exception = exception
|
||||
|
||||
@asynccontextmanager
|
||||
async def connect_session(
|
||||
self, **session_kwargs: Any
|
||||
) -> AsyncIterator[ClientSession]:
|
||||
raise self._exception
|
||||
yield
|
||||
|
||||
|
||||
class TestSessionTaskErrorPropagation:
|
||||
"""Tests for ensuring session task errors propagate to client calls.
|
||||
|
||||
|
|
@ -143,6 +158,40 @@ class TestSessionTaskErrorPropagation:
|
|||
client._session_state.session_task = original_task
|
||||
|
||||
|
||||
class TestConnectionFailurePropagation:
|
||||
@pytest.mark.parametrize(
|
||||
"failure",
|
||||
[
|
||||
MCPError(code=INTERNAL_ERROR, message="upstream failed"),
|
||||
httpx2.HTTPStatusError(
|
||||
"upstream unavailable",
|
||||
request=httpx2.Request("GET", "https://example.com"),
|
||||
response=httpx2.Response(503),
|
||||
),
|
||||
],
|
||||
ids=["mcp-error", "http-status-error"],
|
||||
)
|
||||
async def test_preserves_passthrough_exception(self, failure: Exception):
|
||||
client = Client(transport=_FailingTransport(failure))
|
||||
|
||||
with pytest.raises(type(failure)) as exc_info:
|
||||
async with client:
|
||||
pass
|
||||
|
||||
assert exc_info.value is failure
|
||||
assert exc_info.value.__cause__ is not failure
|
||||
|
||||
async def test_wraps_other_failures_with_cause(self):
|
||||
failure = OSError("connection refused")
|
||||
client = Client(transport=_FailingTransport(failure))
|
||||
|
||||
with pytest.raises(RuntimeError, match="Client failed to connect") as exc_info:
|
||||
async with client:
|
||||
pass
|
||||
|
||||
assert exc_info.value.__cause__ is failure
|
||||
|
||||
|
||||
class TestCustomSessionClass:
|
||||
"""Transports build the session class the client asks for."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue