mcp-jarvis-config
Version:
MCP JARVIS configuration tool for Claude Desktop
536 lines (457 loc) • 19.4 kB
JavaScript
// 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);
});