fix(mcp): handle passthrough OAuth metadata and startup auth errors

- discoverable_endpoints: For pass-through MCP servers, when upstream
  oauth-protected-resource returns a non-200/non-dict response, raise
  HTTP 502 instead of falling through to default gateway metadata.
  Falling through would direct MCP clients at the gateway, which is
  not the authorization server for pass-through configs.

- mcp_server_manager: Wrap _get_tools_from_server in startup tool name
  mapping with try/except. Since _get_tools_from_server now re-raises
  MCPUpstreamAuthError, an upstream 401 from a pass-through server at
  startup (when no user token is present) would otherwise abort the
  loop and leave subsequent servers unmapped.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-20 18:19:28 +00:00
parent d1d9ff0121
commit 719adfd043
No known key found for this signature in database
2 changed files with 33 additions and 1 deletions

View file

@ -893,6 +893,22 @@ async def _build_oauth_protected_resource_response(
response = {**upstream_metadata, "resource": resource_url}
return response
# Upstream responded but with non-200 or non-dict payload. For
# pass-through servers the gateway is NOT the authorization server,
# so we must not fall through to the default gateway metadata —
# that would point clients at the wrong IdP.
verbose_logger.warning(
"Upstream oauth-protected-resource metadata unavailable for "
f"pass-through MCP server {mcp_server.name!r}"
)
raise HTTPException(
status_code=502,
detail=(
"Upstream oauth-protected-resource metadata unavailable "
f"for MCP server {mcp_server.name!r}"
),
)
return {
"authorization_servers": [
(

View file

@ -3060,7 +3060,23 @@ class MCPServerManager:
if server.needs_user_oauth_token:
# Skip OAuth2 servers that rely on user-provided tokens
continue
tools = await self._get_tools_from_server(server)
try:
tools = await self._get_tools_from_server(server)
except MCPUpstreamAuthError as e:
# Pass-through servers expect a user-supplied bearer token;
# at startup we have none, so an upstream 401 is normal.
# Swallow it so we keep mapping the remaining servers.
verbose_logger.debug(
f"Skipping tool name mapping for server {server.name} "
f"due to upstream auth error: {str(e)}"
)
continue
except Exception as e:
verbose_logger.warning(
f"Failed to get tools from server {server.name} during "
f"tool name mapping initialization: {str(e)}"
)
continue
for tool in tools:
# The tool.name here is already prefixed from _get_tools_from_server
# Extract original name for mapping