bookworm-boot/拓展/mcp-config-wizard/wizard.js

497 lines
16 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const os = require('os');
const readline = require('readline');
// MCP 服务定义
const MCP_SERVICES = {
// 无需环境变量的服务
'playwright': {
package: '@modelcontextprotocol/server-playwright',
description: '浏览器自动化测试',
category: 'automation',
envVars: []
},
'chrome-devtools': {
package: '@executeautomation/chrome-devtools-mcp',
description: 'Chrome 开发者工具集成',
category: 'automation',
envVars: []
},
'context7': {
package: 'context7-mcp',
description: '编程文档查询',
category: 'documentation',
envVars: []
},
'deep-research': {
package: 'deep-research-mcp',
description: '深度研究助手',
category: 'research',
envVars: []
},
'sequential-thinking': {
package: '@modelcontextprotocol/server-sequential-thinking',
description: '顺序思考推理',
category: 'reasoning',
envVars: []
},
'scrapling': {
package: 'scrapling-mcp',
description: '网页抓取工具',
category: 'web',
envVars: []
},
'figma': {
package: '@figma/mcp-server-figma',
description: 'Figma 设计工具集成',
category: 'design',
envVars: [],
oauth: true
},
// 需要环境变量的服务
'github': {
package: '@modelcontextprotocol/server-github',
description: 'GitHub 代码仓库集成',
category: 'development',
envVars: ['GITHUB_PERSONAL_ACCESS_TOKEN'],
setup: 'https://github.com/settings/tokens'
},
'slack': {
package: '@modelcontextprotocol/server-slack',
description: 'Slack 团队协作',
category: 'communication',
envVars: ['SLACK_BOT_TOKEN', 'SLACK_TEAM_ID'],
setup: 'https://api.slack.com/apps'
},
'linear': {
package: '@modelcontextprotocol/server-linear',
description: 'Linear 项目管理',
category: 'productivity',
envVars: ['LINEAR_API_KEY'],
setup: 'https://linear.app/settings/api'
},
'browserbase': {
package: '@browserbasehq/mcp-server-browserbase',
description: '云端浏览器服务',
category: 'automation',
envVars: ['BROWSERBASE_PROJECT_ID', 'BROWSERBASE_API_KEY'],
setup: 'https://browserbase.com'
},
'cloudflare': {
package: '@cloudflare/mcp-server-cloudflare',
description: 'Cloudflare CDN 管理',
category: 'infrastructure',
envVars: ['CLOUDFLARE_API_TOKEN'],
setup: 'https://dash.cloudflare.com/profile/api-tokens'
},
'firecrawl': {
package: '@mendable/firecrawl-mcp',
description: '智能网页爬虫',
category: 'web',
envVars: ['FIRECRAWL_API_KEY'],
setup: 'https://firecrawl.dev'
},
'supabase': {
package: '@supabase/mcp-server-supabase',
description: 'Supabase 数据库',
category: 'database',
envVars: ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY'],
setup: 'https://supabase.com/dashboard/project/_/settings/api'
},
'sentry': {
package: '@sentry/mcp-server',
description: 'Sentry 错误监控',
category: 'monitoring',
envVars: ['SENTRY_AUTH_TOKEN'],
setup: 'https://sentry.io/settings/account/api/auth-tokens/'
},
'notion': {
package: '@notionhq/mcp-server',
description: 'Notion 知识库',
category: 'productivity',
envVars: ['NOTION_API_KEY'],
setup: 'https://www.notion.so/my-integrations'
},
'vercel': {
package: '@vercel/mcp-server',
description: 'Vercel 部署平台',
category: 'deployment',
envVars: ['VERCEL_TOKEN'],
setup: 'https://vercel.com/account/tokens'
},
'firebase': {
package: '@firebase/mcp-server',
description: 'Firebase 后端服务',
category: 'backend',
envVars: ['FIREBASE_PROJECT_ID', 'FIREBASE_PRIVATE_KEY', 'FIREBASE_CLIENT_EMAIL'],
setup: 'https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk'
}
};
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function question(prompt) {
return new Promise(resolve => rl.question(prompt, resolve));
}
async function main() {
console.log('╔════════════════════════════════════════════════════════╗');
console.log('║ MCP Configuration Wizard v1.0 ║');
console.log('║ Universal Setup Tool for Claude Code ║');
console.log('╚════════════════════════════════════════════════════════╝\n');
// 加载检测结果
let detection = null;
const detectionFile = path.join(__dirname, 'detection-result.json');
if (fs.existsSync(detectionFile)) {
detection = JSON.parse(fs.readFileSync(detectionFile, 'utf-8'));
console.log('✓ Loaded system detection results\n');
} else {
console.log('⚠ No detection results found. Run "node detect.js" first.\n');
const proceed = await question('Continue anyway? (y/n): ');
if (proceed.toLowerCase() !== 'y') {
rl.close();
return;
}
}
console.log('This wizard will help you configure MCP services for Claude Code.\n');
console.log('Available configuration modes:');
console.log(' 1. Quick Setup - Install recommended services');
console.log(' 2. Custom Setup - Choose services manually');
console.log(' 3. Import from file - Load existing configuration');
console.log(' 4. Export current config - Save for another machine\n');
const mode = await question('Select mode (1-4): ');
const config = {
mcpServers: {},
environment: {}
};
switch (mode) {
case '1':
await quickSetup(config, detection);
break;
case '2':
await customSetup(config, detection);
break;
case '3':
await importConfig(config);
break;
case '4':
await exportConfig(detection);
rl.close();
return;
default:
console.log('Invalid selection');
rl.close();
return;
}
// 生成配置文件
await generateConfig(config, detection);
rl.close();
}
async function quickSetup(config, detection) {
console.log('\n=== Quick Setup ===\n');
console.log('Installing recommended services:');
const recommended = [
'playwright', 'chrome-devtools', 'context7',
'deep-research', 'sequential-thinking', 'scrapling'
];
recommended.forEach(name => {
const service = MCP_SERVICES[name];
console.log(`${name} - ${service.description}`);
addService(config, name, service);
});
console.log('\nOptional services with API keys:');
console.log(' - github, slack, linear, supabase, etc.\n');
const addMore = await question('Configure services with API keys? (y/n): ');
if (addMore.toLowerCase() === 'y') {
await configureApiServices(config, detection);
}
}
async function customSetup(config, detection) {
console.log('\n=== Custom Setup ===\n');
console.log('Available services by category:\n');
const categories = {};
Object.entries(MCP_SERVICES).forEach(([name, service]) => {
if (!categories[service.category]) {
categories[service.category] = [];
}
categories[service.category].push({ name, ...service });
});
for (const [category, services] of Object.entries(categories)) {
console.log(`\n${category.toUpperCase()}:`);
services.forEach((s, i) => {
const envInfo = s.envVars.length > 0 ? ' (requires API key)' : '';
console.log(` ${i + 1}. ${s.name} - ${s.description}${envInfo}`);
});
}
console.log('\nEnter service names separated by commas (or "all" for all services):');
const selection = await question('Services: ');
if (selection.toLowerCase() === 'all') {
Object.entries(MCP_SERVICES).forEach(([name, service]) => {
addService(config, name, service);
});
} else {
const selected = selection.split(',').map(s => s.trim());
selected.forEach(name => {
if (MCP_SERVICES[name]) {
addService(config, name, MCP_SERVICES[name]);
}
});
}
await configureApiServices(config, detection);
}
async function configureApiServices(config, detection) {
console.log('\n=== API Key Configuration ===\n');
for (const [name, service] of Object.entries(config.mcpServers)) {
const serviceDef = MCP_SERVICES[name];
if (serviceDef.envVars && serviceDef.envVars.length > 0) {
console.log(`\n${name} requires:`);
serviceDef.envVars.forEach(v => console.log(` - ${v}`));
if (serviceDef.setup) {
console.log(` Setup: ${serviceDef.setup}`);
}
// 检查是否已有环境变量
const hasExisting = detection && serviceDef.envVars.every(v =>
detection.environment.mcpRelated.includes(v)
);
if (hasExisting) {
console.log(' ✓ Found existing environment variables');
const useExisting = await question(' Use existing? (y/n): ');
if (useExisting.toLowerCase() === 'y') {
continue;
}
}
const configure = await question(' Configure now? (y/n): ');
if (configure.toLowerCase() === 'y') {
for (const varName of serviceDef.envVars) {
const value = await question(` ${varName}: `);
if (value) {
config.environment[varName] = value;
}
}
}
}
}
}
async function importConfig(config) {
console.log('\n=== Import Configuration ===\n');
const filePath = await question('Enter path to config file (.json): ');
if (fs.existsSync(filePath)) {
const imported = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
if (imported.mcpServers) {
config.mcpServers = imported.mcpServers;
console.log(`✓ Imported ${Object.keys(config.mcpServers).length} services`);
}
if (imported.environment) {
config.environment = imported.environment;
console.log(`✓ Imported ${Object.keys(config.environment).length} environment variables`);
}
} else {
console.log('✗ File not found');
}
}
async function exportConfig(detection) {
console.log('\n=== Export Configuration ===\n');
if (!detection || !detection.claude.hasExistingConfig) {
console.log('✗ No existing configuration found');
return;
}
const configFile = path.join(os.homedir(), '.claude.json');
const config = JSON.parse(fs.readFileSync(configFile, 'utf-8'));
const exportData = {
mcpServers: config.mcpServers || {},
environment: {}
};
// 导出环境变量
if (detection.environment.mcpRelated.length > 0) {
console.log('Export environment variables?');
const exportEnv = await question('(y/n): ');
if (exportEnv.toLowerCase() === 'y') {
detection.environment.mcpRelated.forEach(varName => {
const value = process.env[varName];
if (value) {
exportData.environment[varName] = value;
}
});
}
}
const outputPath = path.join(__dirname, 'mcp-config-export.json');
fs.writeFileSync(outputPath, JSON.stringify(exportData, null, 2));
console.log(`\n✓ Configuration exported to: ${outputPath}`);
}
function addService(config, name, service) {
const isWindows = os.platform() === 'win32';
const command = isWindows ? 'cmd' : 'npx';
const args = isWindows
? ['/c', 'npx', '-y', service.package]
: ['-y', service.package];
config.mcpServers[name] = { command, args };
if (service.envVars && service.envVars.length > 0) {
config.mcpServers[name].env = {};
service.envVars.forEach(v => {
config.mcpServers[name].env[v] = `\${${v}}`;
});
}
}
async function generateConfig(config, detection) {
console.log('\n=== Generating Configuration Files ===\n');
// 1. 生成 .claude.json
const claudeConfigPath = path.join(__dirname, 'generated-claude.json');
fs.writeFileSync(claudeConfigPath, JSON.stringify({ mcpServers: config.mcpServers }, null, 2));
console.log(`✓ Generated: ${claudeConfigPath}`);
// 2. 生成环境变量文件
if (Object.keys(config.environment).length > 0) {
const envPath = path.join(__dirname, 'generated-env.json');
fs.writeFileSync(envPath, JSON.stringify(config.environment, null, 2));
console.log(`✓ Generated: ${envPath}`);
// 生成 PowerShell 脚本Windows
if (os.platform() === 'win32') {
const psScript = generatePowerShellScript(config.environment);
const psPath = path.join(__dirname, 'apply-env.ps1');
fs.writeFileSync(psPath, psScript);
console.log(`✓ Generated: ${psPath}`);
}
// 生成 Shell 脚本Unix
const shScript = generateShellScript(config.environment);
const shPath = path.join(__dirname, 'apply-env.sh');
fs.writeFileSync(shPath, shScript);
fs.chmodSync(shPath, '755');
console.log(`✓ Generated: ${shPath}`);
}
// 3. 生成安装说明
const readme = generateReadme(config, detection);
const readmePath = path.join(__dirname, 'INSTALL.md');
fs.writeFileSync(readmePath, readme);
console.log(`✓ Generated: ${readmePath}`);
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('║ Configuration Generated Successfully ║');
console.log('╚════════════════════════════════════════════════════════╝');
console.log('\nNext steps:');
console.log(' 1. Review generated files');
console.log(' 2. Run "node apply.js" to install configuration');
console.log(' 3. Restart Claude Code');
}
function generatePowerShellScript(env) {
let script = '# MCP Environment Variables Setup\n';
script += '# Generated by MCP Configuration Wizard\n\n';
script += '$ErrorActionPreference = "Continue"\n\n';
script += 'Write-Host "Setting up MCP environment variables..." -ForegroundColor Cyan\n\n';
Object.entries(env).forEach(([key, value]) => {
script += `[System.Environment]::SetEnvironmentVariable("${key}", "${value}", [System.EnvironmentVariableTarget]::User)\n`;
script += `Write-Host "[OK] ${key}" -ForegroundColor Green\n`;
});
script += '\nWrite-Host "\\nEnvironment variables configured successfully!" -ForegroundColor Green\n';
script += 'Write-Host "Please restart Claude Code to apply changes" -ForegroundColor Yellow\n';
script += 'Read-Host "\\nPress Enter to exit"\n';
return script;
}
function generateShellScript(env) {
let script = '#!/bin/bash\n';
script += '# MCP Environment Variables Setup\n';
script += '# Generated by MCP Configuration Wizard\n\n';
Object.entries(env).forEach(([key, value]) => {
script += `export ${key}="${value}"\n`;
});
script += '\necho "Environment variables configured for this session"\n';
script += 'echo "To make permanent, add these exports to your ~/.bashrc or ~/.zshrc"\n';
return script;
}
function generateReadme(config, detection) {
let readme = '# MCP Configuration Installation Guide\n\n';
readme += 'Generated by MCP Configuration Wizard\n\n';
readme += `## System Information\n\n`;
if (detection) {
readme += `- Platform: ${detection.system.platform}\n`;
readme += `- User: ${detection.system.username}\n`;
readme += `- Node.js: ${detection.system.nodeVersion}\n\n`;
}
readme += `## Configured Services (${Object.keys(config.mcpServers).length})\n\n`;
Object.keys(config.mcpServers).forEach(name => {
const service = MCP_SERVICES[name];
readme += `- **${name}**: ${service.description}\n`;
});
readme += '\n## Installation Steps\n\n';
readme += '### 1. Apply Environment Variables\n\n';
if (os.platform() === 'win32') {
readme += '**Windows:**\n```powershell\n.\\apply-env.ps1\n```\n\n';
}
readme += '**Unix/Mac:**\n```bash\nsource ./apply-env.sh\n```\n\n';
readme += '### 2. Install MCP Configuration\n\n';
readme += '```bash\nnode apply.js\n```\n\n';
readme += '### 3. Restart Claude Code\n\n';
readme += 'Close and reopen Claude Code to load the new MCP services.\n\n';
readme += '## Manual Installation\n\n';
readme += 'If automatic installation fails:\n\n';
readme += '1. Copy `generated-claude.json` content to `~/.claude.json`\n';
readme += '2. Set environment variables from `generated-env.json`\n';
readme += '3. Restart Claude Code\n';
return readme;
}
main().catch(console.error);