mirror of
https://github.com/hangwin/mcp-chrome.git
synced 2026-08-30 18:02:01 +08:00
fix: install script permission fix
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"description": "a chrome extension to use your own chrome as a mcp server",
|
||||
"author": "hangye",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
|
||||
@@ -229,23 +229,77 @@ manifest.json
|
||||
- 用户级别安装需要对用户目录有写入权限
|
||||
- 系统级别安装需要管理员/root权限
|
||||
|
||||
3. **修复执行权限问题(macOS/Linux)**
|
||||
3. **修复执行权限问题**
|
||||
|
||||
- 如果遇到 "Failed to start native messaging host."" 错误
|
||||
- 运行以下命令修复执行权限:
|
||||
**macOS/Linux 平台**:
|
||||
|
||||
**问题描述**:
|
||||
|
||||
- npm 安装通常会保留文件权限,但 pnpm 可能不会
|
||||
- 可能遇到 "Permission denied" 或 "Native host has exited" 错误
|
||||
- Chrome 扩展无法启动 native host 进程
|
||||
|
||||
**解决方案**:
|
||||
|
||||
a) **使用内置修复命令(推荐)**:
|
||||
|
||||
```bash
|
||||
chrome-mcp-bridge fix-permissions
|
||||
```
|
||||
|
||||
- 或者手动设置权限:
|
||||
b) **手动设置权限**:
|
||||
|
||||
```bash
|
||||
# 查找安装路径
|
||||
npm list -g chrome-mcp-bridge
|
||||
# 或者对于 pnpm
|
||||
pnpm list -g chrome-mcp-bridge
|
||||
|
||||
# 设置执行权限(替换为实际路径)
|
||||
chmod +x /path/to/node_modules/chrome-mcp-bridge/run_host.sh
|
||||
chmod +x /path/to/node_modules/chrome-mcp-bridge/index.js
|
||||
chmod +x /path/to/node_modules/chrome-mcp-bridge/cli.js
|
||||
```
|
||||
|
||||
**Windows 平台**:
|
||||
|
||||
**问题描述**:
|
||||
|
||||
- Windows 上 `.bat` 文件通常不需要执行权限,但可能遇到其他问题
|
||||
- 文件可能被标记为只读
|
||||
- 可能遇到 "Access denied" 或文件无法执行的错误
|
||||
|
||||
**解决方案**:
|
||||
|
||||
a) **使用内置修复命令(推荐)**:
|
||||
|
||||
```cmd
|
||||
chrome-mcp-bridge fix-permissions
|
||||
```
|
||||
|
||||
b) **手动检查文件属性**:
|
||||
|
||||
```cmd
|
||||
# 查找安装路径
|
||||
npm list -g chrome-mcp-bridge
|
||||
|
||||
# 检查文件属性(在文件资源管理器中右键 -> 属性)
|
||||
# 确保 run_host.bat 不是只读文件
|
||||
```
|
||||
|
||||
c) **重新安装并强制权限**:
|
||||
|
||||
```bash
|
||||
# 卸载
|
||||
npm uninstall -g chrome-mcp-bridge
|
||||
# 或 pnpm uninstall -g chrome-mcp-bridge
|
||||
|
||||
# 重新安装
|
||||
npm install -g chrome-mcp-bridge
|
||||
# 或 pnpm install -g chrome-mcp-bridge
|
||||
|
||||
# 如果仍有问题,运行权限修复
|
||||
chrome-mcp-bridge fix-permissions
|
||||
```
|
||||
|
||||
4. 在 Windows 上,确保注册表访问没有被限制
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mcp-chrome-bridge",
|
||||
"version": "1.0.9",
|
||||
"version": "1.0.10",
|
||||
"description": "Chrome Native-Messaging host (Node)",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
|
||||
@@ -8,43 +8,130 @@ import { colorText, tryRegisterUserLevelHost } from './utils';
|
||||
|
||||
// Check if this script is run directly
|
||||
const isDirectRun = require.main === module;
|
||||
const isGlobalInstall = process.env.npm_config_global === 'true';
|
||||
|
||||
// Detect global installation for both npm and pnpm
|
||||
function detectGlobalInstall(): boolean {
|
||||
// npm uses npm_config_global
|
||||
if (process.env.npm_config_global === 'true') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// pnpm detection methods
|
||||
// Method 1: Check if PNPM_HOME is set and current path contains it
|
||||
if (process.env.PNPM_HOME && __dirname.includes(process.env.PNPM_HOME)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 2: Check if we're in a global pnpm directory structure
|
||||
// pnpm global packages are typically installed in ~/.local/share/pnpm/global/5/node_modules
|
||||
const globalPnpmPatterns = ['/pnpm/global/', '/.local/share/pnpm/', '/pnpm-global/'];
|
||||
|
||||
if (globalPnpmPatterns.some((pattern) => __dirname.includes(pattern))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 3: Check npm_config_prefix for pnpm
|
||||
if (process.env.npm_config_prefix && __dirname.includes(process.env.npm_config_prefix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const isGlobalInstall = detectGlobalInstall();
|
||||
|
||||
/**
|
||||
* 尝试注册Native Messaging主机
|
||||
*/
|
||||
/**
|
||||
* 确保执行权限(无论是否为全局安装)
|
||||
*/
|
||||
async function ensureExecutionPermissions(): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows 平台处理
|
||||
await ensureWindowsFilePermissions();
|
||||
return;
|
||||
}
|
||||
|
||||
// Unix/Linux 平台处理
|
||||
const filesToCheck = [
|
||||
path.join(__dirname, '..', 'index.js'),
|
||||
path.join(__dirname, '..', 'run_host.sh'),
|
||||
path.join(__dirname, '..', '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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows 平台文件权限处理
|
||||
*/
|
||||
async function ensureWindowsFilePermissions(): Promise<void> {
|
||||
const filesToCheck = [
|
||||
path.join(__dirname, '..', 'index.js'),
|
||||
path.join(__dirname, '..', 'run_host.bat'),
|
||||
path.join(__dirname, '..', '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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function tryRegisterNativeHost(): Promise<void> {
|
||||
try {
|
||||
console.log(colorText('Attempting to register Chrome Native Messaging host...', 'blue'));
|
||||
|
||||
// Always ensure execution permissions, regardless of installation type
|
||||
await ensureExecutionPermissions();
|
||||
|
||||
if (isGlobalInstall) {
|
||||
// 1. Ensure native host has execution permissions
|
||||
const launcherPath = path.join(__dirname, '..', 'index.js');
|
||||
const wrapperScriptPath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
process.platform === 'win32' ? 'run_host.bat' : 'run_host.sh',
|
||||
);
|
||||
|
||||
try {
|
||||
// Set execution permissions on non-Windows platforms
|
||||
if (process.platform !== 'win32') {
|
||||
fs.chmodSync(launcherPath, '755');
|
||||
console.log('✓ Set launcher execution permissions');
|
||||
|
||||
// Also set execution permissions for the wrapper script
|
||||
if (fs.existsSync(wrapperScriptPath)) {
|
||||
fs.chmodSync(wrapperScriptPath, '755');
|
||||
console.log('✓ Set wrapper script execution permissions');
|
||||
} else {
|
||||
console.warn('⚠️ Wrapper script not found:', wrapperScriptPath);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.warn('⚠️ Unable to set execution permissions:', err.message);
|
||||
// Non-critical error, don't block
|
||||
}
|
||||
|
||||
// First try user-level installation (no elevated permissions required)
|
||||
const userLevelSuccess = await tryRegisterUserLevelHost();
|
||||
|
||||
@@ -141,6 +228,17 @@ function printManualInstructions(): void {
|
||||
async function main(): Promise<void> {
|
||||
console.log(colorText(`Installing ${COMMAND_NAME}...`, 'green'));
|
||||
|
||||
// Debug information
|
||||
console.log(colorText('Installation environment debug info:', 'blue'));
|
||||
console.log(` __dirname: ${__dirname}`);
|
||||
console.log(` npm_config_global: ${process.env.npm_config_global}`);
|
||||
console.log(` PNPM_HOME: ${process.env.PNPM_HOME}`);
|
||||
console.log(` npm_config_prefix: ${process.env.npm_config_prefix}`);
|
||||
console.log(` isGlobalInstall: ${isGlobalInstall}`);
|
||||
|
||||
// Always ensure execution permissions first
|
||||
await ensureExecutionPermissions();
|
||||
|
||||
// If global installation, try automatic registration
|
||||
if (isGlobalInstall) {
|
||||
await tryRegisterNativeHost();
|
||||
|
||||
@@ -101,13 +101,16 @@ export async function getMainPath(): Promise<string> {
|
||||
* 确保关键文件具有执行权限
|
||||
*/
|
||||
export async function ensureExecutionPermissions(): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows 平台不需要设置执行权限
|
||||
return;
|
||||
}
|
||||
|
||||
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'),
|
||||
@@ -138,6 +141,49 @@ export async function ensureExecutionPermissions(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user