mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
A 401 while listing tools (a missing or expired per-user OAuth token, or an upstream 401 for any auth_type) was swallowed to an empty tool list, so a single-server client got a 200 with no tools and no WWW-Authenticate challenge instead of a 401 it could re-authenticate against. Only oauth pass-through and delegate-to-upstream oauth2 servers surfaced it; every other auth_type, and the missing-token case for all of them, masked it. The surface-vs-absorb decision now keys on the route, not the auth_type. An upstream 401 in _fetch_tools_with_timeout becomes an MCPUpstreamAuthError regardless of auth_type, and the per-user OAuth challenge raised during client creation (a bare HTTPException 401 carrying a WWW-Authenticate header) is converted to the same type in _get_tools_from_server. The challenge is scoped to 401: a 403 (authenticated but forbidden, e.g. insufficient scope) is not a re-auth signal and degrades to an empty list like any other non-auth error, and the stdio-allowlist 403 (no challenge header) stays absorbed. The existing routing then does the right thing: single-server routes turn the error into a 401 + WWW-Authenticate, while the multi-server aggregator absorbs it to an empty list so one unauthenticated server does not fail the whole listing. On the UI tools page, an OBO (per-user authorization_code) server now shows the Authorize gate when the list call returns 401, not only when no credential row exists. The backend already refreshes a still-refreshable token on the list call, so a 401 means there is no valid token and none could be minted (expired with no usable refresh token), which is exactly when the user must reauthorize. |
||
|---|---|---|
| .. | ||
| auth | ||
| guardrail_translation | ||
| outbound_credentials | ||
| test_byok_oauth_endpoints.py | ||
| test_callback_oauth_error_responses.py | ||
| test_db_credentials.py | ||
| test_discoverable_endpoints.py | ||
| test_is_tool_name_prefixed.py | ||
| test_jwt_mcp_enforcement.py | ||
| test_jwt_mcp_simple.py | ||
| test_mcp_cost_calculator.py | ||
| test_mcp_custom_fields.py | ||
| test_mcp_debug.py | ||
| test_mcp_discovery.py | ||
| test_mcp_elicitation_handler.py | ||
| test_mcp_env_vars.py | ||
| test_mcp_header_alias_utils.py | ||
| test_mcp_hook_extra_headers.py | ||
| test_mcp_max_concurrent_requests.py | ||
| test_mcp_metadata_preservation.py | ||
| test_mcp_oauth_passthrough.py | ||
| test_mcp_oauth_passthrough_cold_start.py | ||
| test_mcp_oauth_passthrough_tools.py | ||
| test_mcp_partial_update.py | ||
| test_mcp_sampling_completion_flow.py | ||
| test_mcp_sampling_model_access.py | ||
| test_mcp_sampling_model_resolution.py | ||
| test_mcp_sampling_priority_selection.py | ||
| test_mcp_sampling_request_builder.py | ||
| test_mcp_sampling_response_conversion.py | ||
| test_mcp_sampling_tool_conversion.py | ||
| test_mcp_server.py | ||
| test_mcp_server_identity_env.py | ||
| test_mcp_server_manager.py | ||
| test_mcp_session_logging.py | ||
| test_mcp_sigv4_auth.py | ||
| test_mcp_stale_session.py | ||
| test_mcp_tool_search.py | ||
| test_mcp_toolset_scope.py | ||
| test_oauth2_token_cache.py | ||
| test_openapi_to_mcp_generator.py | ||
| test_openapi_tool_auth.py | ||
| test_rest_endpoints.py | ||
| test_semantic_tool_filter.py | ||
| test_short_mcp_tool_prefix.py | ||
| test_ui_session_utils.py | ||