mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
refactor(mcp): keep tools failure handling in place and drop catalog fault log
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
2c9c042cf0
commit
6030fcc01d
2 changed files with 45 additions and 21 deletions
|
|
@ -1415,11 +1415,12 @@ def _upstream_failure_suffix(exc: BaseException) -> str:
|
|||
return f"\n upstream exchange: {detail}" if detail else ""
|
||||
|
||||
|
||||
def _raise_single_server_list_failure(error: Exception, server: MCPServer, catalog: str) -> NoReturn:
|
||||
"""Relay a failed single-server catalog fetch: auth challenges (upstream, or a v2 resolver's
|
||||
HTTPException 401/403 raised at client-build time) become ``MCPUpstreamAuthError`` with the
|
||||
``WWW-Authenticate`` kept (dropped for dcr_bridge servers, whose upstream challenge points at the
|
||||
wrong metadata); anything else becomes a classified ``MCPServerListError``."""
|
||||
def _raise_single_server_list_failure(error: Exception, server: MCPServer) -> NoReturn:
|
||||
"""Relay a failed single-server catalog fetch the way ``_get_tools_from_server`` does: auth
|
||||
challenges (upstream, or a v2 resolver's HTTPException 401/403 raised at client-build time) become
|
||||
``MCPUpstreamAuthError`` with the ``WWW-Authenticate`` kept (dropped for dcr_bridge servers, whose
|
||||
upstream challenge points at the wrong metadata); anything else becomes a classified
|
||||
``MCPServerListError``. Callers log before delegating here."""
|
||||
match error:
|
||||
case MCPUpstreamAuthError() if server.is_dcr_bridge and error.www_authenticate is not None:
|
||||
raise MCPUpstreamAuthError(
|
||||
|
|
@ -1438,18 +1439,10 @@ def _raise_single_server_list_failure(error: Exception, server: MCPServer, catal
|
|||
server_name=server.name,
|
||||
) from error
|
||||
case HTTPException():
|
||||
verbose_logger.warning("Failed to get %s from server %s: %s", catalog, server.name, error)
|
||||
raise MCPServerListError(
|
||||
ServerListFault(tag="internal", status_code=error.status_code), server.name
|
||||
) from error
|
||||
case _:
|
||||
verbose_logger.warning(
|
||||
"Failed to get %s from server %s: %s%s",
|
||||
catalog,
|
||||
server.name,
|
||||
type(error).__name__,
|
||||
_upstream_failure_suffix(error),
|
||||
)
|
||||
raise_classified_list_failure(error, server.name, suppress_challenge=server.is_dcr_bridge)
|
||||
|
||||
|
||||
|
|
@ -4446,8 +4439,40 @@ class MCPServerManager:
|
|||
|
||||
return prefixed_or_original_tools
|
||||
|
||||
except MCPUpstreamAuthError as upstream_auth_error:
|
||||
# Pass-through 401 must surface to single-server routes so the
|
||||
# client triggers the upstream OAuth flow. The multi-server
|
||||
# aggregator catches this explicitly to keep absorbing.
|
||||
if server.is_dcr_bridge and upstream_auth_error.www_authenticate is not None:
|
||||
raise MCPUpstreamAuthError(
|
||||
status_code=upstream_auth_error.status_code,
|
||||
www_authenticate=None,
|
||||
server_name=upstream_auth_error.server_name,
|
||||
) from upstream_auth_error
|
||||
raise
|
||||
except HTTPException as e:
|
||||
# A v2 resolver auth challenge (token_exchange's RFC 9728 401, authorization_code's
|
||||
# browser-OAuth 401, or a 403) is raised at client-build time, inside this try. Route it
|
||||
# through the same MCPUpstreamAuthError channel as pass-through so single-server routes
|
||||
# surface the challenge (the client re-authenticates) while the aggregator keeps absorbing.
|
||||
# Non-auth HTTP errors stay absorbed so one misconfigured server can't blank the listing.
|
||||
if e.status_code in (401, 403):
|
||||
headers: Final = e.headers or {}
|
||||
challenge_header: Final = headers.get("WWW-Authenticate") or headers.get("www-authenticate")
|
||||
raise MCPUpstreamAuthError(
|
||||
status_code=e.status_code,
|
||||
www_authenticate=None if server.is_dcr_bridge else challenge_header,
|
||||
server_name=server.name,
|
||||
) from e
|
||||
verbose_logger.warning("Failed to get tools from server %s: %s", server.name, e)
|
||||
raise MCPServerListError(ServerListFault(tag="internal", status_code=e.status_code), server.name) from e
|
||||
except MCPServerListError:
|
||||
raise
|
||||
except Exception as e:
|
||||
_raise_single_server_list_failure(e, server, "tools")
|
||||
verbose_logger.warning(
|
||||
"Failed to get tools from server %s: %s%s", server.name, type(e).__name__, _upstream_failure_suffix(e)
|
||||
)
|
||||
raise_classified_list_failure(e, server.name, suppress_challenge=server.is_dcr_bridge)
|
||||
|
||||
async def _resolve_list_headers(
|
||||
self,
|
||||
|
|
@ -4569,9 +4594,9 @@ class MCPServerManager:
|
|||
items: Final = await self._prompt_discovery_cache.get(key, fetch)
|
||||
return self._create_prefixed_prompts(items, server, add_prefix=add_prefix)
|
||||
except Exception as error:
|
||||
if raise_on_error:
|
||||
_raise_single_server_list_failure(error, server, "prompts")
|
||||
verbose_logger.warning("Failed to get prompts from server %s: %s", server.name, error)
|
||||
if raise_on_error:
|
||||
_raise_single_server_list_failure(error, server)
|
||||
return []
|
||||
|
||||
async def get_resources_from_server(
|
||||
|
|
@ -4613,9 +4638,9 @@ class MCPServerManager:
|
|||
items: Final = await self._resource_discovery_cache.get(key, fetch)
|
||||
return self._create_prefixed_resources(items, server, add_prefix=add_prefix)
|
||||
except Exception as error:
|
||||
if raise_on_error:
|
||||
_raise_single_server_list_failure(error, server, "resources")
|
||||
verbose_logger.warning("Failed to get resources from server %s: %s", server.name, error)
|
||||
if raise_on_error:
|
||||
_raise_single_server_list_failure(error, server)
|
||||
return []
|
||||
|
||||
async def get_resource_templates_from_server(
|
||||
|
|
@ -4657,9 +4682,9 @@ class MCPServerManager:
|
|||
items: Final = await self._template_discovery_cache.get(key, fetch)
|
||||
return self._create_prefixed_resource_templates(items, server, add_prefix=add_prefix)
|
||||
except Exception as error:
|
||||
if raise_on_error:
|
||||
_raise_single_server_list_failure(error, server, "resource templates")
|
||||
verbose_logger.warning("Failed to get resource_templates from server %s: %s", server.name, error)
|
||||
if raise_on_error:
|
||||
_raise_single_server_list_failure(error, server)
|
||||
return []
|
||||
|
||||
async def read_resource_from_server(
|
||||
|
|
|
|||
|
|
@ -1121,7 +1121,6 @@ if MCP_AVAILABLE:
|
|||
error: MCPServerListError, server: MCPServer, catalog: Literal["prompts", "resources"]
|
||||
) -> HTTPException:
|
||||
fault: Final = classify_list_exception(error)
|
||||
verbose_logger.info("Listing %s from %s failed with a %s fault", catalog, server.name, fault.tag)
|
||||
return HTTPException(
|
||||
status_code=list_fault_http_status(fault),
|
||||
detail={
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue