fix(mcp): correct 403 detail text and remove dead _list_tools_for_single_server duplicate

- MCPUpstreamAuthError.to_http_exception() now returns detail='Forbidden' for
  403 upstream responses (and 'Unauthorized' for 401), matching the
  _check_passthrough_upstream_auth pre-flight probe.
- Remove the shadowed first definition of _list_tools_for_single_server in
  rest_endpoints.py; the second definition was the live one and the dead copy
  was a maintenance trap.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-21 20:10:54 +00:00
parent ca8e04c05e
commit 77206df140
No known key found for this signature in database
2 changed files with 2 additions and 101 deletions

View file

@ -50,8 +50,9 @@ class MCPUpstreamAuthError(Exception):
"Bearer resource_metadata="
f'"{prefix}/.well-known/oauth-protected-resource/mcp/{self.server_name}"'
)
detail = "Forbidden" if self.status_code == 403 else "Unauthorized"
return HTTPException(
status_code=self.status_code,
detail="Unauthorized",
detail=detail,
headers={"www-authenticate": challenge} if challenge else None,
)

View file

@ -432,106 +432,6 @@ if MCP_AVAILABLE:
allowed_mcp_servers.append(server)
return allowed_mcp_servers
async def _list_tools_for_single_server(
server_id: str,
allowed_server_ids: List[str],
rest_client_ip: Optional[str],
mcp_server_auth_headers: dict,
mcp_auth_header: Optional[str],
raw_headers_from_request: dict,
user_api_key_dict: "UserAPIKeyAuth",
request: Request,
) -> dict:
"""
Resolve and fetch tools for a single specified MCP server.
Returns the full REST response dict (tools / error / message).
Raises HTTPException on access / IP-filter errors.
"""
# Resolve a server name to its UUID if needed
_name_resolved = None
if server_id not in allowed_server_ids:
_name_resolved = global_mcp_server_manager.get_mcp_server_by_name(server_id)
if _name_resolved is not None and _name_resolved.server_id in set(
allowed_server_ids
):
server_id = _name_resolved.server_id
if server_id not in allowed_server_ids:
_server = (
global_mcp_server_manager.get_mcp_server_by_id(server_id)
or _name_resolved
)
if (
_server is not None
and rest_client_ip is not None
and not global_mcp_server_manager._is_server_accessible_from_ip(
_server, rest_client_ip
)
):
raise HTTPException(
status_code=403,
detail={
"error": "ip_filtering",
"message": (
f"MCP server '{server_id}' is not accessible from your IP address "
f"({rest_client_ip}). This server is restricted to internal "
"networks only. To make it externally accessible, set "
"'available_on_public_internet: true' in the server configuration."
),
},
)
raise HTTPException(
status_code=403,
detail={
"error": "access_denied",
"message": f"The key is not allowed to access server {server_id}",
},
)
server = global_mcp_server_manager.get_mcp_server_by_id(server_id)
if server is None:
return {
"tools": [],
"error": "server_not_found",
"message": f"Server with id {server_id} not found",
}
server_auth_header = _get_server_auth_header(
server, mcp_server_auth_headers, mcp_auth_header
)
user_oauth_extra_headers = await _get_user_oauth_extra_headers(
server, user_api_key_dict
)
try:
tools = await _get_tools_for_single_server(
server,
server_auth_header,
raw_headers_from_request,
user_api_key_dict,
extra_headers=user_oauth_extra_headers,
)
except MCPUpstreamAuthError as e:
# Pass-through server returned 401 — surface it to the client so
# standards-compliant MCP clients trigger the upstream OAuth flow.
raise e.to_http_exception(base_url=get_request_base_url(request))
except Exception as e:
verbose_logger.exception(f"Error getting tools from {server.name}: {e}")
return {
"tools": [],
"error": "server_error",
"message": f"Failed to get tools from server {server.name}: {str(e)}",
}
return {
"tools": tools,
"error": None,
"message": "Successfully retrieved tools",
}
########################################################
async def _list_tools_for_single_server(
server_id: str,
allowed_server_ids: List[str],