UNPKG

mcp-jarvis-config

Version:

MCP JARVIS configuration tool for Claude Desktop

536 lines (457 loc) 19.4 kB
// Windows version of the MCP JARVIS Configuration Tool // This file will be similar to index.js but with Windows-specific code // For implementation, you'll need to replace AppleScript with PowerShell const fs = require('fs'); const path = require('path'); const { exec, execSync } = require('child_process'); const os = require('os'); const http = require('http'); const https = require('https'); const { findPlaceholders, replacePlaceholders, getDisplayName } = require('./process-placeholders'); // For Windows implementation, we'll use PowerShell for UI dialogs // Example placeholder function for Windows function askForPlaceholders(config, placeholders, index, replacements, callback) { const placeholderArray = Array.from(placeholders); // Base case: no more placeholders to process if (index >= placeholderArray.length) { // Apply all replacements const updatedConfig = replacePlaceholders(config, replacements); callback(updatedConfig); return; } const placeholder = placeholderArray[index]; const displayName = getDisplayName(placeholder); // PowerShell script to show a dialog with options const promptScript = ` Add-Type -AssemblyName System.Windows.Forms $form = New-Object System.Windows.Forms.Form $form.Text = "MCP JARVIS Configuration" $form.Size = New-Object System.Drawing.Size(400, 200) $form.StartPosition = "CenterScreen" $label = New-Object System.Windows.Forms.Label $label.Location = New-Object System.Drawing.Point(10, 20) $label.Size = New-Object System.Drawing.Size(380, 40) $label.Text = "Would you like to set up ${displayName}?" $form.Controls.Add($label) $skipButton = New-Object System.Windows.Forms.Button $skipButton.Location = New-Object System.Drawing.Point(100, 100) $skipButton.Size = New-Object System.Drawing.Size(100, 30) $skipButton.Text = "Skip" $skipButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel $form.Controls.Add($skipButton) $enterButton = New-Object System.Windows.Forms.Button $enterButton.Location = New-Object System.Drawing.Point(210, 100) $enterButton.Size = New-Object System.Drawing.Size(100, 30) $enterButton.Text = "Enter Value" $enterButton.DialogResult = [System.Windows.Forms.DialogResult]::OK $form.Controls.Add($enterButton) $form.AcceptButton = $enterButton $form.CancelButton = $skipButton $result = $form.ShowDialog() if ($result -eq [System.Windows.Forms.DialogResult]::OK) { Write-Host "Enter" } else { Write-Host "Skip" } `; // This is pseudocode - you'll need to adapt this for Windows // exec(`powershell -Command "${promptScript}"`, (error, stdout, stderr) => {...}); // For now, we're just providing a template console.log('Windows implementation would ask for placeholder: ' + placeholder); // Proceed to next placeholder askForPlaceholders(config, placeholders, index + 1, replacements, callback); }#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { exec, execSync } = require('child_process'); const os = require('os'); const crypto = require('crypto'); const http = require('http'); const https = require('https'); // Parse command line arguments const args = process.argv.slice(2); let customConfig = null; // Check for config URL if (args.includes('--config-url')) { const urlIndex = args.indexOf('--config-url'); if (args.length > urlIndex + 1) { const configUrl = args[urlIndex + 1]; console.log(`Fetching configuration from: ${configUrl}`); fetchConfig(configUrl) .then(config => { customConfig = config; console.log('Successfully loaded custom configuration'); startInstallation(customConfig); }) .catch(error => { console.error(`Error loading configuration: ${error.message}`); process.exit(1); }); // Exit here to prevent the normal flow from continuing return; } } // Function to fetch config from URL function fetchConfig(url) { return new Promise((resolve, reject) => { const client = url.startsWith('https') ? https : http; client.get(url, (res) => { if (res.statusCode < 200 || res.statusCode >= 300) { return reject(new Error(`Failed to fetch config: ${res.statusCode}`)); } const data = []; res.on('data', chunk => data.push(chunk)); res.on('end', () => { try { const config = JSON.parse(Buffer.concat(data).toString()); resolve(config); } catch (e) { reject(new Error(`Invalid JSON in config: ${e.message}`)); } }); }).on('error', reject); }); } // Check Node.js version const requiredNodeVersion = '20.18.2'; const currentVersion = process.version.slice(1); // Remove the 'v' prefix function compareVersions(v1, v2) { const v1parts = v1.split('.').map(Number); const v2parts = v2.split('.').map(Number); for (let i = 0; i < v1parts.length; ++i) { if (v2parts.length === i) { return 1; } if (v1parts[i] === v2parts[i]) { continue; } return v1parts[i] > v2parts[i] ? 1 : -1; } return v1parts.length === v2parts.length ? 0 : -1; } if (compareVersions(requiredNodeVersion, currentVersion) > 0) { console.error(`Error: MCP JARVIS requires Node.js version ${requiredNodeVersion} or later.`); console.error(`Current version: ${currentVersion}`); console.error(`Please update Node.js from https://nodejs.org/ and try again.`); process.exit(1); } // Check if we're on Windows if (process.platform !== 'win32') { console.error('This script is designed for Windows only. For macOS, use the standard mcp-jarvis-config command.'); process.exit(1); } // Define configuration template based on config-light.json const configTemplate = { "mcpServers": { "brave-search": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-brave-search" ], "env": { "BRAVE_API_KEY": "YOUR_API_KEY_HERE" }, "disabled": false }, "filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/downloads/folder" ] }, "youtube-transcript": { "command": "npx", "args": ["-y", "@80ai20u/mcp-youtube-transcript"] }, "markdown-downloader": { "command": "npx", "args": [ "-y", "mcp-markdown-downloader", "/downloads/folder" ] } } }; // Start the normal installation if no config URL was provided if (!args.includes('--config-url')) { startInstallation(); } function startInstallation(configOverride = null) { // Use the custom config if provided if (configOverride) { configTemplate = configOverride; } // Welcome message console.log('╔════════════════════════════════════════╗'); console.log('║ MCP JARVIS Configuration Tool ║'); console.log('╚════════════════════════════════════════╝'); console.log(''); console.log('This tool will configure MCP JARVIS on your Windows system.'); console.log(''); // Get temp directory for script files const tempDir = os.tmpdir(); const scriptId = crypto.randomBytes(8).toString('hex'); // Use PowerShell to show a folder selection dialog - create a temp script file const folderSelectionScript = ` Add-Type -AssemblyName System.Windows.Forms; $folderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog; $folderBrowser.Description = "Select your Downloads folder"; $folderBrowser.RootFolder = "MyComputer"; if ($folderBrowser.ShowDialog() -eq "OK") { Write-Output $folderBrowser.SelectedPath; } else { Write-Output "CANCELLED"; } `; const folderScriptPath = path.join(tempDir, `folder-selection-${scriptId}.ps1`); fs.writeFileSync(folderScriptPath, folderSelectionScript); try { // Execute the PowerShell script file const downloadPath = execSync(`powershell -ExecutionPolicy Bypass -File "${folderScriptPath}"`, { encoding: 'utf8' }).trim(); // Clean up the temp script file try { fs.unlinkSync(folderScriptPath); } catch (e) { /* ignore error */ } if (downloadPath === "CANCELLED") { console.log('Folder selection was cancelled. Configuration aborted.'); process.exit(0); } console.log(`Selected downloads folder: ${downloadPath}`); // Define Claude Desktop config file path for Windows const appDataDir = process.env.APPDATA || path.join(os.homedir(), 'AppData/Roaming'); const configDir = path.join(appDataDir, 'Claude'); const claudeConfigPath = path.join(configDir, 'claude_desktop_config.json'); // Check if Claude Desktop is installed if (!fs.existsSync(configDir)) { // Prompt the user to install Claude Desktop const installPromptScript = ` Add-Type -AssemblyName System.Windows.Forms; $result = [System.Windows.Forms.MessageBox]::Show( "Claude Desktop doesn't appear to be installed. You need to install it before continuing.", "Claude Desktop Required", [System.Windows.Forms.MessageBoxButtons]::OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information ); Write-Output $result; `; const installScriptPath = path.join(tempDir, `claude-check-${scriptId}.ps1`); fs.writeFileSync(installScriptPath, installPromptScript); try { const installResponse = execSync(`powershell -ExecutionPolicy Bypass -File "${installScriptPath}"`, { encoding: 'utf8' }).trim(); // Clean up the temp script file try { fs.unlinkSync(installScriptPath); } catch (e) { /* ignore error */ } if (installResponse === "OK") { // Open Claude Desktop download page execSync('start https://claude.ai/download'); console.log('Please install Claude Desktop and run this tool again.'); process.exit(0); } else { console.log('Installation canceled. Please install Claude Desktop before configuring MCP JARVIS.'); process.exit(0); } } catch (err) { console.error('Error showing dialog:', err); process.exit(1); } } // Create config directory if it doesn't exist (should not happen if Claude is installed) if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); console.log(`Created directory: ${configDir}`); } // Create backup of existing configuration if it exists and is not empty if (fs.existsSync(claudeConfigPath)) { try { const existingConfig = fs.readFileSync(claudeConfigPath, 'utf8'); // Check if the config is not empty (more than just {}) if (existingConfig.trim().length > 2) { // Create a dated backup const date = new Date(); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const backupPath = path.join(configDir, `claude_desktop_config-${year}-${month}-${day}.json`); // Don't overwrite an existing backup from the same day let finalBackupPath = backupPath; let counter = 1; while (fs.existsSync(finalBackupPath)) { finalBackupPath = path.join(configDir, `claude_desktop_config-${year}-${month}-${day}-${counter}.json`); counter++; } fs.copyFileSync(claudeConfigPath, finalBackupPath); console.log(`Backed up existing configuration to: ${finalBackupPath}`); } } catch (err) { console.log(`Warning: Could not back up existing configuration: ${err.message}`); } } // Update the configuration with the downloads folder path const config = JSON.parse(JSON.stringify(configTemplate)); // Deep clone // Update all instances of /downloads/folder with the selected path function updatePaths(obj, downloadPath) { if (Array.isArray(obj)) { for (let i = 0; i < obj.length; i++) { if (typeof obj[i] === 'string' && obj[i] === '/downloads/folder') { obj[i] = downloadPath; } else if (typeof obj[i] === 'object' && obj[i] !== null) { updatePaths(obj[i], downloadPath); } } } else if (typeof obj === 'object' && obj !== null) { for (const key in obj) { if (typeof obj[key] === 'string' && obj[key] === '/downloads/folder') { obj[key] = downloadPath; } else if (typeof obj[key] === 'object' && obj[key] !== null) { updatePaths(obj[key], downloadPath); } } } } updatePaths(config, downloadPath); // Ask for Brave API key askForBraveApiKey(config, (updatedConfig) => { // Read existing Claude Desktop config if it exists let claudeConfig = {}; if (fs.existsSync(claudeConfigPath)) { try { const existingConfig = fs.readFileSync(claudeConfigPath, 'utf8'); if (isValidJson(existingConfig)) { claudeConfig = JSON.parse(existingConfig); console.log('Loaded existing Claude Desktop configuration.'); } } catch (err) { console.log('Could not parse existing Claude Desktop config file. Creating a new one.'); } } // Update the config with MCP servers claudeConfig.mcpServers = updatedConfig.mcpServers; // Write the updated config fs.writeFileSync(claudeConfigPath, JSON.stringify(claudeConfig, null, 2)); console.log(`MCP configuration updated successfully at: ${claudeConfigPath}`); // Display success dialog using a temp script file const successScript = ` Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show( "MCP JARVIS has been successfully configured!" + [char]10 + [char]10 + "You can now start using Claude Desktop with MCP JARVIS capabilities.", "Installation Complete", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ); `; const successScriptPath = path.join(tempDir, `success-message-${scriptId}.ps1`); fs.writeFileSync(successScriptPath, successScript); try { execSync(`powershell -ExecutionPolicy Bypass -File "${successScriptPath}"`); try { fs.unlinkSync(successScriptPath); } catch (e) { /* ignore error */ } } catch (err) { console.error('Error displaying success dialog:', err); } console.log('\nMCP JARVIS is now configured and ready to use!'); console.log('You can now start using Claude Desktop with MCP JARVIS capabilities.'); }); } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); } } function askForBraveApiKey(config, callback) { // PowerShell to show a dialog with options - using a temp script file const apiKeyScript = ` Add-Type -AssemblyName System.Windows.Forms; $result = [System.Windows.Forms.MessageBox]::Show( "Would you like to set up Brave Search API?", "Brave API Setup", [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question ); Write-Output $result; `; const apiKeyScriptPath = path.join(os.tmpdir(), `brave-api-${crypto.randomBytes(8).toString('hex')}.ps1`); fs.writeFileSync(apiKeyScriptPath, apiKeyScript); try { const result = execSync(`powershell -ExecutionPolicy Bypass -File "${apiKeyScriptPath}"`, { encoding: 'utf8' }).trim(); try { fs.unlinkSync(apiKeyScriptPath); } catch (e) { /* ignore error */ } if (result !== "Yes") { // User skipped or error occurred if (config.mcpServers && config.mcpServers['brave-search']) { config.mcpServers['brave-search'].disabled = true; } callback(config); return; } // User wants to enter API key const getKeyScript = ` Add-Type -AssemblyName Microsoft.VisualBasic; $apiKey = [Microsoft.VisualBasic.Interaction]::InputBox("Enter your Brave API Key:", "Brave API Key", ""); Write-Output $apiKey; `; const getKeyScriptPath = path.join(os.tmpdir(), `brave-key-${crypto.randomBytes(8).toString('hex')}.ps1`); fs.writeFileSync(getKeyScriptPath, getKeyScript); try { const apiKey = execSync(`powershell -ExecutionPolicy Bypass -File "${getKeyScriptPath}"`, { encoding: 'utf8' }).trim(); try { fs.unlinkSync(getKeyScriptPath); } catch (e) { /* ignore error */ } if (apiKey && apiKey.length > 0) { // Update config with API key if (!config.mcpServers) config.mcpServers = {}; if (!config.mcpServers['brave-search']) config.mcpServers['brave-search'] = {}; config.mcpServers['brave-search'].env.BRAVE_API_KEY = apiKey; config.mcpServers['brave-search'].disabled = false; // Show success message console.log('Brave API Key has been set.'); } else { // Disable Brave Search if no key provided if (config.mcpServers && config.mcpServers['brave-search']) { config.mcpServers['brave-search'].disabled = true; } } callback(config); } catch (keyError) { console.error(`Error getting API key: ${keyError.message}`); if (config.mcpServers && config.mcpServers['brave-search']) { config.mcpServers['brave-search'].disabled = true; } callback(config); } } catch (error) { console.error(`Error showing API key dialog: ${error.message}`); if (config.mcpServers && config.mcpServers['brave-search']) { config.mcpServers['brave-search'].disabled = true; } callback(config); } } // Check if the given string is a valid JSON function isValidJson(str) { try { const json = JSON.parse(str); return typeof json === 'object'; } catch (e) { return false; } } // Add error handling process.on('uncaughtException', (err) => { console.error('An unexpected error occurred:'); console.error(err); // Show native dialog using a temp script file const errorScript = ` Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show( "An error occurred: ${err.message.replace(/"/g, '\\"').replace(/'/g, "''").replace(/\n/g, " ")}", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ); `; const errorScriptPath = path.join(os.tmpdir(), `error-message-${crypto.randomBytes(8).toString('hex')}.ps1`); fs.writeFileSync(errorScriptPath, errorScript); try { execSync(`powershell -ExecutionPolicy Bypass -File "${errorScriptPath}"`); try { fs.unlinkSync(errorScriptPath); } catch (e) { /* ignore error */ } } catch (dialogErr) { console.error('Failed to show error dialog:', dialogErr); } process.exit(1); });