From b776089ecc0662d357410e98be8d6e66928b4ac7 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Wed, 4 Feb 2026 23:30:31 +0000 Subject: [PATCH] Add @handle_tool_errors decorator for standardized error handling (#2885) * Add @handle_tool_errors decorator for standardized error handling * Add tests for @handle_tool_errors decorator * Add documentation for @handle_tool_errors decorator * Fix type checking: use getattr for func.__name__ with fallback * Add @overload declarations for proper async/sync type checking * Fix type checking: reorder overloads and use Coroutine for async typing * Update lockfile and fix test formatting * Improve error_handling module: add docstrings, fix logging, handle cancellation, and update documentation * Add auth error mappings, doc tweaks, and doc fix * Pivot to hybrid approach * Remove decorator, keep only core 429/timeout handling --------- Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> --- src/fastmcp/server/server.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 1172793c9..27494f706 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -1095,6 +1095,18 @@ class FastMCP( raise except Exception as e: logger.exception(f"Error calling tool {name!r}") + # Handle actionable errors that should reach the LLM + # even when masking is enabled + if isinstance(e, httpx.HTTPStatusError): + if e.response.status_code == 429: + raise ToolError( + "Rate limited by upstream API, please retry later" + ) from e + if isinstance(e, httpx.TimeoutException): + raise ToolError( + "Upstream request timed out, please retry" + ) from e + # Standard masking logic if self._mask_error_details: raise ToolError(f"Error calling tool {name!r}") from e raise ToolError(f"Error calling tool {name!r}: {e}") from e @@ -1198,6 +1210,17 @@ class FastMCP( raise except Exception as e: logger.exception(f"Error reading resource {uri!r}") + # Handle actionable errors that should reach the LLM + if isinstance(e, httpx.HTTPStatusError): + if e.response.status_code == 429: + raise ResourceError( + "Rate limited by upstream API, please retry later" + ) from e + if isinstance(e, httpx.TimeoutException): + raise ResourceError( + "Upstream request timed out, please retry" + ) from e + # Standard masking logic if self._mask_error_details: raise ResourceError( f"Error reading resource {uri!r}" @@ -1226,6 +1249,17 @@ class FastMCP( raise except Exception as e: logger.exception(f"Error reading resource {uri!r}") + # Handle actionable errors that should reach the LLM + if isinstance(e, httpx.HTTPStatusError): + if e.response.status_code == 429: + raise ResourceError( + "Rate limited by upstream API, please retry later" + ) from e + if isinstance(e, httpx.TimeoutException): + raise ResourceError( + "Upstream request timed out, please retry" + ) from e + # Standard masking logic if self._mask_error_details: raise ResourceError(f"Error reading resource {uri!r}") from e raise ResourceError(f"Error reading resource {uri!r}: {e}") from e