UNPKG

@build000r/mcp-mcp

Version:

Fast CLI tool to scan, view, park (JIT), and interactively manage MCP server configurations across all Claude projects

152 lines (127 loc) 4.83 kB
const fs = require('fs'); const path = require('path'); const os = require('os'); const { promisify } = require('util'); const readdir = promisify(fs.readdir); const ConfigParser = require('./ConfigParser'); const PathUtils = require('../utils/PathUtils'); class ConfigScanner { constructor() { const patterns = PathUtils.getConfigPatterns(); this.skipDirPattern = patterns.skipDirPattern; this.configFilePattern = patterns.configFilePattern; } async findConfigs(mode = 'upstream') { const configs = []; const homeDir = os.homedir(); // Always check main Claude config first const homeConfigPath = path.join(homeDir, '.claude.json'); if (fs.existsSync(homeConfigPath)) { const homeConfigs = await ConfigParser.parseClaudeConfig(homeConfigPath); configs.push(...homeConfigs); } if (mode === 'upstream') { // Scan from current directory up to home await this.scanUpstream(process.cwd(), homeDir, configs); } else { // Scan entire computer (common directories) await this.scanAll(homeDir, configs); } // Remove duplicates and sort const uniqueConfigs = configs.filter((config, index, self) => index === self.findIndex(c => c.path === config.path && c.configType === config.configType) ); return uniqueConfigs.sort((a, b) => a.path.length - b.path.length); } async scanUpstream(startDir, homeDir, configs) { const visited = new Set(); let currentDir = startDir; // Walk up the directory tree from current to home while (currentDir && currentDir.startsWith(homeDir)) { if (!visited.has(currentDir)) { visited.add(currentDir); await this.scanSingleDirectory(currentDir, configs); } const parentDir = path.dirname(currentDir); if (parentDir === currentDir) break; // Reached root currentDir = parentDir; } // Also scan the current directory's immediate subdirectories await this.scanDirectoryRecursive(startDir, configs, visited, 2); } async scanAll(homeDir, configs) { const visited = new Set(); const searchDirs = PathUtils.getSearchDirectories(homeDir); // Scan all directories in parallel for speed const scanPromises = []; for (const dir of searchDirs) { if (fs.existsSync(dir) && !visited.has(dir)) { scanPromises.push(this.scanDirectoryRecursive(dir, configs, visited, 5)); } } await Promise.all(scanPromises); } async scanSingleDirectory(dir, configs) { try { const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.isFile() && this.configFilePattern.test(entry.name)) { const filePath = path.join(dir, entry.name); const config = await ConfigParser.parseMCPConfigFile(filePath, entry.name); if (config) { configs.push({ path: dir, configPath: filePath, config: config, scope: PathUtils.determineScope(dir), configType: entry.name }); } } } } catch (error) { // Skip directories we can't read } } async scanDirectoryRecursive(dir, configs, visited, maxDepth, currentDepth = 0) { if (currentDepth > maxDepth || visited.has(dir)) return; visited.add(dir); try { const entries = await readdir(dir, { withFileTypes: true }); const subDirPromises = []; // Process files first for (const entry of entries) { if (entry.isFile() && this.configFilePattern.test(entry.name)) { const filePath = path.join(dir, entry.name); const config = await ConfigParser.parseMCPConfigFile(filePath, entry.name); if (config) { configs.push({ path: dir, configPath: filePath, config: config, scope: PathUtils.determineScope(dir), configType: entry.name }); } } } // Then process subdirectories in parallel for (const entry of entries) { if (entry.isDirectory() && !this.skipDirPattern.test(entry.name) && !entry.name.startsWith('.')) { const subDir = path.join(dir, entry.name); subDirPromises.push( this.scanDirectoryRecursive(subDir, configs, visited, maxDepth, currentDepth + 1) ); } } // Limit parallelism const batchSize = 5; for (let i = 0; i < subDirPromises.length; i += batchSize) { await Promise.all(subDirPromises.slice(i, i + batchSize)); } } catch (error) { // Skip directories we can't read } } } module.exports = ConfigScanner;