Skip to main content
Glama

Chrome MCP Server

utils.ts14.4 kB
import fs from 'fs'; import path from 'path'; import os from 'os'; import { execSync } from 'child_process'; import { promisify } from 'util'; import { COMMAND_NAME, DESCRIPTION, EXTENSION_ID, HOST_NAME } from './constant'; export const access = promisify(fs.access); export const mkdir = promisify(fs.mkdir); export const writeFile = promisify(fs.writeFile); /** * 打印彩色文本 */ export function colorText(text: string, color: string): string { const colors: Record<string, string> = { red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', reset: '\x1b[0m', }; return colors[color] + text + colors.reset; } /** * Get user-level manifest file path */ export function getUserManifestPath(): string { if (os.platform() === 'win32') { // Windows: %APPDATA%\Google\Chrome\NativeMessagingHosts\ return path.join( process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Google', 'Chrome', 'NativeMessagingHosts', `${HOST_NAME}.json`, ); } else if (os.platform() === 'darwin') { // macOS: ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/ return path.join( os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome', 'NativeMessagingHosts', `${HOST_NAME}.json`, ); } else { // Linux: ~/.config/google-chrome/NativeMessagingHosts/ return path.join( os.homedir(), '.config', 'google-chrome', 'NativeMessagingHosts', `${HOST_NAME}.json`, ); } } /** * Get system-level manifest file path */ export function getSystemManifestPath(): string { if (os.platform() === 'win32') { // Windows: %ProgramFiles%\Google\Chrome\NativeMessagingHosts\ return path.join( process.env.ProgramFiles || 'C:\\Program Files', 'Google', 'Chrome', 'NativeMessagingHosts', `${HOST_NAME}.json`, ); } else if (os.platform() === 'darwin') { // macOS: /Library/Google/Chrome/NativeMessagingHosts/ return path.join('/Library', 'Google', 'Chrome', 'NativeMessagingHosts', `${HOST_NAME}.json`); } else { // Linux: /etc/opt/chrome/native-messaging-hosts/ return path.join('/etc', 'opt', 'chrome', 'native-messaging-hosts', `${HOST_NAME}.json`); } } /** * Get native host startup script file path */ export async function getMainPath(): Promise<string> { try { const packageDistDir = path.join(__dirname, '..'); const wrapperScriptName = process.platform === 'win32' ? 'run_host.bat' : 'run_host.sh'; const absoluteWrapperPath = path.resolve(packageDistDir, wrapperScriptName); return absoluteWrapperPath; } catch (error) { console.log(colorText('Cannot find global package path, using current directory', 'yellow')); throw error; } } /** * 确保关键文件具有执行权限 */ export async function ensureExecutionPermissions(): Promise<void> { try { const packageDistDir = path.join(__dirname, '..'); if (process.platform === 'win32') { // Windows 平台处理 await ensureWindowsFilePermissions(packageDistDir); return; } // Unix/Linux 平台处理 const filesToCheck = [ path.join(packageDistDir, 'index.js'), path.join(packageDistDir, 'run_host.sh'), path.join(packageDistDir, 'cli.js'), ]; for (const filePath of filesToCheck) { if (fs.existsSync(filePath)) { try { fs.chmodSync(filePath, '755'); console.log( colorText(`✓ Set execution permissions for ${path.basename(filePath)}`, 'green'), ); } catch (err: any) { console.warn( colorText( `⚠️ Unable to set execution permissions for ${path.basename(filePath)}: ${err.message}`, 'yellow', ), ); } } else { console.warn(colorText(`⚠️ File not found: ${filePath}`, 'yellow')); } } } catch (error: any) { console.warn(colorText(`⚠️ Error ensuring execution permissions: ${error.message}`, 'yellow')); } } /** * Windows 平台文件权限处理 */ async function ensureWindowsFilePermissions(packageDistDir: string): Promise<void> { const filesToCheck = [ path.join(packageDistDir, 'index.js'), path.join(packageDistDir, 'run_host.bat'), path.join(packageDistDir, 'cli.js'), ]; for (const filePath of filesToCheck) { if (fs.existsSync(filePath)) { try { // 检查文件是否为只读,如果是则移除只读属性 const stats = fs.statSync(filePath); if (!(stats.mode & parseInt('200', 8))) { // 检查写权限 // 尝试移除只读属性 fs.chmodSync(filePath, stats.mode | parseInt('200', 8)); console.log( colorText(`✓ Removed read-only attribute from ${path.basename(filePath)}`, 'green'), ); } // 验证文件可读性 fs.accessSync(filePath, fs.constants.R_OK); console.log( colorText(`✓ Verified file accessibility for ${path.basename(filePath)}`, 'green'), ); } catch (err: any) { console.warn( colorText( `⚠️ Unable to verify file permissions for ${path.basename(filePath)}: ${err.message}`, 'yellow', ), ); } } else { console.warn(colorText(`⚠️ File not found: ${filePath}`, 'yellow')); } } } /** * Create Native Messaging host manifest content */ export async function createManifestContent(): Promise<any> { const mainPath = await getMainPath(); return { name: HOST_NAME, description: DESCRIPTION, path: mainPath, // Node.js可执行文件路径 type: 'stdio', allowed_origins: [`chrome-extension://${EXTENSION_ID}/`], }; } /** * 验证Windows注册表项是否存在 */ function verifyWindowsRegistryEntry(registryKey: string, expectedPath: string): boolean { if (os.platform() !== 'win32') { return true; // 非Windows平台跳过验证 } try { const result = execSync(`reg query "${registryKey}" /ve`, { encoding: 'utf8', stdio: 'pipe' }); const lines = result.split('\n'); for (const line of lines) { if (line.includes('REG_SZ') && line.includes(expectedPath.replace(/\\/g, '\\\\'))) { return true; } } return false; } catch (error) { return false; } } /** * 尝试注册用户级别的Native Messaging主机 */ export async function tryRegisterUserLevelHost(): Promise<boolean> { try { console.log(colorText('Attempting to register user-level Native Messaging host...', 'blue')); // 1. 确保执行权限 await ensureExecutionPermissions(); // 2. 确定清单文件路径 const manifestPath = getUserManifestPath(); // 3. 确保目录存在 await mkdir(path.dirname(manifestPath), { recursive: true }); // 4. 创建清单内容 const manifest = await createManifestContent(); console.log('manifest path==>', manifest, manifestPath); // 5. 写入清单文件 await writeFile(manifestPath, JSON.stringify(manifest, null, 2)); if (os.platform() === 'win32') { const registryKey = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`; try { // 确保路径使用正确的转义格式 const escapedPath = manifestPath.replace(/\\/g, '\\\\'); const regCommand = `reg add "${registryKey}" /ve /t REG_SZ /d "${escapedPath}" /f`; console.log(colorText(`Executing registry command: ${regCommand}`, 'blue')); execSync(regCommand, { stdio: 'pipe' }); // 验证注册表项是否创建成功 if (verifyWindowsRegistryEntry(registryKey, manifestPath)) { console.log(colorText('✓ Successfully created Windows registry entry', 'green')); } else { console.log(colorText('⚠️ Registry entry created but verification failed', 'yellow')); } } catch (error: any) { console.log( colorText(`⚠️ Unable to create Windows registry entry: ${error.message}`, 'yellow'), ); console.log(colorText(`Registry key: ${registryKey}`, 'yellow')); console.log(colorText(`Manifest path: ${manifestPath}`, 'yellow')); return false; // Windows上如果注册表项创建失败,整个注册过程应该视为失败 } } console.log(colorText('Successfully registered user-level Native Messaging host!', 'green')); return true; } catch (error) { console.log( colorText( `User-level registration failed: ${error instanceof Error ? error.message : String(error)}`, 'yellow', ), ); return false; } } // 导入is-admin包(仅在Windows平台使用) let isAdmin: () => boolean = () => false; if (process.platform === 'win32') { try { isAdmin = require('is-admin'); } catch (error) { console.warn('缺少is-admin依赖,Windows平台下可能无法正确检测管理员权限'); console.warn(error); } } /** * 使用提升权限注册系统级清单 */ export async function registerWithElevatedPermissions(): Promise<void> { try { console.log(colorText('Attempting to register system-level manifest...', 'blue')); // 1. 确保执行权限 await ensureExecutionPermissions(); // 2. 准备清单内容 const manifest = await createManifestContent(); // 3. 获取系统级清单路径 const manifestPath = getSystemManifestPath(); // 4. 创建临时清单文件 const tempManifestPath = path.join(os.tmpdir(), `${HOST_NAME}.json`); await writeFile(tempManifestPath, JSON.stringify(manifest, null, 2)); // 5. 检测是否已经有管理员权限 const isRoot = process.getuid && process.getuid() === 0; // Unix/Linux/Mac const hasAdminRights = process.platform === 'win32' ? isAdmin() : false; // Windows平台检测管理员权限 const hasElevatedPermissions = isRoot || hasAdminRights; // 准备命令 const command = os.platform() === 'win32' ? `if not exist "${path.dirname(manifestPath)}" mkdir "${path.dirname(manifestPath)}" && copy "${tempManifestPath}" "${manifestPath}"` : `mkdir -p "${path.dirname(manifestPath)}" && cp "${tempManifestPath}" "${manifestPath}" && chmod 644 "${manifestPath}"`; if (hasElevatedPermissions) { // 已经有管理员权限,直接执行命令 try { // 创建目录 if (!fs.existsSync(path.dirname(manifestPath))) { fs.mkdirSync(path.dirname(manifestPath), { recursive: true }); } // 复制文件 fs.copyFileSync(tempManifestPath, manifestPath); // 设置权限(非Windows平台) if (os.platform() !== 'win32') { fs.chmodSync(manifestPath, '644'); } console.log(colorText('System-level manifest registration successful!', 'green')); } catch (error: any) { console.error( colorText(`System-level manifest installation failed: ${error.message}`, 'red'), ); throw error; } } else { // 没有管理员权限,打印手动操作提示 console.log( colorText('⚠️ Administrator privileges required for system-level installation', 'yellow'), ); console.log( colorText( 'Please run one of the following commands with administrator privileges:', 'blue', ), ); if (os.platform() === 'win32') { console.log(colorText(' 1. Open Command Prompt as Administrator and run:', 'blue')); console.log(colorText(` ${command}`, 'cyan')); } else { console.log(colorText(' 1. Run with sudo:', 'blue')); console.log(colorText(` sudo ${command}`, 'cyan')); } console.log( colorText(' 2. Or run the registration command with elevated privileges:', 'blue'), ); console.log(colorText(` sudo ${COMMAND_NAME} register --system`, 'cyan')); throw new Error('Administrator privileges required for system-level installation'); } // 6. Windows特殊处理 - 设置系统级注册表 if (os.platform() === 'win32') { const registryKey = `HKLM\\Software\\Google\\Chrome\\NativeMessagingHosts\\${HOST_NAME}`; // 确保路径使用正确的转义格式 const escapedPath = manifestPath.replace(/\\/g, '\\\\'); const regCommand = `reg add "${registryKey}" /ve /t REG_SZ /d "${escapedPath}" /f`; console.log(colorText(`Creating system registry entry: ${registryKey}`, 'blue')); console.log(colorText(`Manifest path: ${manifestPath}`, 'blue')); if (hasElevatedPermissions) { // 已经有管理员权限,直接执行注册表命令 try { execSync(regCommand, { stdio: 'pipe' }); // 验证注册表项是否创建成功 if (verifyWindowsRegistryEntry(registryKey, manifestPath)) { console.log(colorText('Windows registry entry created successfully!', 'green')); } else { console.log(colorText('⚠️ Registry entry created but verification failed', 'yellow')); } } catch (error: any) { console.error( colorText(`Windows registry entry creation failed: ${error.message}`, 'red'), ); console.error(colorText(`Command: ${regCommand}`, 'red')); throw error; } } else { // 没有管理员权限,打印手动操作提示 console.log( colorText( '⚠️ Administrator privileges required for Windows registry modification', 'yellow', ), ); console.log(colorText('Please run the following command as Administrator:', 'blue')); console.log(colorText(` ${regCommand}`, 'cyan')); console.log(colorText('Or run the registration command with elevated privileges:', 'blue')); console.log( colorText( ` Run Command Prompt as Administrator and execute: ${COMMAND_NAME} register --system`, 'cyan', ), ); throw new Error('Administrator privileges required for Windows registry modification'); } } } catch (error: any) { console.error(colorText(`注册失败: ${error.message}`, 'red')); throw error; } }

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/marie6789040106650/mcp-chrome-bk'

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