fix(mcp/v2): surface MCPUpstreamError status codes and raise the env-var precondition on single-result reads

Wire MCPUpstreamError into the three conversion sites (the streamable and SSE /mcp protocol handlers
and the REST tool-call endpoint) so a non-auth upstream failure on a single-result op returns its
semantic status (502 unreachable, 428 precondition, 500 misconfigured) instead of falling through to a
blanket 500.

Also make read_resource and get_prompt pass raise_on_missing_env=True like call_tool, so a missing
per-user env var on any single-result op returns the 412 + setup URL rather than degrading to an
under-authenticated request and a downstream 401. List ops keep degrading (non-blocking discovery).

Validated: 8 unit tests; proxy boots with the wiring loaded; a live tools/call on a none-mode server
returns the upstream result through the v2 path.
This commit is contained in:
Tin Chi Lo 2026-06-20 16:55:13 -07:00
parent 577ee9680a
commit e868947c80
3 changed files with 30 additions and 4 deletions

View file

@ -285,7 +285,11 @@ class MCPServerManagerV2(MCPServerManager):
server, url, mcp_auth_header, extra_headers, raw_headers
)
conn = await self._v2_connection(
server, None, raw_headers=raw_headers, extra_headers=extra_headers
server,
None,
raw_headers=raw_headers,
extra_headers=extra_headers,
raise_on_missing_env=True,
)
if isinstance(conn, Error):
self._egress_item_failure(server, conn.error)
@ -315,7 +319,11 @@ class MCPServerManagerV2(MCPServerManager):
raw_headers,
)
conn = await self._v2_connection(
server, None, raw_headers=raw_headers, extra_headers=extra_headers
server,
None,
raw_headers=raw_headers,
extra_headers=extra_headers,
raise_on_missing_env=True,
)
if isinstance(conn, Error):
self._egress_item_failure(server, conn.error)

View file

@ -18,7 +18,10 @@ import httpx
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from litellm._logging import verbose_logger
from litellm.proxy._experimental.mcp_server.exceptions import MCPUpstreamAuthError
from litellm.proxy._experimental.mcp_server.exceptions import (
MCPUpstreamAuthError,
MCPUpstreamError,
)
from litellm.proxy._experimental.mcp_server.ui_session_utils import (
build_effective_auth_contexts,
)
@ -886,6 +889,10 @@ if MCP_AVAILABLE:
"setup_url": e.setup_url,
},
)
except MCPUpstreamError as e:
# Non-auth upstream failure on the tool call; surface the semantic status
# (502 unreachable / 428 precondition / 500 misconfigured) instead of a blanket 500.
raise e.to_http_exception()
except BlockedPiiEntityError as e:
verbose_logger.error(f"BlockedPiiEntityError in MCP tool call: {str(e)}")
raise HTTPException(

View file

@ -40,7 +40,10 @@ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging
from litellm.proxy._experimental.mcp_server.auth.user_api_key_auth_mcp import (
MCPRequestHandler,
)
from litellm.proxy._experimental.mcp_server.exceptions import MCPUpstreamAuthError
from litellm.proxy._experimental.mcp_server.exceptions import (
MCPUpstreamAuthError,
MCPUpstreamError,
)
from litellm.proxy._experimental.mcp_server.discoverable_endpoints import (
get_request_base_url,
)
@ -4003,6 +4006,10 @@ if MCP_AVAILABLE:
base_url=get_request_base_url(StarletteRequest(scope)),
request_path=scope.get("_original_path") or scope.get("path"),
)
except MCPUpstreamError as e:
# Non-auth upstream failure on a single-result op; surface the semantic status
# (502 unreachable / 428 precondition / 500 misconfigured) instead of a blanket 500.
raise e.to_http_exception()
except HTTPException:
# Re-raise HTTP exceptions to preserve status codes and details
raise
@ -4119,6 +4126,10 @@ if MCP_AVAILABLE:
base_url=get_request_base_url(StarletteRequest(scope)),
request_path=scope.get("_original_path") or scope.get("path"),
)
except MCPUpstreamError as e:
# Non-auth upstream failure on a single-result op; surface the semantic status
# (502 unreachable / 428 precondition / 500 misconfigured) instead of a blanket 500.
raise e.to_http_exception()
except HTTPException:
# Re-raise HTTP exceptions to preserve status codes and details
# (e.g. 401 + WWW-Authenticate challenges from OAuth pass-through).