- 拓展/: MCP配置向导、PowerShell工具、mcp-auto-loader 纳入版本管理 - guide.html: badges 从模糊 "90+" 改为精确 "92 Skills | 18 Agents | 34 Hooks"
497 lines
16 KiB
JavaScript
497 lines
16 KiB
JavaScript
#!/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);
|