From a9e049d33b1aabb055939083a69a1bc6657b93ba Mon Sep 17 00:00:00 2001 From: shrey kharbanda Date: Wed, 28 Oct 2026 15:46:32 -0700 Subject: [PATCH] fix(mcp): walk the exception cause chain with a bounded loop The recursive_detector code-quality check rejects recursive functions, so _caused_by now follows __cause__ / __context__ in a loop capped at 16 links. Behavior is unchanged --- .../proxy/mcp_server/auth/user_api_key_auth_mcp.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/mcp_server/auth/user_api_key_auth_mcp.py b/litellm/proxy/mcp_server/auth/user_api_key_auth_mcp.py index fc84e699ae9..78ec2c1fe6e 100644 --- a/litellm/proxy/mcp_server/auth/user_api_key_auth_mcp.py +++ b/litellm/proxy/mcp_server/auth/user_api_key_auth_mcp.py @@ -101,6 +101,7 @@ if TYPE_CHECKING: _EMPTY_TOOLSET_GRANTS: Final[Mapping[str, Sequence[str]]] = MappingProxyType({}) OPTIONAL_STRING_ADAPTER: Final[TypeAdapter[str | None]] = TypeAdapter(str | None) _PROXY_SETTINGS_ADAPTER: Final = TypeAdapter(Mapping[str, object]) +_CAUSE_CHAIN_MAX_DEPTH: Final = 16 _CACHED_VALUE: Final = TypeAdapter(object) @@ -234,10 +235,15 @@ def _is_legacy_delegate_cold_start(mcp_servers: list[str] | None, client_ip: str return True -def _caused_by(exc: BaseException | None, error_type: type[BaseException], depth: int = 16) -> bool: - if exc is None or depth == 0: - return False - return isinstance(exc, error_type) or _caused_by(exc.__cause__ or exc.__context__, error_type, depth - 1) +def _caused_by(exc: BaseException, error_type: type[BaseException]) -> bool: + link: BaseException | None = exc # rebind-ok: advances one link of the cause chain per iteration + for _ in range(_CAUSE_CHAIN_MAX_DEPTH): + if link is None: + return False + if isinstance(link, error_type): + return True + link = link.__cause__ or link.__context__ + return False async def _bearer_is_litellm_credential(token: str) -> bool: