@darbotlabs/darbot-windows-mcp
Version:
šŖ Darbot Windows MCP - Professional Windows desktop automation server for AI agents via Model Context Protocol (MCP). Control Windows like a human with 15 powerful tools. Includes Python dependencies, UV, and VS Code configuration.
402 lines (335 loc) ⢠15 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const { spawn } = require('child_process');
const chalk = require('chalk');
const ora = require('ora');
const inquirer = require('inquirer');
class DarbotSetup {
constructor() {
this.packageDir = path.dirname(__dirname);
this.workspaceDir = process.cwd();
}
async run() {
console.log(chalk.blue.bold('šŖ Darbot Windows MCP Setup Wizard'));
console.log(chalk.gray(' Desktop automation for AI agents'));
console.log('');
// Check system requirements
await this.checkSystemRequirements();
// Ask user preferences
const preferences = await this.getUserPreferences();
// Install dependencies
await this.installDependencies(preferences);
// Update workspace directory from user input
this.workspaceDir = preferences.workspaceDir;
// Configure VS Code
if (preferences.configureVSCode) {
await this.configureVSCode(preferences);
}
// Configure Claude Desktop
if (preferences.configureClaude) {
await this.configureClaude();
}
console.log(chalk.green.bold('\nš Setup completed successfully!'));
console.log(chalk.cyan('\nFiles created:'));
if (preferences.configureVSCode) {
console.log(chalk.gray(` ⢠${path.join(this.workspaceDir, '.vscode', 'mcp.json')}`));
console.log(chalk.gray(` ⢠${path.join(this.workspaceDir, '.vscode', 'settings.json')}`));
}
console.log(chalk.cyan('\nNext steps:'));
console.log('⢠Restart VS Code if you configured it');
console.log('⢠Restart Claude Desktop if you configured it');
console.log('⢠Run "darbot-windows-mcp --help" for usage information');
}
async checkSystemRequirements() {
const spinner = ora('Checking system requirements...').start();
try {
// Check Python
const pythonResult = await this.runCommand('python', ['--version']);
const pythonVersion = pythonResult.stdout.trim();
if (!pythonVersion.includes('3.12') && !pythonVersion.includes('3.13') && !pythonVersion.includes('3.14') && !pythonVersion.includes('3.15')) {
spinner.fail('Python 3.12+ is required');
console.log(chalk.red('Please install Python 3.12 or higher'));
process.exit(1);
}
spinner.succeed(`Found ${pythonVersion}`);
} catch (error) {
spinner.fail('Python not found');
console.log(chalk.red('Please install Python 3.12+ and ensure it\'s in your PATH'));
process.exit(1);
}
}
async getUserPreferences() {
console.log(chalk.cyan(`Current workspace: ${this.workspaceDir}`));
console.log('');
return await inquirer.prompt([
{
type: 'input',
name: 'workspaceDir',
message: 'VS Code workspace directory (press Enter to use current):',
default: this.workspaceDir,
validate: (input) => {
if (!fs.existsSync(input)) {
return 'Directory does not exist';
}
return true;
}
},
{
type: 'list',
name: 'installMethod',
message: 'Choose installation method:',
choices: [
{ name: 'UV (Recommended - faster, modern)', value: 'uv' },
{ name: 'Standard Python (pip + venv)', value: 'python' }
],
default: 'uv'
},
{
type: 'confirm',
name: 'configureVSCode',
message: 'Configure VS Code MCP integration?',
default: true
},
{
type: 'confirm',
name: 'configureClaude',
message: 'Configure Claude Desktop integration?',
default: true
}
]);
}
async installDependencies(preferences) {
if (preferences.installMethod === 'uv') {
await this.installWithUV();
} else {
await this.installWithPython();
}
}
async installWithUV() {
let spinner = ora('Installing UV...').start();
try {
// Check if UV is already installed
await this.runCommand('uv', ['--version']);
spinner.succeed('UV already installed');
} catch (error) {
try {
await this.runCommand('python', ['-m', 'pip', 'install', 'uv']);
spinner.succeed('UV installed successfully');
} catch (installError) {
spinner.fail('Failed to install UV');
throw installError;
}
}
spinner = ora('Installing Python dependencies with UV...').start();
try {
// Set UV_LINK_MODE to copy to avoid hardlink issues on OneDrive
const env = { ...process.env, UV_LINK_MODE: 'copy' };
await this.runCommand('uv', ['sync'], { cwd: this.packageDir, env });
spinner.succeed('Dependencies installed successfully');
} catch (error) {
spinner.fail('Failed to install dependencies');
throw error;
}
}
async installWithPython() {
const spinner = ora('Installing Python dependencies...').start();
try {
// Create virtual environment in package directory if it doesn't exist
const venvPath = path.join(this.packageDir, 'venv');
if (!fs.existsSync(venvPath)) {
await this.runCommand('python', ['-m', 'venv', 'venv'], { cwd: this.packageDir });
}
// Install requirements
const requirementsPath = path.join(this.packageDir, 'requirements.txt');
const pythonPath = path.join(venvPath, 'Scripts', 'python.exe');
await this.runCommand(pythonPath, ['-m', 'pip', 'install', '-r', requirementsPath]);
spinner.succeed('Dependencies installed successfully');
} catch (error) {
spinner.fail('Failed to install dependencies');
throw error;
}
}
async configureVSCode(preferences) {
const spinner = ora(`Configuring VS Code in ${this.workspaceDir}...`).start();
try {
const vscodeDir = path.join(this.workspaceDir, '.vscode');
await fs.ensureDir(vscodeDir);
// Create mcp.json using template
const mcpConfig = await this.createMCPConfigFromTemplate(preferences);
const mcpPath = path.join(vscodeDir, 'mcp.json');
await fs.writeJSON(mcpPath, mcpConfig, { spaces: 2 });
// Create/update settings.json using template
const settingsPath = path.join(vscodeDir, 'settings.json');
let settings = {};
if (fs.existsSync(settingsPath)) {
settings = await fs.readJSON(settingsPath);
}
const settingsConfig = await this.createSettingsConfigFromTemplate(preferences);
settings['mcp.servers'] = settings['mcp.servers'] || {};
settings['mcp.servers']['darbot-windows-mcp'] = settingsConfig['mcp.servers']['darbot-windows-mcp'];
await fs.writeJSON(settingsPath, settings, { spaces: 2 });
spinner.succeed(`VS Code configured successfully`);
console.log(chalk.green(` ā Created: ${mcpPath}`));
console.log(chalk.green(` ā Updated: ${settingsPath}`));
} catch (error) {
spinner.fail('Failed to configure VS Code');
throw error;
}
}
async configureClaude() {
const spinner = ora('Configuring Claude Desktop...').start();
try {
const claudeConfigDir = path.join(process.env.APPDATA, 'Claude');
const claudeConfigPath = path.join(claudeConfigDir, 'claude_desktop_config.json');
await fs.ensureDir(claudeConfigDir);
let config = {};
if (fs.existsSync(claudeConfigPath)) {
config = await fs.readJSON(claudeConfigPath);
}
config.mcpServers = config.mcpServers || {};
config.mcpServers['darbot-windows-mcp'] = {
command: 'darbot-windows-mcp',
args: []
};
await fs.writeJSON(claudeConfigPath, config, { spaces: 2 });
spinner.succeed('Claude Desktop configured successfully');
} catch (error) {
spinner.fail('Failed to configure Claude Desktop');
console.log(chalk.yellow('You may need to configure Claude Desktop manually'));
}
}
async createMCPConfigFromTemplate(preferences) {
const templatePath = path.join(this.packageDir, 'templates', 'mcp.json.template');
const template = await fs.readFile(templatePath, 'utf8');
let command, args;
if (preferences.installMethod === 'uv') {
command = 'uv';
args = [
'--directory',
this.packageDir,
'run',
'python',
path.join(this.packageDir, 'main.py')
];
} else {
const venvPython = path.join(this.packageDir, 'venv', 'Scripts', 'python.exe');
command = venvPython;
args = [path.join(this.packageDir, 'main.py')];
}
// Create proper JSON array string
const argsJsonArray = JSON.stringify(args, null, 8).replace(/\n {8}/g, '\n ');
const argsString = argsJsonArray.slice(1, -1); // Remove outer brackets
const configContent = template
.replace('"{{COMMAND}}"', JSON.stringify(command))
.replace('{{ARGS}}', argsString);
return JSON.parse(configContent);
}
async createSettingsConfigFromTemplate(preferences) {
const templatePath = path.join(this.packageDir, 'templates', 'settings.json.template');
const template = await fs.readFile(templatePath, 'utf8');
let command, args;
if (preferences.installMethod === 'uv') {
command = 'uv';
args = [
'--directory',
this.packageDir,
'run',
'python',
path.join(this.packageDir, 'main.py')
];
} else {
const venvPython = path.join(this.packageDir, 'venv', 'Scripts', 'python.exe');
command = venvPython;
args = [path.join(this.packageDir, 'main.py')];
}
// Create proper JSON array string
const argsJsonArray = JSON.stringify(args, null, 8).replace(/\n {8}/g, '\n ');
const argsString = argsJsonArray.slice(1, -1); // Remove outer brackets
const configContent = template
.replace('"{{COMMAND}}"', JSON.stringify(command))
.replace('{{ARGS}}', argsString);
return JSON.parse(configContent);
}
createMCPConfig(preferences) {
const config = {
servers: {
'darbot-windows-mcp': {
type: 'stdio'
}
},
inputs: []
};
if (preferences.installMethod === 'uv') {
config.servers['darbot-windows-mcp'].command = 'uv';
config.servers['darbot-windows-mcp'].args = [
'--directory',
this.packageDir,
'run',
'python',
path.join(this.packageDir, 'main.py')
];
} else {
const venvPython = path.join(this.packageDir, 'venv', 'Scripts', 'python.exe');
config.servers['darbot-windows-mcp'].command = venvPython;
config.servers['darbot-windows-mcp'].args = [path.join(this.packageDir, 'main.py')];
config.servers['darbot-windows-mcp'].cwd = this.packageDir;
}
return config;
}
createSettingsConfig(preferences) {
const config = {
env: {}
};
if (preferences.installMethod === 'uv') {
config.command = 'uv';
config.args = [
'--directory',
this.packageDir,
'run',
'python',
path.join(this.packageDir, 'main.py')
];
} else {
const venvPython = path.join(this.packageDir, 'venv', 'Scripts', 'python.exe');
config.command = venvPython;
config.args = [path.join(this.packageDir, 'main.py')];
config.cwd = this.packageDir;
}
return config;
}
runCommand(command, args = [], options = {}) {
return new Promise((resolve, reject) => {
const processOptions = {
stdio: 'pipe',
env: process.env,
...options
};
const childProcess = spawn(command, args, processOptions);
let stdout = '';
let stderr = '';
childProcess.stdout?.on('data', (data) => {
stdout += data.toString();
});
childProcess.stderr?.on('data', (data) => {
stderr += data.toString();
});
childProcess.on('close', (code) => {
if (code === 0) {
resolve({ stdout, stderr });
} else {
reject(new Error(`Command failed with code ${code}: ${stderr}`));
}
});
childProcess.on('error', reject);
});
}
}
// Run setup if called directly
if (require.main === module) {
const setup = new DarbotSetup();
setup.run().catch((error) => {
console.error(chalk.red('ā Setup failed:'), error.message);
process.exit(1);
});
}