diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 9e2dbc05a65..1e38e2c6c5d 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -6917,14 +6917,16 @@ class MCPServerManager: async def _check_mcp_liveness(url: str) -> tuple[Literal["healthy", "unhealthy", "unknown"], str | None]: async def probe() -> None: endpoint: Final = httpx.URL(url).copy_with(username="", password="") - async with httpx.AsyncClient( - verify=get_ssl_configuration(), - trust_env=False, - follow_redirects=False, - timeout=MCP_HEALTH_CHECK_TIMEOUT, - ) as client: - async with client.stream("GET", endpoint, auth=None): - pass + async with ( + httpx.AsyncClient( + verify=get_ssl_configuration(), + trust_env=False, + follow_redirects=False, + timeout=MCP_HEALTH_CHECK_TIMEOUT, + ) as client, + client.stream("GET", endpoint, auth=None), + ): + pass try: await asyncio.wait_for(probe(), timeout=MCP_HEALTH_CHECK_TIMEOUT) @@ -6933,7 +6935,7 @@ class MCPServerManager: return "unhealthy", f"Health check timed out after {MCP_HEALTH_CHECK_TIMEOUT} seconds" except asyncio.CancelledError: return "unknown", "Health check was cancelled" - except Exception as exc: + except (httpx.HTTPError, httpx.InvalidURL, OSError, ValueError) as exc: return "unhealthy", f"Liveness check failed ({type(exc).__name__})" async def health_check_server(self, server_id: str, mcp_auth_header: str | None = None) -> LiteLLM_MCPServerTable: @@ -6994,12 +6996,14 @@ class MCPServerManager: ): liveness_status, liveness_error = await self._check_mcp_liveness(server.url) return self._build_mcp_server_table(server).model_copy( - update={ - "status": liveness_status, - "health_check_error": liveness_error, - "health_check_type": "liveness", - "last_health_check": datetime.now(), - } + update=MappingProxyType( + { + "status": liveness_status, + "health_check_error": liveness_error, + "health_check_type": "liveness", + "last_health_check": datetime.now(), + } + ) ) if not should_skip_health_check: diff --git a/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py index d2e622b0a8e..eb5ea57c6b3 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_mcp_management_endpoints.py @@ -725,6 +725,7 @@ class TestListMCPServers: # Mock health check result as LiteLLM_MCPServerTable mock_health_result = generate_mock_mcp_server_db_record(server_id="server-1", alias="Server 1") mock_health_result.status = "healthy" + mock_health_result.health_check_type = "liveness" mock_health_result.last_health_check = datetime.now() mock_health_result.health_check_error = None @@ -762,6 +763,7 @@ class TestListMCPServers: assert result.credentials is None assert mock_server.credentials == {"auth_value": "top-secret"} assert result.status == "healthy" + assert result.health_check_type == "liveness" @pytest.mark.asyncio async def test_fetch_single_mcp_server_preserves_upstream_resource_for_admin(self): diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPServerCard.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPServerCard.tsx index b347044c2ed..13505665dec 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPServerCard.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPServerCard.tsx @@ -360,7 +360,7 @@ const HealthChip: FC = ({ } /> - +
Health: {label}
{healthCheckType === "liveness" && (
Authentication and tools were not checked