Skip to main content
Glama

Token Saver MCP

by jerry426
browser-launcher.ts6.05 kB
import type { ChildProcess } from 'node:child_process' import { spawn } from 'node:child_process' import * as fs from 'node:fs' import * as os from 'node:os' import * as path from 'node:path' // Simple logger for browser launcher const logger = { info: (msg: string, ...args: any[]) => console.error(`[BrowserLauncher] ${msg}`, ...args), error: (msg: string, ...args: any[]) => console.error(`[BrowserLauncher ERROR] ${msg}`, ...args), warn: (msg: string, ...args: any[]) => console.warn(`[BrowserLauncher WARN] ${msg}`, ...args), } /** * Browser Launcher for Chrome DevTools Protocol * Manages launching Edge/Chrome with remote debugging enabled * (Edge prioritized for better performance) */ export interface BrowserLaunchOptions { port?: number headless?: boolean userDataDir?: string url?: string additionalArgs?: string[] } export class BrowserLauncher { private browserProcess: ChildProcess | null = null private debugPort: number private userDataDir: string constructor(private options: BrowserLaunchOptions = {}) { this.debugPort = options.port || 9222 this.userDataDir = options.userDataDir || path.join(os.tmpdir(), 'token-saver-mcp-chrome') } /** * Find the Chrome/Edge executable path based on platform */ private findBrowserPath(): string { const platform = process.platform // Common browser paths by platform (Edge prioritized for better performance) const paths: { [key: string]: string[] } = { darwin: [ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge', '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium', ], win32: [ 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe', 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe', 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', ], linux: [ '/usr/bin/microsoft-edge', '/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium', ], } const platformPaths = paths[platform] || [] // Find first existing browser for (const browserPath of platformPaths) { if (fs.existsSync(browserPath)) { logger.info(`Found browser at: ${browserPath}`) return browserPath } } // Fallback to hoping it's in PATH (prefer Edge) if (platform === 'win32') { return 'msedge.exe' } return 'microsoft-edge' } /** * Launch browser with debugging enabled */ async launch(): Promise<void> { if (this.browserProcess) { logger.warn('Browser already launched') return } const browserPath = this.findBrowserPath() // Ensure user data directory exists if (!fs.existsSync(this.userDataDir)) { fs.mkdirSync(this.userDataDir, { recursive: true }) } // Build launch arguments const args = [ `--remote-debugging-port=${this.debugPort}`, `--user-data-dir=${this.userDataDir}`, '--no-first-run', '--no-default-browser-check', ] // Add headless mode if requested if (this.options.headless) { args.push('--headless=new') } // Add any additional arguments if (this.options.additionalArgs) { args.push(...this.options.additionalArgs) } // Add URL to open if (this.options.url) { args.push(this.options.url) } logger.info(`Launching browser: ${browserPath}`) logger.info(`Debug port: ${this.debugPort}`) logger.info(`User data dir: ${this.userDataDir}`) // Launch the browser this.browserProcess = spawn(browserPath, args, { detached: false, stdio: 'pipe', }) this.browserProcess.on('error', (error) => { logger.error('Failed to launch browser:', error) this.browserProcess = null }) this.browserProcess.on('exit', (code, signal) => { logger.info(`Browser exited with code ${code} and signal ${signal}`) this.browserProcess = null }) // Give browser time to start await this.waitForDebugger() } /** * Wait for debugger to be ready */ private async waitForDebugger(maxAttempts = 30): Promise<void> { for (let i = 0; i < maxAttempts; i++) { try { // Try to fetch the version info from debugger const response = await fetch(`http://localhost:${this.debugPort}/json/version`) if (response.ok) { const info = await response.json() as any logger.info(`Browser debugger ready: ${info.Browser}`) return } } catch (e) { // Not ready yet } // Wait 100ms before next attempt await new Promise(resolve => setTimeout(resolve, 100)) } throw new Error('Browser debugger failed to start after 3 seconds') } /** * Get the debug port */ getPort(): number { return this.debugPort } /** * Check if browser is running */ isRunning(): boolean { return this.browserProcess !== null && !this.browserProcess.killed } /** * Kill the browser process */ async kill(): Promise<void> { if (this.browserProcess) { logger.info('Killing browser process') this.browserProcess.kill('SIGTERM') // Give it time to close gracefully await new Promise(resolve => setTimeout(resolve, 500)) // Force kill if still running if (this.browserProcess && !this.browserProcess.killed) { this.browserProcess.kill('SIGKILL') } this.browserProcess = null } } /** * Get list of available tabs/pages */ async getTargets(): Promise<any[]> { try { const response = await fetch(`http://localhost:${this.debugPort}/json/list`) if (response.ok) { return await response.json() as any[] } } catch (error) { logger.error('Failed to get browser targets:', error) } return [] } }

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/jerry426/token-saver-mcp'

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