Skip to main content
Glama

Claude Desktop Commander MCP

import { homedir, platform } from 'os'; import fs from 'fs/promises'; import path from 'path'; import { join } from 'path'; import { readFileSync, writeFileSync, existsSync, appendFileSync, mkdirSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { exec } from "node:child_process"; import { version as nodeVersion } from 'process'; import * as https from 'https'; import { randomUUID } from 'crypto'; // Google Analytics configuration const GA_MEASUREMENT_ID = 'G-NGGDNL0K4L'; // Replace with your GA4 Measurement ID const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A'; // Replace with your GA4 API Secret const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`; // Read clientId and telemetry settings from existing config let uniqueUserId = 'unknown'; let telemetryEnabled = false; // Default to disabled for privacy async function getConfigSettings() { try { const USER_HOME = homedir(); const CONFIG_DIR = path.join(USER_HOME, '.claude-server-commander'); const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json'); if (existsSync(CONFIG_FILE)) { const configData = readFileSync(CONFIG_FILE, 'utf8'); const config = JSON.parse(configData); return { clientId: config.clientId || randomUUID(), telemetryEnabled: config.telemetryEnabled === true // Explicit check for true }; } // Fallback: generate new ID and default telemetry to false if config doesn't exist return { clientId: `unknown-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`, telemetryEnabled: false }; } catch (error) { // Final fallback return { clientId: `random-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`, telemetryEnabled: false }; } } // Uninstall tracking let uninstallSteps = []; let uninstallStartTime = Date.now(); // Fix for Windows ESM path resolution const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Setup logging const LOG_FILE = join(__dirname, 'setup.log'); function logToFile(message, isError = false) { const timestamp = new Date().toISOString(); const logMessage = `${timestamp} - ${isError ? 'ERROR: ' : ''}${message}\n`; try { appendFileSync(LOG_FILE, logMessage); const jsonOutput = { type: isError ? 'error' : 'info', timestamp, message }; process.stdout.write(`${message}\n`); } catch (err) { process.stderr.write(`${JSON.stringify({ type: 'error', timestamp: new Date().toISOString(), message: `Failed to write to log file: ${err.message}` })}\n`); } } // Function to get npm version async function getNpmVersion() { try { return new Promise((resolve, reject) => { exec('npm --version', (error, stdout, stderr) => { if (error) { resolve('unknown'); return; } resolve(stdout.trim()); }); }); } catch (error) { return 'unknown'; } } // Get Desktop Commander version const getVersion = async () => { try { if (process.env.npm_package_version) { return process.env.npm_package_version; } const versionPath = join(__dirname, 'version.js'); if (existsSync(versionPath)) { const { VERSION } = await import(versionPath); return VERSION; } const packageJsonPath = join(__dirname, 'package.json'); if (existsSync(packageJsonPath)) { const packageJsonContent = readFileSync(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); if (packageJson.version) { return packageJson.version; } } return 'unknown'; } catch (error) { return 'unknown'; } }; // Function to detect shell environment function detectShell() { if (process.platform === 'win32') { if (process.env.TERM_PROGRAM === 'vscode') return 'vscode-terminal'; if (process.env.WT_SESSION) return 'windows-terminal'; if (process.env.SHELL?.includes('bash')) return 'git-bash'; if (process.env.TERM?.includes('xterm')) return 'xterm-on-windows'; if (process.env.ComSpec?.toLowerCase().includes('powershell')) return 'powershell'; if (process.env.PROMPT) return 'cmd'; if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) { return `wsl-${process.env.WSL_DISTRO_NAME || 'unknown'}`; } return 'windows-unknown'; } if (process.env.SHELL) { const shellPath = process.env.SHELL.toLowerCase(); if (shellPath.includes('bash')) return 'bash'; if (shellPath.includes('zsh')) return 'zsh'; if (shellPath.includes('fish')) return 'fish'; if (shellPath.includes('ksh')) return 'ksh'; if (shellPath.includes('csh')) return 'csh'; if (shellPath.includes('dash')) return 'dash'; return `other-unix-${shellPath.split('/').pop()}`; } if (process.env.TERM_PROGRAM) { return process.env.TERM_PROGRAM.toLowerCase(); } return 'unknown-shell'; } // Function to determine execution context function getExecutionContext() { const isNpx = process.env.npm_lifecycle_event === 'npx' || process.env.npm_execpath?.includes('npx') || process.env._?.includes('npx') || import.meta.url.includes('node_modules'); const isGlobal = process.env.npm_config_global === 'true' || process.argv[1]?.includes('node_modules/.bin'); const isNpmScript = !!process.env.npm_lifecycle_script; return { runMethod: isNpx ? 'npx' : (isGlobal ? 'global' : (isNpmScript ? 'npm_script' : 'direct')), isCI: !!process.env.CI || !!process.env.GITHUB_ACTIONS || !!process.env.TRAVIS || !!process.env.CIRCLECI, shell: detectShell() }; } // Enhanced tracking properties let npmVersionCache = null; async function getTrackingProperties(additionalProps = {}) { const propertiesStep = addUninstallStep('get_tracking_properties'); try { if (npmVersionCache === null) { npmVersionCache = await getNpmVersion(); } const context = getExecutionContext(); const version = await getVersion(); updateUninstallStep(propertiesStep, 'completed'); return { platform: platform(), node_version: nodeVersion, npm_version: npmVersionCache, execution_context: context.runMethod, is_ci: context.isCI, shell: context.shell, app_version: version, engagement_time_msec: "100", ...additionalProps }; } catch (error) { updateUninstallStep(propertiesStep, 'failed', error); return { platform: platform(), node_version: nodeVersion, error: error.message, ...additionalProps }; } } // Enhanced tracking function with retries async function trackEvent(eventName, additionalProps = {}) { const trackingStep = addUninstallStep(`track_event_${eventName}`); // Check if telemetry is disabled if (!telemetryEnabled) { updateUninstallStep(trackingStep, 'skipped_telemetry_disabled'); return true; // Return success since this is expected behavior } if (!GA_MEASUREMENT_ID || !GA_API_SECRET) { updateUninstallStep(trackingStep, 'skipped', new Error('GA not configured')); return; } const maxRetries = 2; let attempt = 0; let lastError = null; while (attempt <= maxRetries) { try { attempt++; const eventProperties = await getTrackingProperties(additionalProps); const payload = { client_id: uniqueUserId, non_personalized_ads: false, timestamp_micros: Date.now() * 1000, events: [{ name: eventName, params: eventProperties }] }; const postData = JSON.stringify(payload); const options = { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) } }; const result = await new Promise((resolve, reject) => { const req = https.request(GA_BASE_URL, options); const timeoutId = setTimeout(() => { req.destroy(); reject(new Error('Request timeout')); }, 5000); req.on('error', (error) => { clearTimeout(timeoutId); reject(error); }); req.on('response', (res) => { clearTimeout(timeoutId); let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('error', (error) => { reject(error); }); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve({ success: true, data }); } else { reject(new Error(`HTTP error ${res.statusCode}: ${data}`)); } }); }); req.write(postData); req.end(); }); updateUninstallStep(trackingStep, 'completed'); return result; } catch (error) { lastError = error; if (attempt <= maxRetries) { await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); } } } updateUninstallStep(trackingStep, 'failed', lastError); return false; } // Ensure tracking completes before process exits async function ensureTrackingCompleted(eventName, additionalProps = {}, timeoutMs = 6000) { return new Promise(async (resolve) => { const timeoutId = setTimeout(() => { resolve(false); }, timeoutMs); try { await trackEvent(eventName, additionalProps); clearTimeout(timeoutId); resolve(true); } catch (error) { clearTimeout(timeoutId); resolve(false); } }); } // Setup global error handlers (will be initialized after config is loaded) let errorHandlersInitialized = false; function initializeErrorHandlers() { if (errorHandlersInitialized) return; process.on('uncaughtException', async (error) => { if (telemetryEnabled) { await trackEvent('uninstall_uncaught_exception', { error: error.message }); } setTimeout(() => { process.exit(1); }, 1000); }); process.on('unhandledRejection', async (reason, promise) => { if (telemetryEnabled) { await trackEvent('uninstall_unhandled_rejection', { error: String(reason) }); } setTimeout(() => { process.exit(1); }, 1000); }); errorHandlersInitialized = true; } // Track when the process is about to exit let isExiting = false; process.on('exit', () => { if (!isExiting) { isExiting = true; } }); // Determine OS and set appropriate config path const os = platform(); const isWindows = os === 'win32'; let claudeConfigPath; switch (os) { case 'win32': claudeConfigPath = join(process.env.APPDATA, 'Claude', 'claude_desktop_config.json'); break; case 'darwin': claudeConfigPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'); break; case 'linux': claudeConfigPath = join(homedir(), '.config', 'Claude', 'claude_desktop_config.json'); break; default: claudeConfigPath = join(homedir(), '.claude_desktop_config.json'); } // Step tracking functions function addUninstallStep(step, status = 'started', error = null) { const timestamp = Date.now(); uninstallSteps.push({ step, status, timestamp, timeFromStart: timestamp - uninstallStartTime, error: error ? error.message || String(error) : null }); return uninstallSteps.length - 1; } function updateUninstallStep(index, status, error = null) { if (uninstallSteps[index]) { const timestamp = Date.now(); uninstallSteps[index].status = status; uninstallSteps[index].completionTime = timestamp; uninstallSteps[index].timeFromStart = timestamp - uninstallStartTime; if (error) { uninstallSteps[index].error = error.message || String(error); } } } async function execAsync(command) { const execStep = addUninstallStep(`exec_${command.substring(0, 20)}...`); return new Promise((resolve, reject) => { const actualCommand = isWindows ? `cmd.exe /c ${command}` : command; exec(actualCommand, { timeout: 10000 }, (error, stdout, stderr) => { if (error) { updateUninstallStep(execStep, 'failed', error); reject(error); return; } updateUninstallStep(execStep, 'completed'); resolve({ stdout, stderr }); }); }); } // Backup configuration before removal async function createConfigBackup(configPath) { const backupStep = addUninstallStep('create_config_backup'); try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = `${configPath}.backup.${timestamp}`; if (existsSync(configPath)) { const configData = readFileSync(configPath, 'utf8'); writeFileSync(backupPath, configData, 'utf8'); updateUninstallStep(backupStep, 'completed'); logToFile(`Configuration backup created: ${backupPath}`); await trackEvent('uninstall_backup_created'); return backupPath; } else { updateUninstallStep(backupStep, 'no_config_file'); return null; } } catch (error) { updateUninstallStep(backupStep, 'failed', error); await trackEvent('uninstall_backup_failed', { error: error.message }); logToFile(`Failed to create backup: ${error.message}`, true); return null; } } // Restore configuration from backup async function restoreFromBackup(backupPath) { if (!backupPath || !existsSync(backupPath)) { return false; } try { const backupData = readFileSync(backupPath, 'utf8'); writeFileSync(claudeConfigPath, backupData, 'utf8'); logToFile(`Configuration restored from backup: ${backupPath}`); await trackEvent('uninstall_backup_restored'); return true; } catch (error) { logToFile(`Failed to restore from backup: ${error.message}`, true); await trackEvent('uninstall_backup_restore_failed', { error: error.message }); return false; } } async function restartClaude() { const restartStep = addUninstallStep('restart_claude'); try { const platform = process.platform; logToFile('Attempting to restart Claude...'); await trackEvent('uninstall_restart_claude_attempt'); // Try to kill Claude process first const killStep = addUninstallStep('kill_claude_process'); try { switch (platform) { case "win32": await execAsync(`taskkill /F /IM "Claude.exe"`); break; case "darwin": await execAsync(`killall "Claude"`); break; case "linux": await execAsync(`pkill -f "claude"`); break; } updateUninstallStep(killStep, 'completed'); logToFile("Claude process terminated successfully"); await trackEvent('uninstall_kill_claude_success'); } catch (killError) { updateUninstallStep(killStep, 'no_process_found', killError); logToFile("Claude process not found or already terminated"); await trackEvent('uninstall_kill_claude_not_needed'); } // Wait a bit to ensure process termination await new Promise((resolve) => setTimeout(resolve, 2000)); // Try to start Claude const startStep = addUninstallStep('start_claude_process'); try { if (platform === "win32") { logToFile("Windows: Claude restart skipped - please restart Claude manually"); updateUninstallStep(startStep, 'skipped'); await trackEvent('uninstall_start_claude_skipped'); } else if (platform === "darwin") { await execAsync(`open -a "Claude"`); updateUninstallStep(startStep, 'completed'); logToFile("✅ Claude has been restarted automatically!"); await trackEvent('uninstall_start_claude_success'); } else if (platform === "linux") { await execAsync(`claude`); updateUninstallStep(startStep, 'completed'); logToFile("✅ Claude has been restarted automatically!"); await trackEvent('uninstall_start_claude_success'); } else { logToFile('To complete uninstallation, restart Claude if it\'s currently running'); updateUninstallStep(startStep, 'manual_required'); } updateUninstallStep(restartStep, 'completed'); await trackEvent('uninstall_restart_claude_success'); } catch (startError) { updateUninstallStep(startStep, 'failed', startError); await trackEvent('uninstall_start_claude_error', { error: startError.message }); logToFile(`Could not automatically restart Claude: ${startError.message}. Please restart it manually.`); } } catch (error) { updateUninstallStep(restartStep, 'failed', error); await trackEvent('uninstall_restart_claude_error', { error: error.message }); logToFile(`Failed to restart Claude: ${error.message}. Please restart it manually.`, true); } } async function removeDesktopCommanderConfig() { const configStep = addUninstallStep('remove_mcp_config'); let backupPath = null; try { // Check if config file exists if (!existsSync(claudeConfigPath)) { updateUninstallStep(configStep, 'no_config_file'); logToFile(`Claude config file not found at: ${claudeConfigPath}`); logToFile('✅ Desktop Commander was not configured or already removed.'); await trackEvent('uninstall_config_not_found'); return true; } // Create backup before making changes backupPath = await createConfigBackup(claudeConfigPath); // Read existing config let config; const readStep = addUninstallStep('read_config_file'); try { const configData = readFileSync(claudeConfigPath, 'utf8'); config = JSON.parse(configData); updateUninstallStep(readStep, 'completed'); } catch (readError) { updateUninstallStep(readStep, 'failed', readError); await trackEvent('uninstall_config_read_error', { error: readError.message }); throw new Error(`Failed to read config file: ${readError.message}`); } // Check if mcpServers exists if (!config.mcpServers) { updateUninstallStep(configStep, 'no_mcp_servers'); logToFile('No MCP servers configured in Claude.'); logToFile('✅ Desktop Commander was not configured or already removed.'); await trackEvent('uninstall_no_mcp_servers'); return true; } // Track what we're removing const serversToRemove = []; if (config.mcpServers["desktop-commander"]) { serversToRemove.push("desktop-commander"); } if (serversToRemove.length === 0) { updateUninstallStep(configStep, 'not_found'); logToFile('Desktop Commander MCP server not found in configuration.'); logToFile('✅ Desktop Commander was not configured or already removed.'); await trackEvent('uninstall_server_not_found'); return true; } // Remove the server configurations const removeStep = addUninstallStep('remove_server_configs'); try { serversToRemove.forEach(serverName => { delete config.mcpServers[serverName]; logToFile(`Removed "${serverName}" from Claude configuration`); }); updateUninstallStep(removeStep, 'completed'); await trackEvent('uninstall_servers_removed'); } catch (removeError) { updateUninstallStep(removeStep, 'failed', removeError); await trackEvent('uninstall_servers_remove_error', { error: removeError.message }); throw new Error(`Failed to remove server configs: ${removeError.message}`); } // Write the updated config back const writeStep = addUninstallStep('write_updated_config'); try { writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8'); updateUninstallStep(writeStep, 'completed'); updateUninstallStep(configStep, 'completed'); logToFile('✅ Desktop Commander successfully removed from Claude configuration'); logToFile(`Configuration updated at: ${claudeConfigPath}`); await trackEvent('uninstall_config_updated'); } catch (writeError) { updateUninstallStep(writeStep, 'failed', writeError); await trackEvent('uninstall_config_write_error', { error: writeError.message }); // Try to restore from backup if (backupPath) { logToFile('Attempting to restore configuration from backup...'); const restored = await restoreFromBackup(backupPath); if (restored) { throw new Error(`Failed to write updated config, but backup was restored: ${writeError.message}`); } else { throw new Error(`Failed to write updated config and backup restoration failed: ${writeError.message}`); } } else { throw new Error(`Failed to write updated config: ${writeError.message}`); } } return true; } catch (error) { updateUninstallStep(configStep, 'failed', error); await trackEvent('uninstall_config_error', { error: error.message }); logToFile(`Error removing Desktop Commander configuration: ${error.message}`, true); // Try to restore from backup if we have one if (backupPath) { logToFile('Attempting to restore configuration from backup...'); await restoreFromBackup(backupPath); } return false; } } // Main uninstall function export default async function uninstall() { // Initialize clientId and telemetry settings from existing config const configSettings = await getConfigSettings(); uniqueUserId = configSettings.clientId; telemetryEnabled = configSettings.telemetryEnabled; // Initialize error handlers now that telemetry setting is known initializeErrorHandlers(); // Log telemetry status for transparency if (!telemetryEnabled) { logToFile('Telemetry disabled - no analytics will be sent'); } // Initial tracking (only if telemetry enabled) await ensureTrackingCompleted('uninstall_start'); const mainStep = addUninstallStep('main_uninstall'); try { logToFile('Starting Desktop Commander uninstallation...'); // Remove the server configuration from Claude const configRemoved = await removeDesktopCommanderConfig(); if (configRemoved) { // Try to restart Claude // await restartClaude(); updateUninstallStep(mainStep, 'completed'); const appVersion = await getVersion(); logToFile(`\n✅ Desktop Commander has been successfully uninstalled!`); logToFile('The MCP server has been removed from Claude\'s configuration.'); logToFile('\nIf you want to reinstall later, you can run:'); logToFile('npx @wonderwhy-er/desktop-commander@latest setup'); logToFile('\n🎁 We\'re sorry to see you leaving, we’d love to understand your decision not to use Desktop Commander.') logToFile('In return for a brief 30-minute call, we’ll send you a $20 Amazon gift card as a thank-you.'); logToFile('To get a gift card, please fill out this form:'); logToFile(' https://tally.so/r/w8lyRo'); logToFile('\nThank you for using Desktop Commander! 👋\n'); // Send final tracking event await ensureTrackingCompleted('uninstall_complete'); return true; } else { updateUninstallStep(mainStep, 'failed_config_removal'); logToFile('\n❌ Uninstallation completed with errors.'); logToFile('You may need to manually remove Desktop Commander from Claude\'s configuration.'); logToFile(`Configuration file location: ${claudeConfigPath}\n`); logToFile('\n🎁 We\'re sorry to see you leaving, we\'d love to understand your decision not to use Desktop Commander.') logToFile('In return for a brief 30-minute call, we\'ll send you a $20 Amazon gift card as a thank-you.'); logToFile('To get a gift card, please fill out this form:'); logToFile(' https://tally.so/r/w8lyRo'); await ensureTrackingCompleted('uninstall_partial_failure'); return false; } } catch (error) { updateUninstallStep(mainStep, 'fatal_error', error); await ensureTrackingCompleted('uninstall_fatal_error', { error: error.message, error_stack: error.stack, last_successful_step: uninstallSteps.filter(s => s.status === 'completed').pop()?.step || 'none' }); logToFile(`Fatal error during uninstallation: ${error.message}`, true); logToFile('\n❌ Uninstallation failed.'); logToFile('You may need to manually remove Desktop Commander from Claude\'s configuration.'); logToFile(`Configuration file location: ${claudeConfigPath}\n`); logToFile('\n🎁 We\'re sorry to see you leaving, we\'d love to understand your decision not to use Desktop Commander.') logToFile('In return for a brief 30-minute call, we\'ll send you a $20 Amazon gift card as a thank-you.'); logToFile('To get a gift card, please fill out this form:'); logToFile('https://tally.so/r/w8lyRo'); return false; } } // Allow direct execution if (process.argv.length >= 2 && process.argv[1] === fileURLToPath(import.meta.url)) { uninstall().then(success => { if (!success) { process.exit(1); } }).catch(async error => { await ensureTrackingCompleted('uninstall_execution_error', { error: error.message, error_stack: error.stack }); logToFile(`Fatal error: ${error}`, true); process.exit(1); }); }

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/wonderwhy-er/DesktopCommanderMCP'

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