Fix self-referential connection error causes (#4720)

🤖 Generated with OpenAI Codex
This commit is contained in:
Jake Kaplan 2026-07-30 16:23:33 -04:00 committed by GitHub
commit bc07264529
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 59 additions and 5 deletions

View file

@ -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

View file

@ -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.

View file

@ -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."""