Skip to main content
Glama

Linux Bash MCP Server

by gunjanjp
setup.js16 kB
#!/usr/bin/env node import { promisify } from "util"; import { exec } from "child_process"; import fs from "fs/promises"; import path from "path"; import os from "os"; import readline from "readline"; const execAsync = promisify(exec); class SetupScript { constructor() { this.projectDir = process.cwd(); this.configPath = this.getClaudeConfigPath(); this.serverConfigPath = path.join(this.projectDir, "config.json"); this.selectedDistribution = null; } getClaudeConfigPath() { const platform = os.platform(); if (platform === "win32") { return path.join(os.homedir(), "AppData", "Roaming", "Claude", "claude_desktop_config.json"); } else if (platform === "darwin") { return path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json"); } else { return path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json"); } } async promptUser(question) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); }); }); } async detectWSLDistributions() { console.log("🔍 Detecting available WSL distributions...\n"); try { const { stdout } = await execAsync("wsl -l -v"); // Parse WSL output to extract distribution names and states const lines = stdout.split('\n').filter(line => line.trim()); const distributions = []; for (let i = 1; i < lines.length; i++) { // Skip header const line = lines[i].trim(); if (line) { // Remove special characters and parse const cleanLine = line.replace(/[\x00-\x1f\x7f-\x9f]/g, ''); const parts = cleanLine.split(/\s+/); if (parts.length >= 3) { const name = parts[0].replace(/^\*\s*/, ''); // Remove default marker const state = parts[1]; const version = parts[2]; if (state === "Running" || state === "Stopped") { distributions.push({ name: name, state: state, version: version, isDefault: line.includes('*') }); } } } } return distributions; } catch (error) { throw new Error(`Failed to detect WSL distributions: ${error.message}`); } } async selectDistribution(distributions) { if (distributions.length === 0) { throw new Error("No WSL distributions found. Please install a Linux distribution first."); } console.log("📋 Available WSL distributions:"); distributions.forEach((dist, index) => { const defaultMarker = dist.isDefault ? " (default)" : ""; const stateIcon = dist.state === "Running" ? "🟢" : "🔴"; console.log(`${index + 1}. ${stateIcon} ${dist.name} - ${dist.state}, WSL${dist.version}${defaultMarker}`); }); console.log(""); let selectedIndex; if (distributions.length === 1) { console.log(`🎯 Only one distribution available: ${distributions[0].name}`); selectedIndex = 0; } else { const answer = await this.promptUser("Select distribution number (or press Enter for default): "); if (answer === "") { // Find default distribution const defaultDist = distributions.find(d => d.isDefault); if (defaultDist) { selectedIndex = distributions.indexOf(defaultDist); console.log(`📌 Using default distribution: ${defaultDist.name}`); } else { selectedIndex = 0; console.log(`📌 Using first distribution: ${distributions[0].name}`); } } else { selectedIndex = parseInt(answer) - 1; if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= distributions.length) { throw new Error("Invalid selection. Please run setup again."); } } } const selected = distributions[selectedIndex]; console.log(`✅ Selected: ${selected.name}\n`); return selected; } async testDistribution(distribution) { console.log(`🧪 Testing ${distribution.name}...`); try { // Start the distribution if it's stopped if (distribution.state === "Stopped") { console.log(` Starting ${distribution.name}...`); await execAsync(`wsl -d ${distribution.name} -- echo "starting"`); } // Test basic commands const tests = [ { cmd: "echo 'Hello WSL'", desc: "Basic echo test" }, { cmd: "whoami", desc: "User identification" }, { cmd: "pwd", desc: "Working directory" }, { cmd: "uname -s", desc: "Operating system" } ]; for (const test of tests) { try { const { stdout } = await execAsync(`wsl -d ${distribution.name} -- ${test.cmd}`); console.log(` ✅ ${test.desc}: ${stdout.trim()}`); } catch (error) { console.log(` ⚠️ ${test.desc}: ${error.message}`); } } console.log(`✅ ${distribution.name} is working correctly\n`); } catch (error) { throw new Error(`Failed to test ${distribution.name}: ${error.message}`); } } async checkPrerequisites() { console.log("🔍 Checking prerequisites...\n"); // Check Node.js version try { const { stdout } = await execAsync("node --version"); const version = stdout.trim(); console.log(`✅ Node.js: ${version}`); const majorVersion = parseInt(version.slice(1).split('.')[0]); if (majorVersion < 18) { throw new Error("Node.js 18+ is required"); } } catch (error) { console.log("❌ Node.js not found or version too old"); throw error; } // Check WSL try { const { stdout } = await execAsync("wsl --version"); console.log("✅ WSL is installed"); } catch (error) { console.log("❌ WSL not found"); throw new Error("WSL2 is required but not installed"); } // Detect and select WSL distribution try { const distributions = await this.detectWSLDistributions(); this.selectedDistribution = await this.selectDistribution(distributions); await this.testDistribution(this.selectedDistribution); } catch (error) { console.log("❌ WSL distribution setup failed"); throw error; } console.log("✅ All prerequisites check passed!\n"); } async updateServerConfig() { console.log("⚙️ Updating server configuration...\n"); try { // Read existing config let config = {}; try { const configContent = await fs.readFile(this.serverConfigPath, "utf8"); config = JSON.parse(configContent); } catch (error) { // Use default config if file doesn't exist config = { wslDistribution: "auto-detect", defaultTimeout: 30000, scriptTimeout: 60000, maxBufferSize: 10485760, debugMode: false }; } // Update with selected distribution config.wslDistribution = this.selectedDistribution.name; config.selectedDistributionInfo = { name: this.selectedDistribution.name, state: this.selectedDistribution.state, version: this.selectedDistribution.version, isDefault: this.selectedDistribution.isDefault, configuredAt: new Date().toISOString() }; // Write updated config await fs.writeFile(this.serverConfigPath, JSON.stringify(config, null, 2)); console.log(`✅ Server configured to use: ${this.selectedDistribution.name}`); console.log(`📄 Configuration saved to: ${this.serverConfigPath}\n`); } catch (error) { throw new Error(`Failed to update server configuration: ${error.message}`); } } async installDependencies() { console.log("📦 Installing Node.js dependencies...\n"); try { const { stdout, stderr } = await execAsync("npm install", { cwd: this.projectDir }); console.log("✅ Dependencies installed successfully"); if (stderr) { console.log("⚠️ Warnings:", stderr); } } catch (error) { console.log("❌ Failed to install dependencies"); throw error; } console.log(""); } async runTests() { console.log("🧪 Running tests to verify setup...\n"); try { // Set environment variable for tests to use selected distribution process.env.WSL_DISTRIBUTION = this.selectedDistribution.name; const { stdout } = await execAsync("npm test", { cwd: this.projectDir, env: { ...process.env, WSL_DISTRIBUTION: this.selectedDistribution.name } }); console.log(stdout); } catch (error) { console.log("❌ Tests failed"); console.log(error.stdout || error.message); throw error; } } async loadExistingClaudeConfig() { try { const configContent = await fs.readFile(this.configPath, "utf8"); return JSON.parse(configContent); } catch (error) { if (error.code === 'ENOENT') { // File doesn't exist, return empty config return {}; } else if (error instanceof SyntaxError) { // Invalid JSON, backup and start fresh const backupPath = `${this.configPath}.backup.${Date.now()}`; await fs.copyFile(this.configPath, backupPath); console.log(`⚠️ Invalid JSON in config file. Backed up to: ${backupPath}`); return {}; } else { throw error; } } } async generateConfig() { console.log("⚙️ Updating Claude Desktop configuration...\n"); const serverPath = path.join(this.projectDir, "src", "index.js").replace(/\\/g, "\\\\"); const newMcpServer = { "linux-bash": { command: "node", args: [serverPath], env: { WSL_DISTRIBUTION: this.selectedDistribution.name } } }; // Load existing configuration let existingConfig = await this.loadExistingClaudeConfig(); // Ensure mcpServers exists if (!existingConfig.mcpServers) { existingConfig.mcpServers = {}; console.log("📄 Creating new Claude Desktop configuration"); } else { console.log("📄 Found existing Claude Desktop configuration"); // Show existing MCP servers const existingServers = Object.keys(existingConfig.mcpServers); if (existingServers.length > 0) { console.log("📋 Existing MCP servers:"); existingServers.forEach(server => { console.log(` • ${server}`); }); } } // Check if our server already exists if (existingConfig.mcpServers["linux-bash"]) { console.log("⚠️ 'linux-bash' MCP server already exists - updating configuration"); } else { console.log("➕ Adding new 'linux-bash' MCP server"); } // Merge configurations - add our server without affecting others const mergedConfig = { ...existingConfig, mcpServers: { ...existingConfig.mcpServers, ...newMcpServer } }; // Create directory if it doesn't exist await fs.mkdir(path.dirname(this.configPath), { recursive: true }); // Write merged configuration await fs.writeFile(this.configPath, JSON.stringify(mergedConfig, null, 2)); console.log(`✅ Configuration updated at: ${this.configPath}`); // Show final MCP servers list const finalServers = Object.keys(mergedConfig.mcpServers); console.log("📋 All configured MCP servers:"); finalServers.forEach(server => { const isNew = server === "linux-bash"; const icon = isNew ? "🆕" : "📌"; const label = isNew ? " (newly added)" : ""; console.log(` ${icon} ${server}${label}`); }); console.log(""); return this.configPath; } async printInstructions(configPath) { console.log("🎉 Setup completed successfully!\n"); console.log("📋 Configuration Summary:"); console.log(` • WSL Distribution: ${this.selectedDistribution.name}`); console.log(` • Distribution State: ${this.selectedDistribution.state}`); console.log(` • WSL Version: ${this.selectedDistribution.version}`); console.log(` • Server Config: ${this.serverConfigPath}`); console.log(` • Claude Config: ${configPath}\n`); console.log("🔧 Claude Desktop Integration:"); console.log(" ✅ MCP server 'linux-bash' has been added to your Claude Desktop configuration"); console.log(" ✅ Existing MCP servers have been preserved"); console.log(" ✅ No existing configurations were modified or removed\n"); console.log("📋 Next steps:"); console.log("1. 🔄 Restart Claude Desktop application"); console.log("2. 🛠️ The 'linux-bash' MCP server should now be available alongside your existing servers"); console.log("3. 🧪 Try these example commands in Claude Desktop:"); console.log(" - 'Check WSL status and show system information'"); console.log(" - 'List files in /home directory'"); console.log(" - 'Show Linux distribution information'"); console.log(" - 'Create a system monitoring script and run it'\n"); console.log("📁 Project files:"); console.log(` - Server: ${path.join(this.projectDir, "src", "index.js")}`); console.log(` - Server Config: ${this.serverConfigPath}`); console.log(` - Claude Config: ${configPath}`); console.log(` - Examples: ${path.join(this.projectDir, "examples")}`); console.log(` - Tests: ${path.join(this.projectDir, "test")}\n`); console.log("🔧 Available tools in 'linux-bash' MCP server:"); console.log(" - execute_bash_command: Run single bash commands"); console.log(" - execute_bash_script: Run bash script files with arguments"); console.log(" - create_bash_script: Create new bash scripts"); console.log(" - list_directory: List directory contents"); console.log(" - get_system_info: Get comprehensive system information"); console.log(" - check_wsl_status: Check WSL2 status and distribution info\n"); console.log("🔄 To change WSL distribution later:"); console.log(" Run 'npm run setup' again to reconfigure\n"); console.log("🔗 Integration notes:"); console.log(" • This server works alongside any existing MCP servers you have"); console.log(" • Your existing Claude Desktop configuration has been preserved"); console.log(" • You can use multiple MCP servers simultaneously in Claude Desktop\n"); console.log("📖 For more information, see README.md"); } async run() { try { console.log("🚀 Linux Bash MCP Server Setup\n"); await this.checkPrerequisites(); await this.updateServerConfig(); await this.installDependencies(); await this.runTests(); const configPath = await this.generateConfig(); await this.printInstructions(configPath); } catch (error) { console.log("\n❌ Setup failed:", error.message); console.log("\n🔧 Troubleshooting:"); console.log("1. Ensure WSL2 is installed: 'wsl --install'"); console.log("2. Install a Linux distribution: 'wsl --install -d Ubuntu' or 'wsl --install -d Debian'"); console.log("3. Update WSL: 'wsl --update'"); console.log("4. Check Node.js version: 'node --version' (requires 18+)"); console.log("5. List WSL distributions: 'wsl -l -v'"); console.log("6. See README.md for detailed instructions"); process.exit(1); } } } // Run setup if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { const setup = new SetupScript(); setup.run(); } export default SetupScript;

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/gunjanjp/linuxshell-mcp'

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