@mearl/mcp-server
Advanced tools
+11
-1
@@ -8,5 +8,15 @@ #!/usr/bin/env node | ||
| * mearl-mcp-configure --extension-id=YOUR_ID # 直接指定自定义 Extension ID | ||
| * mearl-mcp-configure --yes # 配置所有已检测客户端,不询问 | ||
| * mearl-mcp-configure --skip-mcp # 跳过 MCP 配置 | ||
| * mearl-mcp-configure --get # 查看当前配置 | ||
| */ | ||
| export {}; | ||
| export interface ConfigureArgs { | ||
| extensionId?: string; | ||
| get: boolean; | ||
| globalInstall: boolean; | ||
| skipMcp: boolean; | ||
| yes: boolean; | ||
| } | ||
| export declare function parseConfigureArgs(args?: string[]): ConfigureArgs; | ||
| export declare function shouldUseNpx(globalInstall: boolean, detectNpxMode?: () => boolean): boolean; | ||
| export declare function runConfigure(argv?: string[]): Promise<void>; |
+98
-53
@@ -8,2 +8,3 @@ #!/usr/bin/env node | ||
| * mearl-mcp-configure --extension-id=YOUR_ID # 直接指定自定义 Extension ID | ||
| * mearl-mcp-configure --yes # 配置所有已检测客户端,不询问 | ||
| * mearl-mcp-configure --skip-mcp # 跳过 MCP 配置 | ||
@@ -14,3 +15,4 @@ * mearl-mcp-configure --get # 查看当前配置 | ||
| import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; | ||
| import { join, dirname } from 'path'; | ||
| import path, { dirname, join } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import { HOST_NAME, DEFAULT_EXTENSION_ID } from './constants.js'; | ||
@@ -24,7 +26,8 @@ import prompts from 'prompts'; | ||
| // 解析命令行参数 | ||
| function parseArgs() { | ||
| const args = process.argv.slice(2); | ||
| export function parseConfigureArgs(args = process.argv.slice(2)) { | ||
| let extensionId; | ||
| let skipMcp = false; | ||
| let get = false; | ||
| let globalInstall = false; | ||
| let yes = false; | ||
| for (const arg of args) { | ||
@@ -43,4 +46,10 @@ if (arg.startsWith('--extension-id=')) { | ||
| } | ||
| else if (arg === '--global-install') { | ||
| globalInstall = true; | ||
| } | ||
| else if (arg === '--yes' || arg === '-y' || arg === '--non-interactive') { | ||
| yes = true; | ||
| } | ||
| } | ||
| return { extensionId, skipMcp, get }; | ||
| return { extensionId, skipMcp, get, globalInstall, yes }; | ||
| } | ||
@@ -205,6 +214,7 @@ // 替换或追加 TOML 中的 mcp_servers.xxx 节 | ||
| } | ||
| async function main() { | ||
| const parseResult = parseArgs(); | ||
| const { skipMcp, get } = parseResult; | ||
| const extensionId = parseResult.extensionId; | ||
| export function shouldUseNpx(globalInstall, detectNpxMode = isNpxMode) { | ||
| return !globalInstall && detectNpxMode(); | ||
| } | ||
| export async function runConfigure(argv = process.argv.slice(2)) { | ||
| const { extensionId, get, globalInstall, skipMcp, yes } = parseConfigureArgs(argv); | ||
| // 如果是查看配置,显示后退出 | ||
@@ -217,6 +227,9 @@ if (get) { | ||
| // 检测 npx 模式 | ||
| const useNpx = isNpxMode(); | ||
| const useNpx = shouldUseNpx(globalInstall); | ||
| if (useNpx) { | ||
| log('检测到 npx 模式,将生成 npx 配置'); | ||
| } | ||
| else if (globalInstall) { | ||
| log('使用全局 mearl-mcp-server 命令生成配置'); | ||
| } | ||
| // 配置 Native Messaging Host | ||
@@ -226,3 +239,3 @@ console.log('\n📦 配置 Chrome Native Messaging Host...'); | ||
| // the same time (they share the same Unix socket and handlers). | ||
| await installNativeHost({ | ||
| const nativeHostInstalled = await installNativeHost({ | ||
| mode: 'both', | ||
@@ -232,2 +245,5 @@ extensionId: extensionId || process.env.MEARL_EXTENSION_ID || DEFAULT_EXTENSION_ID, | ||
| }); | ||
| if (!nativeHostInstalled) { | ||
| throw new Error('Native Messaging Host 配置失败'); | ||
| } | ||
| console.log('✅ 配置完成\n'); | ||
@@ -239,49 +255,72 @@ // 配置 MCP 客户端 | ||
| else { | ||
| const response = await prompts({ | ||
| type: 'confirm', | ||
| name: 'setupMcp', | ||
| message: '配置 MCP 客户端?', | ||
| initial: true, | ||
| // 列出全部支持的客户端,已检测到安装的默认预选 | ||
| const allClients = Object.entries(MCP_CLIENTS).map(([key, client]) => { | ||
| const selected = isClientInstalled(key); | ||
| return { | ||
| title: selected ? `${client.name} ✓` : client.name, | ||
| value: key, | ||
| selected, | ||
| }; | ||
| }); | ||
| if (response.setupMcp) { | ||
| // 列出全部支持的客户端,已检测到安装的默认预选 | ||
| const allClients = Object.entries(MCP_CLIENTS).map(([key, client]) => ({ | ||
| title: isClientInstalled(key) ? `${client.name} ✓` : client.name, | ||
| value: key, | ||
| selected: isClientInstalled(key), | ||
| })); | ||
| const detectedNames = allClients.filter(c => c.selected).map(c => c.title); | ||
| if (detectedNames.length > 0) { | ||
| console.log(`\n已检测到: ${detectedNames.join(', ')}\n`); | ||
| const detectedClients = allClients.filter(client => client.selected); | ||
| const detectedNames = detectedClients.map(client => client.title); | ||
| let selectedClients; | ||
| if (yes) { | ||
| if (detectedClients.length === 0) { | ||
| throw new Error('未检测到可配置的 MCP 客户端;请交互式运行 mearl-mcp-configure 进行选择'); | ||
| } | ||
| else { | ||
| console.log('\n未检测到已安装的客户端,可手动选择\n'); | ||
| selectedClients = detectedClients.map(client => client.value); | ||
| console.log(`\n将配置已检测到的客户端: ${detectedNames.join(', ')}\n`); | ||
| } | ||
| else { | ||
| const response = await prompts({ | ||
| type: 'confirm', | ||
| name: 'setupMcp', | ||
| message: '配置 MCP 客户端?', | ||
| initial: true, | ||
| }); | ||
| if (response.setupMcp === undefined) { | ||
| throw new Error('MCP 客户端配置已取消'); | ||
| } | ||
| const clientResponse = await prompts({ | ||
| type: 'multiselect', | ||
| name: 'clients', | ||
| message: '选择要配置的客户端:', | ||
| choices: allClients, | ||
| instructions: false, | ||
| hint: '空格选择,回车确认', | ||
| }); | ||
| if (clientResponse.clients && clientResponse.clients.length > 0) { | ||
| console.log(''); | ||
| for (const clientKey of clientResponse.clients) { | ||
| const client = MCP_CLIENTS[clientKey]; | ||
| if (updateMCPConfig(client.configPath, client, useNpx)) { | ||
| console.log(`✅ ${client.name}`); | ||
| } | ||
| else { | ||
| console.log(`❌ ${client.name} 配置失败`); | ||
| } | ||
| if (response.setupMcp) { | ||
| if (detectedNames.length > 0) { | ||
| console.log(`\n已检测到: ${detectedNames.join(', ')}\n`); | ||
| } | ||
| console.log(''); | ||
| else { | ||
| console.log('\n未检测到已安装的客户端,可手动选择\n'); | ||
| } | ||
| const clientResponse = await prompts({ | ||
| type: 'multiselect', | ||
| name: 'clients', | ||
| message: '选择要配置的客户端:', | ||
| choices: allClients, | ||
| instructions: false, | ||
| hint: '空格选择,回车确认', | ||
| }); | ||
| selectedClients = clientResponse.clients; | ||
| if (!selectedClients || selectedClients.length === 0) { | ||
| throw new Error('未选择要配置的 MCP 客户端'); | ||
| } | ||
| } | ||
| else { | ||
| console.log('已取消\n'); | ||
| console.log('跳过 MCP 客户端配置\n'); | ||
| } | ||
| } | ||
| else { | ||
| console.log('跳过 MCP 客户端配置\n'); | ||
| if (selectedClients && selectedClients.length > 0) { | ||
| console.log(''); | ||
| let configured = true; | ||
| for (const clientKey of selectedClients) { | ||
| const client = MCP_CLIENTS[clientKey]; | ||
| if (updateMCPConfig(client.configPath, client, useNpx)) { | ||
| console.log(`✅ ${client.name}`); | ||
| } | ||
| else { | ||
| configured = false; | ||
| console.log(`❌ ${client.name} 配置失败`); | ||
| } | ||
| } | ||
| console.log(''); | ||
| if (!configured) { | ||
| throw new Error('部分 MCP 客户端配置失败'); | ||
| } | ||
| } | ||
@@ -295,5 +334,11 @@ } | ||
| } | ||
| main().catch(error => { | ||
| console.error('Configuration failed:', error); | ||
| process.exit(1); | ||
| }); | ||
| const currentFile = fileURLToPath(import.meta.url); | ||
| if (process.argv[1] && path.resolve(process.argv[1]) === currentFile) { | ||
| try { | ||
| await runConfigure(); | ||
| } | ||
| catch (error) { | ||
| console.error('Configuration failed:', error); | ||
| process.exitCode = 1; | ||
| } | ||
| } |
+28
-1
@@ -9,2 +9,29 @@ #!/usr/bin/env node | ||
| */ | ||
| export {}; | ||
| import { existsSync } from 'fs'; | ||
| import { MCPClientConfig } from './mcp-clients.js'; | ||
| import { uninstallNativeHost } from '@mearl/native-host'; | ||
| export type MCPConfigCleanupResult = 'removed' | 'not-found' | 'failed'; | ||
| export declare function cleanMCPConfig(configPath: string, clientConfig: MCPClientConfig): MCPConfigCleanupResult; | ||
| export interface MCPUninstallDependencies { | ||
| cleanConfig?: typeof cleanMCPConfig; | ||
| clients?: Record<string, MCPClientConfig>; | ||
| exists?: typeof existsSync; | ||
| globalInstallationDetected?: () => boolean; | ||
| log?: (message: string) => void; | ||
| logError?: (message: string) => void; | ||
| prompt?: (options: { | ||
| type: 'multiselect'; | ||
| name: 'clients'; | ||
| message: string; | ||
| choices: Array<{ | ||
| title: string; | ||
| value: string; | ||
| }>; | ||
| instructions: boolean; | ||
| hint: string; | ||
| }) => Promise<{ | ||
| clients?: string[]; | ||
| }>; | ||
| removeNativeHost?: typeof uninstallNativeHost; | ||
| } | ||
| export declare function runUninstall(argv?: string[], dependencies?: MCPUninstallDependencies): Promise<boolean>; |
+96
-75
@@ -9,25 +9,10 @@ #!/usr/bin/env node | ||
| */ | ||
| import { unlinkSync, existsSync, readFileSync, writeFileSync } from 'fs'; | ||
| import { join } from 'path'; | ||
| import { homedir, platform } from 'os'; | ||
| import { execSync } from 'child_process'; | ||
| import { existsSync, readFileSync, writeFileSync } from 'fs'; | ||
| import { platform } from 'os'; | ||
| import { execFileSync } from 'child_process'; | ||
| import path from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import prompts from 'prompts'; | ||
| import { HOST_NAME } from './constants.js'; | ||
| import { MCP_CLIENTS } from './mcp-clients.js'; | ||
| // 获取 Native Messaging Host 配置目录 | ||
| function getNativeHostDir() { | ||
| const os = platform(); | ||
| if (os === 'darwin') { | ||
| return join(homedir(), 'Library/Application Support/Google/Chrome/NativeMessagingHosts'); | ||
| } | ||
| else if (os === 'linux') { | ||
| return join(homedir(), '.config/google-chrome/NativeMessagingHosts'); | ||
| } | ||
| else if (os === 'win32') { | ||
| throw new Error('Windows 需要手动删除注册表项,请参考 README.md'); | ||
| } | ||
| else { | ||
| throw new Error(`不支持的平台: ${os}`); | ||
| } | ||
| } | ||
| import { uninstallNativeHost } from '@mearl/native-host'; | ||
| // 检查是否全局安装 | ||
@@ -37,3 +22,3 @@ function isGloballyInstalled() { | ||
| const cmd = platform() === 'win32' ? 'where' : 'which'; | ||
| execSync(`${cmd} mearl-mcp-server`, { encoding: 'utf-8', stdio: 'pipe' }); | ||
| execFileSync(cmd, ['mearl-mcp-server'], { encoding: 'utf-8', stdio: 'pipe' }); | ||
| return true; | ||
@@ -45,9 +30,30 @@ } | ||
| } | ||
| function removeTomlMCPSection(content) { | ||
| const lines = content.split('\n'); | ||
| const sectionStart = lines.findIndex(line => line.trim() === '[mcp_servers.mearl]'); | ||
| if (sectionStart === -1) | ||
| return null; | ||
| let sectionEnd = lines.length; | ||
| for (let index = sectionStart + 1; index < lines.length; index += 1) { | ||
| if (lines[index].trim().startsWith('[')) { | ||
| sectionEnd = index; | ||
| break; | ||
| } | ||
| } | ||
| return [...lines.slice(0, sectionStart), ...lines.slice(sectionEnd)].join('\n'); | ||
| } | ||
| // 清理 MCP 配置文件中的 mearl | ||
| function cleanMCPConfig(configPath, clientConfig) { | ||
| export function cleanMCPConfig(configPath, clientConfig) { | ||
| try { | ||
| if (!existsSync(configPath)) { | ||
| return false; | ||
| return 'not-found'; | ||
| } | ||
| const content = readFileSync(configPath, 'utf-8'); | ||
| if (clientConfig.useTomlFormat) { | ||
| const updated = removeTomlMCPSection(content); | ||
| if (updated === null) | ||
| return 'not-found'; | ||
| writeFileSync(configPath, updated); | ||
| return 'removed'; | ||
| } | ||
| const config = JSON.parse(content); | ||
@@ -71,48 +77,48 @@ const useServersFormat = clientConfig.useServersFormat; | ||
| if (!hasConfig) { | ||
| return false; | ||
| return 'not-found'; | ||
| } | ||
| // 写回文件 | ||
| writeFileSync(configPath, JSON.stringify(config, null, 2)); | ||
| return true; | ||
| return 'removed'; | ||
| } | ||
| catch (error) { | ||
| console.error(` ⚠️ 清理配置失败: ${error instanceof Error ? error.message : String(error)}`); | ||
| return false; | ||
| return 'failed'; | ||
| } | ||
| } | ||
| // 卸载主函数 | ||
| async function uninstall() { | ||
| console.log('🗑️ Mearl MCP 卸载\n'); | ||
| export async function runUninstall(argv = process.argv.slice(2), dependencies = {}) { | ||
| const log = dependencies.log ?? (message => console.log(message)); | ||
| const logError = dependencies.logError ?? (message => console.error(message)); | ||
| const removeNativeHost = dependencies.removeNativeHost ?? uninstallNativeHost; | ||
| const detectGlobalInstallation = dependencies.globalInstallationDetected ?? isGloballyInstalled; | ||
| const fileExists = dependencies.exists ?? existsSync; | ||
| const clients = dependencies.clients ?? MCP_CLIENTS; | ||
| const prompt = dependencies.prompt ?? prompts; | ||
| const cleanConfig = dependencies.cleanConfig ?? cleanMCPConfig; | ||
| const managedBySetup = argv.includes('--managed-by-setup'); | ||
| const yes = argv.includes('--yes') || argv.includes('-y'); | ||
| log('🗑️ Mearl MCP 卸载\n'); | ||
| try { | ||
| let completed = true; | ||
| // 1. 清理 Native Messaging Host 配置 | ||
| console.log('清理 Native Messaging Host...'); | ||
| const hostDir = getNativeHostDir(); | ||
| const manifestPath = join(hostDir, `${HOST_NAME}.json`); | ||
| const wrapperPath = join(hostDir, `${HOST_NAME}-launcher.sh`); | ||
| let hasDeleted = false; | ||
| if (existsSync(manifestPath)) { | ||
| unlinkSync(manifestPath); | ||
| console.log(`✅ 删除配置文件`); | ||
| hasDeleted = true; | ||
| } | ||
| if (existsSync(wrapperPath)) { | ||
| unlinkSync(wrapperPath); | ||
| hasDeleted = true; | ||
| } | ||
| if (!hasDeleted) { | ||
| console.log('未找到配置文件'); | ||
| } | ||
| log('清理 Native Messaging Host...'); | ||
| const nativeHostRemoved = await removeNativeHost({ mode: 'mcp', silent: true }); | ||
| log(nativeHostRemoved ? '✅ Native Messaging Host 已清理' : '⚠️ Native Messaging Host 清理失败'); | ||
| completed = nativeHostRemoved; | ||
| // 2. 检查全局安装 | ||
| console.log('\n检查 npm 全局安装...'); | ||
| if (isGloballyInstalled()) { | ||
| console.log('⚠️ 请手动卸载: npm uninstall -g @mearl/mcp-server'); | ||
| if (!managedBySetup) { | ||
| log('\n检查 npm 全局安装...'); | ||
| if (detectGlobalInstallation()) { | ||
| log('⚠️ 请手动卸载: npm uninstall -g @mearl/mcp-server'); | ||
| } | ||
| else { | ||
| log('✅ 未检测到全局安装'); | ||
| } | ||
| } | ||
| else { | ||
| console.log('✅ 未检测到全局安装'); | ||
| } | ||
| // 3. 清理 MCP 客户端配置 | ||
| console.log('\n清理 MCP 客户端配置...'); | ||
| log('\n清理 MCP 客户端配置...'); | ||
| // 检测哪些客户端有配置文件 | ||
| const availableClients = Object.entries(MCP_CLIENTS) | ||
| .filter(([_, client]) => existsSync(client.configPath)) | ||
| const availableClients = Object.entries(clients) | ||
| .filter(([_, client]) => fileExists(client.configPath)) | ||
| .map(([key, client]) => ({ | ||
@@ -123,21 +129,27 @@ title: client.name, | ||
| if (availableClients.length === 0) { | ||
| console.log('未找到 MCP 配置'); | ||
| log('未找到 MCP 配置'); | ||
| } | ||
| else { | ||
| const response = await prompts({ | ||
| type: 'multiselect', | ||
| name: 'clients', | ||
| message: '选择要清理的客户端:', | ||
| choices: availableClients, | ||
| instructions: false, | ||
| hint: '空格选择,回车确认', | ||
| }); | ||
| if (response.clients && response.clients.length > 0) { | ||
| for (const clientKey of response.clients) { | ||
| const client = MCP_CLIENTS[clientKey]; | ||
| if (cleanMCPConfig(client.configPath, client)) { | ||
| console.log(`✅ 已清理 ${client.name}`); | ||
| const selectedClients = yes | ||
| ? availableClients.map(client => client.value) | ||
| : (await prompt({ | ||
| type: 'multiselect', | ||
| name: 'clients', | ||
| message: '选择要清理的客户端:', | ||
| choices: availableClients, | ||
| instructions: false, | ||
| hint: '空格选择,回车确认', | ||
| })).clients; | ||
| if (selectedClients && selectedClients.length > 0) { | ||
| for (const clientKey of selectedClients) { | ||
| const client = clients[clientKey]; | ||
| const result = cleanConfig(client.configPath, client); | ||
| if (result === 'removed') { | ||
| log(`✅ 已清理 ${client.name}`); | ||
| } | ||
| else if (result === 'not-found') { | ||
| log(`未找到 ${client.name} 配置`); | ||
| } | ||
| else { | ||
| console.log(`未找到 ${client.name} 配置`); | ||
| completed = false; | ||
| } | ||
@@ -147,12 +159,21 @@ } | ||
| else { | ||
| console.log('已取消'); | ||
| log('已取消'); | ||
| } | ||
| } | ||
| console.log('\n✅ 卸载完成!'); | ||
| if (completed) { | ||
| log('\n✅ 卸载完成!'); | ||
| } | ||
| else { | ||
| logError('\n❌ 卸载未完整完成,请检查上述错误'); | ||
| } | ||
| return completed; | ||
| } | ||
| catch (error) { | ||
| console.error('\n❌ 卸载失败:', error instanceof Error ? error.message : String(error)); | ||
| process.exit(1); | ||
| logError(`\n❌ 卸载失败: ${error instanceof Error ? error.message : String(error)}`); | ||
| return false; | ||
| } | ||
| } | ||
| uninstall(); | ||
| const currentFile = fileURLToPath(import.meta.url); | ||
| if (process.argv[1] && path.resolve(process.argv[1]) === currentFile) { | ||
| process.exitCode = (await runUninstall()) ? 0 : 1; | ||
| } |
+4
-3
| { | ||
| "name": "@mearl/mcp-server", | ||
| "version": "2.2.1", | ||
| "version": "2.2.2", | ||
| "description": "MCP Server for Mearl - enables AI to interact with Chrome browser requests and logs", | ||
@@ -33,4 +33,4 @@ "mcpName": "io.github.F-loat/mearl", | ||
| "prompts": "^2.4.2", | ||
| "@mearl/client": "2.2.1", | ||
| "@mearl/native-host": "2.2.1" | ||
| "@mearl/client": "2.2.2", | ||
| "@mearl/native-host": "2.2.2" | ||
| }, | ||
@@ -47,4 +47,5 @@ "devDependencies": { | ||
| "setup": "pnpm build && npm link", | ||
| "test": "vitest run", | ||
| "typecheck": "tsc --noEmit" | ||
| } | ||
| } |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
141148
3.66%2872
3.72%1
-50%+ Added
+ Added
- Removed
- Removed
Updated
Updated