Skip to main content
Glama

Lighthouse MCP

by mizchi
browserPool.ts5.12 kB
/** * ブラウザプール管理 */ import { mkdirSync, rmSync } from 'fs'; import { join, resolve } from 'path'; import { cwd } from 'process'; import puppeteer, { Browser } from 'puppeteer'; /** * ブラウザプールの管理クラス */ export class BrowserPool { private browsers: Browser[] = []; private maxBrowsers: number; private availableBrowsers: Browser[] = []; private userDataBaseDir: string; private userDataDirs: Set<string> = new Set(); private browserIndex = 0; constructor(maxBrowsers: number = 5, userDataDir?: string) { this.maxBrowsers = maxBrowsers; // user-data-dirのベースディレクトリを決定 if (userDataDir) { // 明示的に指定された場合はそれを使用 this.userDataBaseDir = resolve(userDataDir); } else if (process.env.LIGHTHOUSE_USER_DATA_DIR) { // 環境変数で指定された場合 this.userDataBaseDir = resolve(process.env.LIGHTHOUSE_USER_DATA_DIR); } else { // デフォルトは現在のディレクトリの.lhdata this.userDataBaseDir = join(cwd(), '.lhdata'); } try { mkdirSync(this.userDataBaseDir, { recursive: true }); } catch { // ディレクトリが既に存在する場合は無視 } // プロセス終了時にクリーンアップ(既にリスナーがある場合は設定しない) if (process.listenerCount('exit') === 0) { process.on('exit', () => this.closeAll()); } if (process.listenerCount('SIGINT') === 0) { process.on('SIGINT', () => this.closeAll()); } } async getBrowser(): Promise<Browser> { // 利用可能なブラウザがあれば再利用 if (this.availableBrowsers.length > 0) { return this.availableBrowsers.pop()!; } // 最大数に達していない場合は新しいブラウザを作成 if (this.browsers.length < this.maxBrowsers) { // 一意のuser-data-dirを作成 const userDataDir = join(this.userDataBaseDir, `browser-${this.browserIndex++}`); mkdirSync(userDataDir, { recursive: true }); this.userDataDirs.add(userDataDir); // WSL環境向けの追加設定 const browser = await puppeteer.launch({ headless: true, userDataDir: userDataDir, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--no-first-run', '--disable-extensions', '--disable-breakpad', '--disable-crash-reporter', `--user-data-dir=${userDataDir}`, // WSL環境向け追加設定 '--disable-features=TranslateUI', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-features=site-per-process', '--force-color-profile=srgb', '--metrics-recording-only', ], // WSL環境向けタイムアウト設定 timeout: 60000, protocolTimeout: 60000, }); this.browsers.push(browser); return browser; } // 最大数に達している場合は利用可能になるまで待機 return new Promise((resolve) => { const checkInterval = setInterval(() => { if (this.availableBrowsers.length > 0) { clearInterval(checkInterval); resolve(this.availableBrowsers.pop()!); } }, 100); }); } async releaseBrowser(browser: Browser): Promise<void> { // ページをすべて閉じる const pages = await browser.pages(); for (const page of pages) { if (page.url() !== 'about:blank') { await page.close(); } } // 再利用のためにプールに戻す this.availableBrowsers.push(browser); } async closeAll(): Promise<void> { const closePromises = this.browsers.map(browser => browser.close()); await Promise.all(closePromises); this.browsers = []; this.availableBrowsers = []; // user-data-dirをクリーンアップ for (const dir of this.userDataDirs) { try { rmSync(dir, { recursive: true, force: true }); } catch { // クリーンアップエラーは無視 } } this.userDataDirs.clear(); } getActiveCount(): number { return this.browsers.length - this.availableBrowsers.length; } getTotalCount(): number { return this.browsers.length; } } // シングルトンインスタンス let browserPoolInstance: BrowserPool | null = null; /** * シングルトンのBrowserPoolインスタンスを取得 */ export function getBrowserPool(maxBrowsers: number = 5, userDataDir?: string): BrowserPool { if (!browserPoolInstance) { browserPoolInstance = new BrowserPool(maxBrowsers, userDataDir); } return browserPoolInstance; } /** * BrowserPoolをリセット(主にテスト用) */ export async function resetBrowserPool(): Promise<void> { if (browserPoolInstance) { await browserPoolInstance.closeAll(); browserPoolInstance = null; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mizchi/lighthouse-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server