start-vibing 2.0.11 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -177
- package/dist/cli.js +19 -2
- package/package.json +42 -42
- package/template/.claude/CLAUDE.md +174 -174
- package/template/.claude/agents/01-orchestration/agent-selector.md +130 -130
- package/template/.claude/agents/01-orchestration/checkpoint-manager.md +142 -142
- package/template/.claude/agents/01-orchestration/context-manager.md +138 -138
- package/template/.claude/agents/01-orchestration/error-recovery.md +182 -182
- package/template/.claude/agents/01-orchestration/orchestrator.md +114 -114
- package/template/.claude/agents/01-orchestration/parallel-coordinator.md +141 -141
- package/template/.claude/agents/01-orchestration/task-decomposer.md +121 -121
- package/template/.claude/agents/01-orchestration/workflow-router.md +114 -114
- package/template/.claude/agents/02-typescript/bun-runtime-expert.md +197 -197
- package/template/.claude/agents/02-typescript/esm-resolver.md +193 -193
- package/template/.claude/agents/02-typescript/import-alias-enforcer.md +158 -158
- package/template/.claude/agents/02-typescript/ts-generics-helper.md +183 -183
- package/template/.claude/agents/02-typescript/ts-migration-helper.md +238 -238
- package/template/.claude/agents/02-typescript/ts-strict-checker.md +180 -180
- package/template/.claude/agents/02-typescript/ts-types-analyzer.md +199 -199
- package/template/.claude/agents/02-typescript/type-definition-writer.md +187 -187
- package/template/.claude/agents/02-typescript/zod-schema-designer.md +212 -212
- package/template/.claude/agents/02-typescript/zod-validator.md +158 -158
- package/template/.claude/agents/03-testing/playwright-assertions.md +265 -265
- package/template/.claude/agents/03-testing/playwright-e2e.md +247 -247
- package/template/.claude/agents/03-testing/playwright-fixtures.md +234 -234
- package/template/.claude/agents/03-testing/playwright-multi-viewport.md +256 -256
- package/template/.claude/agents/03-testing/playwright-page-objects.md +247 -247
- package/template/.claude/agents/03-testing/test-cleanup-manager.md +248 -248
- package/template/.claude/agents/03-testing/test-data-generator.md +254 -254
- package/template/.claude/agents/03-testing/tester-integration.md +278 -278
- package/template/.claude/agents/03-testing/tester-unit.md +207 -207
- package/template/.claude/agents/03-testing/vitest-config.md +287 -287
- package/template/.claude/agents/04-docker/container-health.md +255 -255
- package/template/.claude/agents/04-docker/deployment-validator.md +225 -225
- package/template/.claude/agents/04-docker/docker-compose-designer.md +281 -281
- package/template/.claude/agents/04-docker/docker-env-manager.md +235 -235
- package/template/.claude/agents/04-docker/docker-multi-stage.md +241 -241
- package/template/.claude/agents/04-docker/dockerfile-optimizer.md +208 -208
- package/template/.claude/agents/05-database/database-seeder.md +273 -273
- package/template/.claude/agents/05-database/mongodb-query-optimizer.md +230 -230
- package/template/.claude/agents/05-database/mongoose-aggregation.md +306 -306
- package/template/.claude/agents/05-database/mongoose-index-optimizer.md +182 -182
- package/template/.claude/agents/05-database/mongoose-schema-designer.md +267 -267
- package/template/.claude/agents/06-security/auth-session-validator.md +68 -68
- package/template/.claude/agents/06-security/input-sanitizer.md +80 -80
- package/template/.claude/agents/06-security/owasp-checker.md +97 -97
- package/template/.claude/agents/06-security/permission-auditor.md +100 -100
- package/template/.claude/agents/06-security/security-auditor.md +84 -84
- package/template/.claude/agents/06-security/sensitive-data-scanner.md +83 -83
- package/template/.claude/agents/07-documentation/api-documenter.md +136 -136
- package/template/.claude/agents/07-documentation/changelog-manager.md +105 -105
- package/template/.claude/agents/07-documentation/documenter.md +76 -76
- package/template/.claude/agents/07-documentation/domain-updater.md +81 -81
- package/template/.claude/agents/07-documentation/jsdoc-generator.md +114 -114
- package/template/.claude/agents/07-documentation/readme-generator.md +135 -135
- package/template/.claude/agents/08-git/branch-manager.md +58 -58
- package/template/.claude/agents/08-git/commit-manager.md +63 -63
- package/template/.claude/agents/08-git/pr-creator.md +76 -76
- package/template/.claude/agents/09-quality/code-reviewer.md +71 -71
- package/template/.claude/agents/09-quality/quality-checker.md +67 -67
- package/template/.claude/agents/10-research/best-practices-finder.md +89 -89
- package/template/.claude/agents/10-research/competitor-analyzer.md +106 -106
- package/template/.claude/agents/10-research/pattern-researcher.md +93 -93
- package/template/.claude/agents/10-research/research-cache-manager.md +76 -76
- package/template/.claude/agents/10-research/research-web.md +98 -98
- package/template/.claude/agents/10-research/tech-evaluator.md +101 -101
- package/template/.claude/agents/11-ui-ux/accessibility-auditor.md +136 -136
- package/template/.claude/agents/11-ui-ux/design-system-enforcer.md +125 -125
- package/template/.claude/agents/11-ui-ux/skeleton-generator.md +118 -118
- package/template/.claude/agents/11-ui-ux/ui-desktop.md +132 -132
- package/template/.claude/agents/11-ui-ux/ui-mobile.md +98 -98
- package/template/.claude/agents/11-ui-ux/ui-tablet.md +110 -110
- package/template/.claude/agents/12-performance/api-latency-analyzer.md +156 -156
- package/template/.claude/agents/12-performance/bundle-analyzer.md +113 -113
- package/template/.claude/agents/12-performance/memory-leak-detector.md +137 -137
- package/template/.claude/agents/12-performance/performance-profiler.md +115 -115
- package/template/.claude/agents/12-performance/query-optimizer.md +124 -124
- package/template/.claude/agents/12-performance/render-optimizer.md +154 -154
- package/template/.claude/agents/13-debugging/build-error-fixer.md +207 -207
- package/template/.claude/agents/13-debugging/debugger.md +149 -149
- package/template/.claude/agents/13-debugging/error-stack-analyzer.md +141 -141
- package/template/.claude/agents/13-debugging/network-debugger.md +208 -208
- package/template/.claude/agents/13-debugging/runtime-error-fixer.md +181 -181
- package/template/.claude/agents/13-debugging/type-error-resolver.md +185 -185
- package/template/.claude/agents/14-validation/final-validator.md +93 -93
- package/template/.claude/agents/_backup/analyzer.md +134 -134
- package/template/.claude/agents/_backup/code-reviewer.md +279 -279
- package/template/.claude/agents/_backup/commit-manager.md +219 -219
- package/template/.claude/agents/_backup/debugger.md +280 -280
- package/template/.claude/agents/_backup/documenter.md +237 -237
- package/template/.claude/agents/_backup/domain-updater.md +197 -197
- package/template/.claude/agents/_backup/final-validator.md +169 -169
- package/template/.claude/agents/_backup/orchestrator.md +149 -149
- package/template/.claude/agents/_backup/performance.md +232 -232
- package/template/.claude/agents/_backup/quality-checker.md +240 -240
- package/template/.claude/agents/_backup/research.md +315 -315
- package/template/.claude/agents/_backup/security-auditor.md +192 -192
- package/template/.claude/agents/_backup/tester.md +566 -566
- package/template/.claude/agents/_backup/ui-ux-reviewer.md +247 -247
- package/template/.claude/config/README.md +30 -30
- package/template/.claude/config/mcp-config.json +344 -344
- package/template/.claude/config/project-config.json +53 -53
- package/template/.claude/config/quality-gates.json +46 -46
- package/template/.claude/config/security-rules.json +45 -45
- package/template/.claude/config/testing-config.json +164 -164
- package/template/.claude/hooks/SETUP.md +126 -126
- package/template/.claude/hooks/run-hook.ts +176 -176
- package/template/.claude/hooks/stop-validator.ts +914 -824
- package/template/.claude/hooks/user-prompt-submit.ts +886 -886
- package/template/.claude/scripts/mcp-quick-install.ts +151 -151
- package/template/.claude/scripts/setup-mcps.ts +651 -651
- package/template/.claude/settings.json +275 -275
- package/template/.claude/skills/bun-runtime/SKILL.md +430 -430
- package/template/.claude/skills/codebase-knowledge/domains/claude-system.md +431 -431
- package/template/.claude/skills/codebase-knowledge/domains/mcp-integration.md +295 -295
- package/template/.claude/skills/debugging-patterns/SKILL.md +485 -485
- package/template/.claude/skills/docker-patterns/SKILL.md +555 -555
- package/template/.claude/skills/git-workflow/SKILL.md +454 -454
- package/template/.claude/skills/mongoose-patterns/SKILL.md +499 -499
- package/template/.claude/skills/nextjs-app-router/SKILL.md +327 -327
- package/template/.claude/skills/performance-patterns/SKILL.md +547 -547
- package/template/.claude/skills/playwright-automation/SKILL.md +438 -438
- package/template/.claude/skills/react-patterns/SKILL.md +389 -389
- package/template/.claude/skills/research-cache/SKILL.md +222 -222
- package/template/.claude/skills/shadcn-ui/SKILL.md +511 -511
- package/template/.claude/skills/tailwind-patterns/SKILL.md +465 -465
- package/template/.claude/skills/test-coverage/SKILL.md +467 -467
- package/template/.claude/skills/trpc-api/SKILL.md +434 -434
- package/template/.claude/skills/typescript-strict/SKILL.md +367 -367
- package/template/.claude/skills/zod-validation/SKILL.md +403 -403
- package/template/CLAUDE.md +117 -117
|
@@ -1,651 +1,651 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* MCP Auto-Installer for Claude Code
|
|
4
|
-
*
|
|
5
|
-
* This script automatically installs and configures recommended MCP servers
|
|
6
|
-
* based on the project's agent/skill architecture.
|
|
7
|
-
*
|
|
8
|
-
* Features:
|
|
9
|
-
* - Parallel installation for speed
|
|
10
|
-
* - Progress tracking with visual feedback
|
|
11
|
-
* - Security validation before installation
|
|
12
|
-
* - Automatic .mcp.json generation
|
|
13
|
-
* - Environment variable setup guidance
|
|
14
|
-
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* bun .claude/scripts/setup-mcps.ts [options]
|
|
17
|
-
*
|
|
18
|
-
* Options:
|
|
19
|
-
* --tier=core|productivity|infrastructure|all Install specific tier (default: core)
|
|
20
|
-
* --dry-run Show what would be installed
|
|
21
|
-
* --force Reinstall even if already configured
|
|
22
|
-
* --interactive Prompt for each server
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { spawn, spawnSync } from 'child_process';
|
|
26
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
27
|
-
import { join, dirname } from 'path';
|
|
28
|
-
import { fileURLToPath } from 'url';
|
|
29
|
-
|
|
30
|
-
// Types
|
|
31
|
-
interface MCPServerConfig {
|
|
32
|
-
name: string;
|
|
33
|
-
description: string;
|
|
34
|
-
tier: 'core' | 'productivity' | 'infrastructure';
|
|
35
|
-
verified: boolean;
|
|
36
|
-
publisher: string;
|
|
37
|
-
repository: string;
|
|
38
|
-
transport: 'stdio' | 'http';
|
|
39
|
-
config: {
|
|
40
|
-
command?: string;
|
|
41
|
-
args?: string[];
|
|
42
|
-
url?: string;
|
|
43
|
-
options?: Record<string, unknown>;
|
|
44
|
-
remote?: { url: string };
|
|
45
|
-
local?: { command: string; args: string[] };
|
|
46
|
-
};
|
|
47
|
-
envVars: string[];
|
|
48
|
-
requiredPermissions: string[];
|
|
49
|
-
agentMappings: string[];
|
|
50
|
-
securityNotes: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface MCPConfig {
|
|
54
|
-
metadata: {
|
|
55
|
-
version: string;
|
|
56
|
-
lastUpdated: string;
|
|
57
|
-
researchSources: string[];
|
|
58
|
-
};
|
|
59
|
-
tiers: Record<string, { description: string; servers: string[] }>;
|
|
60
|
-
servers: Record<string, MCPServerConfig>;
|
|
61
|
-
security: {
|
|
62
|
-
guidelines: string[];
|
|
63
|
-
redFlags: string[];
|
|
64
|
-
trustedPublishers: string[];
|
|
65
|
-
};
|
|
66
|
-
installation: {
|
|
67
|
-
parallelLimit: number;
|
|
68
|
-
timeout: number;
|
|
69
|
-
retryAttempts: number;
|
|
70
|
-
scope: string;
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface InstallResult {
|
|
75
|
-
server: string;
|
|
76
|
-
success: boolean;
|
|
77
|
-
message: string;
|
|
78
|
-
duration: number;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
interface MCPJsonEntry {
|
|
82
|
-
command?: string;
|
|
83
|
-
args?: string[];
|
|
84
|
-
url?: string;
|
|
85
|
-
env?: Record<string, string>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Console colors (ANSI escape codes)
|
|
89
|
-
const colors = {
|
|
90
|
-
reset: '\x1b[0m',
|
|
91
|
-
bright: '\x1b[1m',
|
|
92
|
-
dim: '\x1b[2m',
|
|
93
|
-
red: '\x1b[31m',
|
|
94
|
-
green: '\x1b[32m',
|
|
95
|
-
yellow: '\x1b[33m',
|
|
96
|
-
blue: '\x1b[34m',
|
|
97
|
-
magenta: '\x1b[35m',
|
|
98
|
-
cyan: '\x1b[36m',
|
|
99
|
-
white: '\x1b[37m',
|
|
100
|
-
bgGreen: '\x1b[42m',
|
|
101
|
-
bgRed: '\x1b[41m',
|
|
102
|
-
bgYellow: '\x1b[43m',
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// Get script directory
|
|
106
|
-
const getScriptDir = (): string => {
|
|
107
|
-
try {
|
|
108
|
-
if (typeof import.meta.url !== 'undefined') {
|
|
109
|
-
return dirname(fileURLToPath(import.meta.url));
|
|
110
|
-
}
|
|
111
|
-
} catch {
|
|
112
|
-
// Fallback
|
|
113
|
-
}
|
|
114
|
-
return process.cwd();
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const SCRIPT_DIR = getScriptDir();
|
|
118
|
-
const PROJECT_ROOT = join(SCRIPT_DIR, '..', '..');
|
|
119
|
-
const CONFIG_PATH = join(SCRIPT_DIR, '..', 'config', 'mcp-config.json');
|
|
120
|
-
const MCP_JSON_PATH = join(PROJECT_ROOT, '.mcp.json');
|
|
121
|
-
|
|
122
|
-
// Utility functions
|
|
123
|
-
function log(message: string, type: 'info' | 'success' | 'warning' | 'error' = 'info'): void {
|
|
124
|
-
const prefix = {
|
|
125
|
-
info: `${colors.blue}[INFO]${colors.reset}`,
|
|
126
|
-
success: `${colors.green}[OK]${colors.reset}`,
|
|
127
|
-
warning: `${colors.yellow}[WARN]${colors.reset}`,
|
|
128
|
-
error: `${colors.red}[ERROR]${colors.reset}`,
|
|
129
|
-
};
|
|
130
|
-
console.log(`${prefix[type]} ${message}`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function header(text: string): void {
|
|
134
|
-
const line = '='.repeat(60);
|
|
135
|
-
console.log(`\n${colors.cyan}${line}${colors.reset}`);
|
|
136
|
-
console.log(`${colors.bright}${colors.cyan} ${text}${colors.reset}`);
|
|
137
|
-
console.log(`${colors.cyan}${line}${colors.reset}\n`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function subheader(text: string): void {
|
|
141
|
-
console.log(`\n${colors.magenta}>> ${text}${colors.reset}`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function progressBar(current: number, total: number, width = 40): string {
|
|
145
|
-
const percent = Math.round((current / total) * 100);
|
|
146
|
-
const filled = Math.round((current / total) * width);
|
|
147
|
-
const empty = width - filled;
|
|
148
|
-
const bar = `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
|
|
149
|
-
return `${bar} ${percent}% (${current}/${total})`;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Load configuration
|
|
153
|
-
function loadConfig(): MCPConfig {
|
|
154
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
155
|
-
throw new Error(`MCP config not found at: ${CONFIG_PATH}`);
|
|
156
|
-
}
|
|
157
|
-
const content = readFileSync(CONFIG_PATH, 'utf8');
|
|
158
|
-
return JSON.parse(content) as MCPConfig;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Load existing .mcp.json
|
|
162
|
-
function loadMcpJson(): Record<string, MCPJsonEntry> {
|
|
163
|
-
if (!existsSync(MCP_JSON_PATH)) {
|
|
164
|
-
return {};
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
const content = readFileSync(MCP_JSON_PATH, 'utf8');
|
|
168
|
-
return JSON.parse(content) as Record<string, MCPJsonEntry>;
|
|
169
|
-
} catch {
|
|
170
|
-
return {};
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Save .mcp.json
|
|
175
|
-
function saveMcpJson(config: Record<string, MCPJsonEntry>): void {
|
|
176
|
-
writeFileSync(MCP_JSON_PATH, JSON.stringify(config, null, 2));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Check if a command exists
|
|
180
|
-
function commandExists(cmd: string): boolean {
|
|
181
|
-
try {
|
|
182
|
-
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], {
|
|
183
|
-
stdio: 'pipe',
|
|
184
|
-
shell: true,
|
|
185
|
-
});
|
|
186
|
-
return result.status === 0;
|
|
187
|
-
} catch {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Validate security
|
|
193
|
-
function validateSecurity(
|
|
194
|
-
server: MCPServerConfig,
|
|
195
|
-
config: MCPConfig
|
|
196
|
-
): { valid: boolean; warnings: string[] } {
|
|
197
|
-
const warnings: string[] = [];
|
|
198
|
-
|
|
199
|
-
// Check if publisher is trusted
|
|
200
|
-
if (!config.security.trustedPublishers.includes(server.publisher)) {
|
|
201
|
-
warnings.push(`Publisher '${server.publisher}' is not in trusted list`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Check if verified
|
|
205
|
-
if (!server.verified) {
|
|
206
|
-
warnings.push('Server is not verified');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Check for missing repository
|
|
210
|
-
if (!server.repository) {
|
|
211
|
-
warnings.push('No repository URL provided');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
valid: warnings.length === 0,
|
|
216
|
-
warnings,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Install a single MCP server using claude mcp add
|
|
221
|
-
async function installServer(
|
|
222
|
-
serverId: string,
|
|
223
|
-
server: MCPServerConfig,
|
|
224
|
-
scope: string,
|
|
225
|
-
dryRun: boolean
|
|
226
|
-
): Promise<InstallResult> {
|
|
227
|
-
const startTime = Date.now();
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
// Check if claude CLI is available
|
|
231
|
-
if (!commandExists('claude')) {
|
|
232
|
-
return {
|
|
233
|
-
server: serverId,
|
|
234
|
-
success: false,
|
|
235
|
-
message: 'Claude CLI not found. Please install Claude Code first.',
|
|
236
|
-
duration: Date.now() - startTime,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Build the command based on transport type
|
|
241
|
-
let args: string[];
|
|
242
|
-
|
|
243
|
-
if (server.transport === 'http' && server.config.url) {
|
|
244
|
-
// Remote HTTP server
|
|
245
|
-
args = ['mcp', 'add', '--transport', 'http', '-s', scope, serverId, server.config.url];
|
|
246
|
-
} else if (server.transport === 'http' && server.config.remote?.url) {
|
|
247
|
-
// Remote server with remote config
|
|
248
|
-
args = [
|
|
249
|
-
'mcp',
|
|
250
|
-
'add',
|
|
251
|
-
'--transport',
|
|
252
|
-
'http',
|
|
253
|
-
'-s',
|
|
254
|
-
scope,
|
|
255
|
-
serverId,
|
|
256
|
-
server.config.remote.url,
|
|
257
|
-
];
|
|
258
|
-
} else if (server.config.command && server.config.args) {
|
|
259
|
-
// Local stdio server
|
|
260
|
-
args = [
|
|
261
|
-
'mcp',
|
|
262
|
-
'add',
|
|
263
|
-
'-s',
|
|
264
|
-
scope,
|
|
265
|
-
serverId,
|
|
266
|
-
'--',
|
|
267
|
-
server.config.command,
|
|
268
|
-
...server.config.args,
|
|
269
|
-
];
|
|
270
|
-
} else {
|
|
271
|
-
return {
|
|
272
|
-
server: serverId,
|
|
273
|
-
success: false,
|
|
274
|
-
message: 'Invalid server configuration',
|
|
275
|
-
duration: Date.now() - startTime,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (dryRun) {
|
|
280
|
-
return {
|
|
281
|
-
server: serverId,
|
|
282
|
-
success: true,
|
|
283
|
-
message: `Would run: claude ${args.join(' ')}`,
|
|
284
|
-
duration: Date.now() - startTime,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Execute the command
|
|
289
|
-
return new Promise((resolve) => {
|
|
290
|
-
const proc = spawn('claude', args, {
|
|
291
|
-
shell: true,
|
|
292
|
-
stdio: 'pipe',
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
let stdout = '';
|
|
296
|
-
let stderr = '';
|
|
297
|
-
|
|
298
|
-
proc.stdout?.on('data', (data) => {
|
|
299
|
-
stdout += data.toString();
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
proc.stderr?.on('data', (data) => {
|
|
303
|
-
stderr += data.toString();
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
proc.on('close', (code) => {
|
|
307
|
-
const duration = Date.now() - startTime;
|
|
308
|
-
if (code === 0) {
|
|
309
|
-
resolve({
|
|
310
|
-
server: serverId,
|
|
311
|
-
success: true,
|
|
312
|
-
message: 'Installed successfully',
|
|
313
|
-
duration,
|
|
314
|
-
});
|
|
315
|
-
} else {
|
|
316
|
-
resolve({
|
|
317
|
-
server: serverId,
|
|
318
|
-
success: false,
|
|
319
|
-
message: stderr || stdout || `Exit code: ${code}`,
|
|
320
|
-
duration,
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
proc.on('error', (err) => {
|
|
326
|
-
resolve({
|
|
327
|
-
server: serverId,
|
|
328
|
-
success: false,
|
|
329
|
-
message: err.message,
|
|
330
|
-
duration: Date.now() - startTime,
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Timeout
|
|
335
|
-
setTimeout(() => {
|
|
336
|
-
proc.kill();
|
|
337
|
-
resolve({
|
|
338
|
-
server: serverId,
|
|
339
|
-
success: false,
|
|
340
|
-
message: 'Installation timed out',
|
|
341
|
-
duration: Date.now() - startTime,
|
|
342
|
-
});
|
|
343
|
-
}, 60000);
|
|
344
|
-
});
|
|
345
|
-
} catch (err) {
|
|
346
|
-
return {
|
|
347
|
-
server: serverId,
|
|
348
|
-
success: false,
|
|
349
|
-
message: err instanceof Error ? err.message : 'Unknown error',
|
|
350
|
-
duration: Date.now() - startTime,
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Install servers in parallel with limit
|
|
356
|
-
async function installServersParallel(
|
|
357
|
-
servers: Array<{ id: string; config: MCPServerConfig }>,
|
|
358
|
-
scope: string,
|
|
359
|
-
parallelLimit: number,
|
|
360
|
-
dryRun: boolean
|
|
361
|
-
): Promise<InstallResult[]> {
|
|
362
|
-
const results: InstallResult[] = [];
|
|
363
|
-
let completed = 0;
|
|
364
|
-
|
|
365
|
-
// Process in batches
|
|
366
|
-
for (let i = 0; i < servers.length; i += parallelLimit) {
|
|
367
|
-
const batch = servers.slice(i, i + parallelLimit);
|
|
368
|
-
|
|
369
|
-
const batchResults = await Promise.all(
|
|
370
|
-
batch.map(async ({ id, config: serverConfig }) => {
|
|
371
|
-
const result = await installServer(id, serverConfig, scope, dryRun);
|
|
372
|
-
completed++;
|
|
373
|
-
|
|
374
|
-
// Update progress
|
|
375
|
-
const status = result.success
|
|
376
|
-
? `${colors.green}OK${colors.reset}`
|
|
377
|
-
: `${colors.red}FAIL${colors.reset}`;
|
|
378
|
-
console.log(` ${progressBar(completed, servers.length)} ${id}: ${status}`);
|
|
379
|
-
|
|
380
|
-
return result;
|
|
381
|
-
})
|
|
382
|
-
);
|
|
383
|
-
|
|
384
|
-
results.push(...batchResults);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return results;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Generate .mcp.json for project sharing
|
|
391
|
-
function generateMcpJson(
|
|
392
|
-
servers: Array<{ id: string; config: MCPServerConfig }>,
|
|
393
|
-
existingConfig: Record<string, MCPJsonEntry>
|
|
394
|
-
): Record<string, MCPJsonEntry> {
|
|
395
|
-
const mcpJson = { ...existingConfig };
|
|
396
|
-
|
|
397
|
-
for (const { id, config: server } of servers) {
|
|
398
|
-
if (server.transport === 'stdio' && server.config.command && server.config.args) {
|
|
399
|
-
const entry: MCPJsonEntry = {
|
|
400
|
-
command: server.config.command,
|
|
401
|
-
args: server.config.args,
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
// Add environment variables
|
|
405
|
-
if (server.envVars.length > 0) {
|
|
406
|
-
entry.env = {};
|
|
407
|
-
for (const envVar of server.envVars) {
|
|
408
|
-
entry.env[envVar] = `\${${envVar}}`;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
mcpJson[id] = entry;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return mcpJson;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Main function
|
|
420
|
-
async function main(): Promise<void> {
|
|
421
|
-
const args = process.argv.slice(2);
|
|
422
|
-
const options = {
|
|
423
|
-
tier: 'core' as string,
|
|
424
|
-
dryRun: false,
|
|
425
|
-
force: false,
|
|
426
|
-
interactive: false,
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
// Parse arguments
|
|
430
|
-
for (const arg of args) {
|
|
431
|
-
if (arg.startsWith('--tier=')) {
|
|
432
|
-
options.tier = arg.split('=')[1] || 'core';
|
|
433
|
-
} else if (arg === '--dry-run') {
|
|
434
|
-
options.dryRun = true;
|
|
435
|
-
} else if (arg === '--force') {
|
|
436
|
-
options.force = true;
|
|
437
|
-
} else if (arg === '--interactive') {
|
|
438
|
-
options.interactive = true;
|
|
439
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
440
|
-
console.log(`
|
|
441
|
-
${colors.bright}MCP Auto-Installer for Claude Code${colors.reset}
|
|
442
|
-
|
|
443
|
-
Usage: bun .claude/scripts/setup-mcps.ts [options]
|
|
444
|
-
|
|
445
|
-
Options:
|
|
446
|
-
--tier=<tier> Install specific tier: core, productivity, infrastructure, all
|
|
447
|
-
(default: core)
|
|
448
|
-
--dry-run Show what would be installed without actually installing
|
|
449
|
-
--force Reinstall even if already configured
|
|
450
|
-
--interactive Prompt before each installation
|
|
451
|
-
--help, -h Show this help message
|
|
452
|
-
|
|
453
|
-
Examples:
|
|
454
|
-
bun .claude/scripts/setup-mcps.ts # Install core MCPs
|
|
455
|
-
bun .claude/scripts/setup-mcps.ts --tier=all # Install all MCPs
|
|
456
|
-
bun .claude/scripts/setup-mcps.ts --dry-run # Preview installation
|
|
457
|
-
bun .claude/scripts/setup-mcps.ts --tier=productivity --force
|
|
458
|
-
`);
|
|
459
|
-
process.exit(0);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
header('MCP Auto-Installer for Claude Code');
|
|
464
|
-
|
|
465
|
-
// Load configuration
|
|
466
|
-
log('Loading MCP configuration...');
|
|
467
|
-
let config: MCPConfig;
|
|
468
|
-
try {
|
|
469
|
-
config = loadConfig();
|
|
470
|
-
log(`Found ${Object.keys(config.servers).length} configured MCP servers`, 'success');
|
|
471
|
-
} catch (err) {
|
|
472
|
-
log(
|
|
473
|
-
`Failed to load config: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
474
|
-
'error'
|
|
475
|
-
);
|
|
476
|
-
process.exit(1);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Check prerequisites
|
|
480
|
-
subheader('Checking Prerequisites');
|
|
481
|
-
|
|
482
|
-
if (!commandExists('claude')) {
|
|
483
|
-
log('Claude CLI not found. Please install Claude Code first.', 'error');
|
|
484
|
-
log('Visit: https://claude.ai/code', 'info');
|
|
485
|
-
process.exit(1);
|
|
486
|
-
}
|
|
487
|
-
log('Claude CLI found', 'success');
|
|
488
|
-
|
|
489
|
-
if (!commandExists('npx')) {
|
|
490
|
-
log('npx not found. Please install Node.js.', 'error');
|
|
491
|
-
process.exit(1);
|
|
492
|
-
}
|
|
493
|
-
log('npx found', 'success');
|
|
494
|
-
|
|
495
|
-
// Determine which servers to install
|
|
496
|
-
subheader('Selecting Servers');
|
|
497
|
-
|
|
498
|
-
const tiersToInstall = options.tier === 'all' ? Object.keys(config.tiers) : [options.tier];
|
|
499
|
-
|
|
500
|
-
const serversToInstall: Array<{ id: string; config: MCPServerConfig }> = [];
|
|
501
|
-
|
|
502
|
-
for (const tier of tiersToInstall) {
|
|
503
|
-
const tierConfig = config.tiers[tier];
|
|
504
|
-
if (!tierConfig) {
|
|
505
|
-
log(`Unknown tier: ${tier}`, 'warning');
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
log(`${colors.bright}Tier: ${tier}${colors.reset} - ${tierConfig.description}`);
|
|
510
|
-
|
|
511
|
-
for (const serverId of tierConfig.servers) {
|
|
512
|
-
const server = config.servers[serverId];
|
|
513
|
-
if (!server) {
|
|
514
|
-
log(` Server not found: ${serverId}`, 'warning');
|
|
515
|
-
continue;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
console.log(` ${colors.cyan}${serverId}${colors.reset}: ${server.description}`);
|
|
519
|
-
serversToInstall.push({ id: serverId, config: server });
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (serversToInstall.length === 0) {
|
|
524
|
-
log('No servers selected for installation', 'warning');
|
|
525
|
-
process.exit(0);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
log(`\nTotal servers to install: ${serversToInstall.length}`, 'info');
|
|
529
|
-
|
|
530
|
-
// Security validation
|
|
531
|
-
subheader('Security Validation');
|
|
532
|
-
|
|
533
|
-
const securityIssues: Array<{ server: string; warnings: string[] }> = [];
|
|
534
|
-
|
|
535
|
-
for (const { id, config: server } of serversToInstall) {
|
|
536
|
-
const validation = validateSecurity(server, config);
|
|
537
|
-
if (!validation.valid) {
|
|
538
|
-
securityIssues.push({ server: id, warnings: validation.warnings });
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
if (securityIssues.length > 0) {
|
|
543
|
-
log('Security warnings found:', 'warning');
|
|
544
|
-
for (const issue of securityIssues) {
|
|
545
|
-
console.log(` ${colors.yellow}${issue.server}${colors.reset}:`);
|
|
546
|
-
for (const warning of issue.warnings) {
|
|
547
|
-
console.log(` - ${warning}`);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
console.log();
|
|
551
|
-
} else {
|
|
552
|
-
log('All servers passed security validation', 'success');
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Environment variables check
|
|
556
|
-
subheader('Environment Variables');
|
|
557
|
-
|
|
558
|
-
const requiredEnvVars = new Set<string>();
|
|
559
|
-
for (const { config: server } of serversToInstall) {
|
|
560
|
-
for (const envVar of server.envVars) {
|
|
561
|
-
requiredEnvVars.add(envVar);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (requiredEnvVars.size > 0) {
|
|
566
|
-
log('The following environment variables are required:', 'info');
|
|
567
|
-
for (const envVar of requiredEnvVars) {
|
|
568
|
-
const isSet = !!process.env[envVar];
|
|
569
|
-
const status = isSet
|
|
570
|
-
? `${colors.green}SET${colors.reset}`
|
|
571
|
-
: `${colors.yellow}NOT SET${colors.reset}`;
|
|
572
|
-
console.log(` ${envVar}: ${status}`);
|
|
573
|
-
}
|
|
574
|
-
console.log();
|
|
575
|
-
log('Tip: Set these in your .env file or system environment', 'info');
|
|
576
|
-
} else {
|
|
577
|
-
log('No environment variables required for selected servers', 'success');
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Installation
|
|
581
|
-
subheader(`${options.dryRun ? 'Dry Run - ' : ''}Installing MCP Servers`);
|
|
582
|
-
|
|
583
|
-
console.log();
|
|
584
|
-
const results = await installServersParallel(
|
|
585
|
-
serversToInstall,
|
|
586
|
-
config.installation.scope,
|
|
587
|
-
config.installation.parallelLimit,
|
|
588
|
-
options.dryRun
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
// Generate .mcp.json for team sharing
|
|
592
|
-
if (!options.dryRun) {
|
|
593
|
-
subheader('Generating .mcp.json');
|
|
594
|
-
|
|
595
|
-
const existingMcpJson = loadMcpJson();
|
|
596
|
-
const newMcpJson = generateMcpJson(serversToInstall, existingMcpJson);
|
|
597
|
-
saveMcpJson(newMcpJson);
|
|
598
|
-
log(`Generated ${MCP_JSON_PATH} for team sharing`, 'success');
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// Summary
|
|
602
|
-
header('Installation Summary');
|
|
603
|
-
|
|
604
|
-
const successful = results.filter((r) => r.success);
|
|
605
|
-
const failed = results.filter((r) => !r.success);
|
|
606
|
-
|
|
607
|
-
console.log(`${colors.green}Successful: ${successful.length}${colors.reset}`);
|
|
608
|
-
for (const result of successful) {
|
|
609
|
-
console.log(` ${colors.green}✓${colors.reset} ${result.server} (${result.duration}ms)`);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
if (failed.length > 0) {
|
|
613
|
-
console.log(`\n${colors.red}Failed: ${failed.length}${colors.reset}`);
|
|
614
|
-
for (const result of failed) {
|
|
615
|
-
console.log(` ${colors.red}✗${colors.reset} ${result.server}: ${result.message}`);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// Next steps
|
|
620
|
-
if (!options.dryRun) {
|
|
621
|
-
subheader('Next Steps');
|
|
622
|
-
|
|
623
|
-
console.log(`
|
|
624
|
-
1. ${colors.cyan}Verify installation:${colors.reset}
|
|
625
|
-
claude mcp list
|
|
626
|
-
|
|
627
|
-
2. ${colors.cyan}Test a server:${colors.reset}
|
|
628
|
-
claude mcp get <server-name>
|
|
629
|
-
|
|
630
|
-
3. ${colors.cyan}Set missing environment variables:${colors.reset}
|
|
631
|
-
Create a .env file with required variables
|
|
632
|
-
|
|
633
|
-
4. ${colors.cyan}Share with team:${colors.reset}
|
|
634
|
-
Commit .mcp.json to version control
|
|
635
|
-
|
|
636
|
-
5. ${colors.cyan}Debug issues:${colors.reset}
|
|
637
|
-
claude --mcp-debug
|
|
638
|
-
`);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Exit code
|
|
642
|
-
if (failed.length > 0 && !options.dryRun) {
|
|
643
|
-
process.exit(1);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// Run
|
|
648
|
-
main().catch((err) => {
|
|
649
|
-
console.error(`${colors.red}Fatal error:${colors.reset}`, err);
|
|
650
|
-
process.exit(1);
|
|
651
|
-
});
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* MCP Auto-Installer for Claude Code
|
|
4
|
+
*
|
|
5
|
+
* This script automatically installs and configures recommended MCP servers
|
|
6
|
+
* based on the project's agent/skill architecture.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Parallel installation for speed
|
|
10
|
+
* - Progress tracking with visual feedback
|
|
11
|
+
* - Security validation before installation
|
|
12
|
+
* - Automatic .mcp.json generation
|
|
13
|
+
* - Environment variable setup guidance
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* bun .claude/scripts/setup-mcps.ts [options]
|
|
17
|
+
*
|
|
18
|
+
* Options:
|
|
19
|
+
* --tier=core|productivity|infrastructure|all Install specific tier (default: core)
|
|
20
|
+
* --dry-run Show what would be installed
|
|
21
|
+
* --force Reinstall even if already configured
|
|
22
|
+
* --interactive Prompt for each server
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { spawn, spawnSync } from 'child_process';
|
|
26
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
27
|
+
import { join, dirname } from 'path';
|
|
28
|
+
import { fileURLToPath } from 'url';
|
|
29
|
+
|
|
30
|
+
// Types
|
|
31
|
+
interface MCPServerConfig {
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
tier: 'core' | 'productivity' | 'infrastructure';
|
|
35
|
+
verified: boolean;
|
|
36
|
+
publisher: string;
|
|
37
|
+
repository: string;
|
|
38
|
+
transport: 'stdio' | 'http';
|
|
39
|
+
config: {
|
|
40
|
+
command?: string;
|
|
41
|
+
args?: string[];
|
|
42
|
+
url?: string;
|
|
43
|
+
options?: Record<string, unknown>;
|
|
44
|
+
remote?: { url: string };
|
|
45
|
+
local?: { command: string; args: string[] };
|
|
46
|
+
};
|
|
47
|
+
envVars: string[];
|
|
48
|
+
requiredPermissions: string[];
|
|
49
|
+
agentMappings: string[];
|
|
50
|
+
securityNotes: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface MCPConfig {
|
|
54
|
+
metadata: {
|
|
55
|
+
version: string;
|
|
56
|
+
lastUpdated: string;
|
|
57
|
+
researchSources: string[];
|
|
58
|
+
};
|
|
59
|
+
tiers: Record<string, { description: string; servers: string[] }>;
|
|
60
|
+
servers: Record<string, MCPServerConfig>;
|
|
61
|
+
security: {
|
|
62
|
+
guidelines: string[];
|
|
63
|
+
redFlags: string[];
|
|
64
|
+
trustedPublishers: string[];
|
|
65
|
+
};
|
|
66
|
+
installation: {
|
|
67
|
+
parallelLimit: number;
|
|
68
|
+
timeout: number;
|
|
69
|
+
retryAttempts: number;
|
|
70
|
+
scope: string;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface InstallResult {
|
|
75
|
+
server: string;
|
|
76
|
+
success: boolean;
|
|
77
|
+
message: string;
|
|
78
|
+
duration: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface MCPJsonEntry {
|
|
82
|
+
command?: string;
|
|
83
|
+
args?: string[];
|
|
84
|
+
url?: string;
|
|
85
|
+
env?: Record<string, string>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Console colors (ANSI escape codes)
|
|
89
|
+
const colors = {
|
|
90
|
+
reset: '\x1b[0m',
|
|
91
|
+
bright: '\x1b[1m',
|
|
92
|
+
dim: '\x1b[2m',
|
|
93
|
+
red: '\x1b[31m',
|
|
94
|
+
green: '\x1b[32m',
|
|
95
|
+
yellow: '\x1b[33m',
|
|
96
|
+
blue: '\x1b[34m',
|
|
97
|
+
magenta: '\x1b[35m',
|
|
98
|
+
cyan: '\x1b[36m',
|
|
99
|
+
white: '\x1b[37m',
|
|
100
|
+
bgGreen: '\x1b[42m',
|
|
101
|
+
bgRed: '\x1b[41m',
|
|
102
|
+
bgYellow: '\x1b[43m',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Get script directory
|
|
106
|
+
const getScriptDir = (): string => {
|
|
107
|
+
try {
|
|
108
|
+
if (typeof import.meta.url !== 'undefined') {
|
|
109
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Fallback
|
|
113
|
+
}
|
|
114
|
+
return process.cwd();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const SCRIPT_DIR = getScriptDir();
|
|
118
|
+
const PROJECT_ROOT = join(SCRIPT_DIR, '..', '..');
|
|
119
|
+
const CONFIG_PATH = join(SCRIPT_DIR, '..', 'config', 'mcp-config.json');
|
|
120
|
+
const MCP_JSON_PATH = join(PROJECT_ROOT, '.mcp.json');
|
|
121
|
+
|
|
122
|
+
// Utility functions
|
|
123
|
+
function log(message: string, type: 'info' | 'success' | 'warning' | 'error' = 'info'): void {
|
|
124
|
+
const prefix = {
|
|
125
|
+
info: `${colors.blue}[INFO]${colors.reset}`,
|
|
126
|
+
success: `${colors.green}[OK]${colors.reset}`,
|
|
127
|
+
warning: `${colors.yellow}[WARN]${colors.reset}`,
|
|
128
|
+
error: `${colors.red}[ERROR]${colors.reset}`,
|
|
129
|
+
};
|
|
130
|
+
console.log(`${prefix[type]} ${message}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function header(text: string): void {
|
|
134
|
+
const line = '='.repeat(60);
|
|
135
|
+
console.log(`\n${colors.cyan}${line}${colors.reset}`);
|
|
136
|
+
console.log(`${colors.bright}${colors.cyan} ${text}${colors.reset}`);
|
|
137
|
+
console.log(`${colors.cyan}${line}${colors.reset}\n`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function subheader(text: string): void {
|
|
141
|
+
console.log(`\n${colors.magenta}>> ${text}${colors.reset}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function progressBar(current: number, total: number, width = 40): string {
|
|
145
|
+
const percent = Math.round((current / total) * 100);
|
|
146
|
+
const filled = Math.round((current / total) * width);
|
|
147
|
+
const empty = width - filled;
|
|
148
|
+
const bar = `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
|
|
149
|
+
return `${bar} ${percent}% (${current}/${total})`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Load configuration
|
|
153
|
+
function loadConfig(): MCPConfig {
|
|
154
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
155
|
+
throw new Error(`MCP config not found at: ${CONFIG_PATH}`);
|
|
156
|
+
}
|
|
157
|
+
const content = readFileSync(CONFIG_PATH, 'utf8');
|
|
158
|
+
return JSON.parse(content) as MCPConfig;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Load existing .mcp.json
|
|
162
|
+
function loadMcpJson(): Record<string, MCPJsonEntry> {
|
|
163
|
+
if (!existsSync(MCP_JSON_PATH)) {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const content = readFileSync(MCP_JSON_PATH, 'utf8');
|
|
168
|
+
return JSON.parse(content) as Record<string, MCPJsonEntry>;
|
|
169
|
+
} catch {
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Save .mcp.json
|
|
175
|
+
function saveMcpJson(config: Record<string, MCPJsonEntry>): void {
|
|
176
|
+
writeFileSync(MCP_JSON_PATH, JSON.stringify(config, null, 2));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check if a command exists
|
|
180
|
+
function commandExists(cmd: string): boolean {
|
|
181
|
+
try {
|
|
182
|
+
const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], {
|
|
183
|
+
stdio: 'pipe',
|
|
184
|
+
shell: true,
|
|
185
|
+
});
|
|
186
|
+
return result.status === 0;
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Validate security
|
|
193
|
+
function validateSecurity(
|
|
194
|
+
server: MCPServerConfig,
|
|
195
|
+
config: MCPConfig
|
|
196
|
+
): { valid: boolean; warnings: string[] } {
|
|
197
|
+
const warnings: string[] = [];
|
|
198
|
+
|
|
199
|
+
// Check if publisher is trusted
|
|
200
|
+
if (!config.security.trustedPublishers.includes(server.publisher)) {
|
|
201
|
+
warnings.push(`Publisher '${server.publisher}' is not in trusted list`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check if verified
|
|
205
|
+
if (!server.verified) {
|
|
206
|
+
warnings.push('Server is not verified');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check for missing repository
|
|
210
|
+
if (!server.repository) {
|
|
211
|
+
warnings.push('No repository URL provided');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
valid: warnings.length === 0,
|
|
216
|
+
warnings,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Install a single MCP server using claude mcp add
|
|
221
|
+
async function installServer(
|
|
222
|
+
serverId: string,
|
|
223
|
+
server: MCPServerConfig,
|
|
224
|
+
scope: string,
|
|
225
|
+
dryRun: boolean
|
|
226
|
+
): Promise<InstallResult> {
|
|
227
|
+
const startTime = Date.now();
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// Check if claude CLI is available
|
|
231
|
+
if (!commandExists('claude')) {
|
|
232
|
+
return {
|
|
233
|
+
server: serverId,
|
|
234
|
+
success: false,
|
|
235
|
+
message: 'Claude CLI not found. Please install Claude Code first.',
|
|
236
|
+
duration: Date.now() - startTime,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Build the command based on transport type
|
|
241
|
+
let args: string[];
|
|
242
|
+
|
|
243
|
+
if (server.transport === 'http' && server.config.url) {
|
|
244
|
+
// Remote HTTP server
|
|
245
|
+
args = ['mcp', 'add', '--transport', 'http', '-s', scope, serverId, server.config.url];
|
|
246
|
+
} else if (server.transport === 'http' && server.config.remote?.url) {
|
|
247
|
+
// Remote server with remote config
|
|
248
|
+
args = [
|
|
249
|
+
'mcp',
|
|
250
|
+
'add',
|
|
251
|
+
'--transport',
|
|
252
|
+
'http',
|
|
253
|
+
'-s',
|
|
254
|
+
scope,
|
|
255
|
+
serverId,
|
|
256
|
+
server.config.remote.url,
|
|
257
|
+
];
|
|
258
|
+
} else if (server.config.command && server.config.args) {
|
|
259
|
+
// Local stdio server
|
|
260
|
+
args = [
|
|
261
|
+
'mcp',
|
|
262
|
+
'add',
|
|
263
|
+
'-s',
|
|
264
|
+
scope,
|
|
265
|
+
serverId,
|
|
266
|
+
'--',
|
|
267
|
+
server.config.command,
|
|
268
|
+
...server.config.args,
|
|
269
|
+
];
|
|
270
|
+
} else {
|
|
271
|
+
return {
|
|
272
|
+
server: serverId,
|
|
273
|
+
success: false,
|
|
274
|
+
message: 'Invalid server configuration',
|
|
275
|
+
duration: Date.now() - startTime,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (dryRun) {
|
|
280
|
+
return {
|
|
281
|
+
server: serverId,
|
|
282
|
+
success: true,
|
|
283
|
+
message: `Would run: claude ${args.join(' ')}`,
|
|
284
|
+
duration: Date.now() - startTime,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Execute the command
|
|
289
|
+
return new Promise((resolve) => {
|
|
290
|
+
const proc = spawn('claude', args, {
|
|
291
|
+
shell: true,
|
|
292
|
+
stdio: 'pipe',
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
let stdout = '';
|
|
296
|
+
let stderr = '';
|
|
297
|
+
|
|
298
|
+
proc.stdout?.on('data', (data) => {
|
|
299
|
+
stdout += data.toString();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
proc.stderr?.on('data', (data) => {
|
|
303
|
+
stderr += data.toString();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
proc.on('close', (code) => {
|
|
307
|
+
const duration = Date.now() - startTime;
|
|
308
|
+
if (code === 0) {
|
|
309
|
+
resolve({
|
|
310
|
+
server: serverId,
|
|
311
|
+
success: true,
|
|
312
|
+
message: 'Installed successfully',
|
|
313
|
+
duration,
|
|
314
|
+
});
|
|
315
|
+
} else {
|
|
316
|
+
resolve({
|
|
317
|
+
server: serverId,
|
|
318
|
+
success: false,
|
|
319
|
+
message: stderr || stdout || `Exit code: ${code}`,
|
|
320
|
+
duration,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
proc.on('error', (err) => {
|
|
326
|
+
resolve({
|
|
327
|
+
server: serverId,
|
|
328
|
+
success: false,
|
|
329
|
+
message: err.message,
|
|
330
|
+
duration: Date.now() - startTime,
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Timeout
|
|
335
|
+
setTimeout(() => {
|
|
336
|
+
proc.kill();
|
|
337
|
+
resolve({
|
|
338
|
+
server: serverId,
|
|
339
|
+
success: false,
|
|
340
|
+
message: 'Installation timed out',
|
|
341
|
+
duration: Date.now() - startTime,
|
|
342
|
+
});
|
|
343
|
+
}, 60000);
|
|
344
|
+
});
|
|
345
|
+
} catch (err) {
|
|
346
|
+
return {
|
|
347
|
+
server: serverId,
|
|
348
|
+
success: false,
|
|
349
|
+
message: err instanceof Error ? err.message : 'Unknown error',
|
|
350
|
+
duration: Date.now() - startTime,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Install servers in parallel with limit
|
|
356
|
+
async function installServersParallel(
|
|
357
|
+
servers: Array<{ id: string; config: MCPServerConfig }>,
|
|
358
|
+
scope: string,
|
|
359
|
+
parallelLimit: number,
|
|
360
|
+
dryRun: boolean
|
|
361
|
+
): Promise<InstallResult[]> {
|
|
362
|
+
const results: InstallResult[] = [];
|
|
363
|
+
let completed = 0;
|
|
364
|
+
|
|
365
|
+
// Process in batches
|
|
366
|
+
for (let i = 0; i < servers.length; i += parallelLimit) {
|
|
367
|
+
const batch = servers.slice(i, i + parallelLimit);
|
|
368
|
+
|
|
369
|
+
const batchResults = await Promise.all(
|
|
370
|
+
batch.map(async ({ id, config: serverConfig }) => {
|
|
371
|
+
const result = await installServer(id, serverConfig, scope, dryRun);
|
|
372
|
+
completed++;
|
|
373
|
+
|
|
374
|
+
// Update progress
|
|
375
|
+
const status = result.success
|
|
376
|
+
? `${colors.green}OK${colors.reset}`
|
|
377
|
+
: `${colors.red}FAIL${colors.reset}`;
|
|
378
|
+
console.log(` ${progressBar(completed, servers.length)} ${id}: ${status}`);
|
|
379
|
+
|
|
380
|
+
return result;
|
|
381
|
+
})
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
results.push(...batchResults);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return results;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Generate .mcp.json for project sharing
|
|
391
|
+
function generateMcpJson(
|
|
392
|
+
servers: Array<{ id: string; config: MCPServerConfig }>,
|
|
393
|
+
existingConfig: Record<string, MCPJsonEntry>
|
|
394
|
+
): Record<string, MCPJsonEntry> {
|
|
395
|
+
const mcpJson = { ...existingConfig };
|
|
396
|
+
|
|
397
|
+
for (const { id, config: server } of servers) {
|
|
398
|
+
if (server.transport === 'stdio' && server.config.command && server.config.args) {
|
|
399
|
+
const entry: MCPJsonEntry = {
|
|
400
|
+
command: server.config.command,
|
|
401
|
+
args: server.config.args,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Add environment variables
|
|
405
|
+
if (server.envVars.length > 0) {
|
|
406
|
+
entry.env = {};
|
|
407
|
+
for (const envVar of server.envVars) {
|
|
408
|
+
entry.env[envVar] = `\${${envVar}}`;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
mcpJson[id] = entry;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return mcpJson;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Main function
|
|
420
|
+
async function main(): Promise<void> {
|
|
421
|
+
const args = process.argv.slice(2);
|
|
422
|
+
const options = {
|
|
423
|
+
tier: 'core' as string,
|
|
424
|
+
dryRun: false,
|
|
425
|
+
force: false,
|
|
426
|
+
interactive: false,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// Parse arguments
|
|
430
|
+
for (const arg of args) {
|
|
431
|
+
if (arg.startsWith('--tier=')) {
|
|
432
|
+
options.tier = arg.split('=')[1] || 'core';
|
|
433
|
+
} else if (arg === '--dry-run') {
|
|
434
|
+
options.dryRun = true;
|
|
435
|
+
} else if (arg === '--force') {
|
|
436
|
+
options.force = true;
|
|
437
|
+
} else if (arg === '--interactive') {
|
|
438
|
+
options.interactive = true;
|
|
439
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
440
|
+
console.log(`
|
|
441
|
+
${colors.bright}MCP Auto-Installer for Claude Code${colors.reset}
|
|
442
|
+
|
|
443
|
+
Usage: bun .claude/scripts/setup-mcps.ts [options]
|
|
444
|
+
|
|
445
|
+
Options:
|
|
446
|
+
--tier=<tier> Install specific tier: core, productivity, infrastructure, all
|
|
447
|
+
(default: core)
|
|
448
|
+
--dry-run Show what would be installed without actually installing
|
|
449
|
+
--force Reinstall even if already configured
|
|
450
|
+
--interactive Prompt before each installation
|
|
451
|
+
--help, -h Show this help message
|
|
452
|
+
|
|
453
|
+
Examples:
|
|
454
|
+
bun .claude/scripts/setup-mcps.ts # Install core MCPs
|
|
455
|
+
bun .claude/scripts/setup-mcps.ts --tier=all # Install all MCPs
|
|
456
|
+
bun .claude/scripts/setup-mcps.ts --dry-run # Preview installation
|
|
457
|
+
bun .claude/scripts/setup-mcps.ts --tier=productivity --force
|
|
458
|
+
`);
|
|
459
|
+
process.exit(0);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
header('MCP Auto-Installer for Claude Code');
|
|
464
|
+
|
|
465
|
+
// Load configuration
|
|
466
|
+
log('Loading MCP configuration...');
|
|
467
|
+
let config: MCPConfig;
|
|
468
|
+
try {
|
|
469
|
+
config = loadConfig();
|
|
470
|
+
log(`Found ${Object.keys(config.servers).length} configured MCP servers`, 'success');
|
|
471
|
+
} catch (err) {
|
|
472
|
+
log(
|
|
473
|
+
`Failed to load config: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
474
|
+
'error'
|
|
475
|
+
);
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Check prerequisites
|
|
480
|
+
subheader('Checking Prerequisites');
|
|
481
|
+
|
|
482
|
+
if (!commandExists('claude')) {
|
|
483
|
+
log('Claude CLI not found. Please install Claude Code first.', 'error');
|
|
484
|
+
log('Visit: https://claude.ai/code', 'info');
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
log('Claude CLI found', 'success');
|
|
488
|
+
|
|
489
|
+
if (!commandExists('npx')) {
|
|
490
|
+
log('npx not found. Please install Node.js.', 'error');
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
log('npx found', 'success');
|
|
494
|
+
|
|
495
|
+
// Determine which servers to install
|
|
496
|
+
subheader('Selecting Servers');
|
|
497
|
+
|
|
498
|
+
const tiersToInstall = options.tier === 'all' ? Object.keys(config.tiers) : [options.tier];
|
|
499
|
+
|
|
500
|
+
const serversToInstall: Array<{ id: string; config: MCPServerConfig }> = [];
|
|
501
|
+
|
|
502
|
+
for (const tier of tiersToInstall) {
|
|
503
|
+
const tierConfig = config.tiers[tier];
|
|
504
|
+
if (!tierConfig) {
|
|
505
|
+
log(`Unknown tier: ${tier}`, 'warning');
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
log(`${colors.bright}Tier: ${tier}${colors.reset} - ${tierConfig.description}`);
|
|
510
|
+
|
|
511
|
+
for (const serverId of tierConfig.servers) {
|
|
512
|
+
const server = config.servers[serverId];
|
|
513
|
+
if (!server) {
|
|
514
|
+
log(` Server not found: ${serverId}`, 'warning');
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
console.log(` ${colors.cyan}${serverId}${colors.reset}: ${server.description}`);
|
|
519
|
+
serversToInstall.push({ id: serverId, config: server });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (serversToInstall.length === 0) {
|
|
524
|
+
log('No servers selected for installation', 'warning');
|
|
525
|
+
process.exit(0);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
log(`\nTotal servers to install: ${serversToInstall.length}`, 'info');
|
|
529
|
+
|
|
530
|
+
// Security validation
|
|
531
|
+
subheader('Security Validation');
|
|
532
|
+
|
|
533
|
+
const securityIssues: Array<{ server: string; warnings: string[] }> = [];
|
|
534
|
+
|
|
535
|
+
for (const { id, config: server } of serversToInstall) {
|
|
536
|
+
const validation = validateSecurity(server, config);
|
|
537
|
+
if (!validation.valid) {
|
|
538
|
+
securityIssues.push({ server: id, warnings: validation.warnings });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (securityIssues.length > 0) {
|
|
543
|
+
log('Security warnings found:', 'warning');
|
|
544
|
+
for (const issue of securityIssues) {
|
|
545
|
+
console.log(` ${colors.yellow}${issue.server}${colors.reset}:`);
|
|
546
|
+
for (const warning of issue.warnings) {
|
|
547
|
+
console.log(` - ${warning}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
console.log();
|
|
551
|
+
} else {
|
|
552
|
+
log('All servers passed security validation', 'success');
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Environment variables check
|
|
556
|
+
subheader('Environment Variables');
|
|
557
|
+
|
|
558
|
+
const requiredEnvVars = new Set<string>();
|
|
559
|
+
for (const { config: server } of serversToInstall) {
|
|
560
|
+
for (const envVar of server.envVars) {
|
|
561
|
+
requiredEnvVars.add(envVar);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (requiredEnvVars.size > 0) {
|
|
566
|
+
log('The following environment variables are required:', 'info');
|
|
567
|
+
for (const envVar of requiredEnvVars) {
|
|
568
|
+
const isSet = !!process.env[envVar];
|
|
569
|
+
const status = isSet
|
|
570
|
+
? `${colors.green}SET${colors.reset}`
|
|
571
|
+
: `${colors.yellow}NOT SET${colors.reset}`;
|
|
572
|
+
console.log(` ${envVar}: ${status}`);
|
|
573
|
+
}
|
|
574
|
+
console.log();
|
|
575
|
+
log('Tip: Set these in your .env file or system environment', 'info');
|
|
576
|
+
} else {
|
|
577
|
+
log('No environment variables required for selected servers', 'success');
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Installation
|
|
581
|
+
subheader(`${options.dryRun ? 'Dry Run - ' : ''}Installing MCP Servers`);
|
|
582
|
+
|
|
583
|
+
console.log();
|
|
584
|
+
const results = await installServersParallel(
|
|
585
|
+
serversToInstall,
|
|
586
|
+
config.installation.scope,
|
|
587
|
+
config.installation.parallelLimit,
|
|
588
|
+
options.dryRun
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
// Generate .mcp.json for team sharing
|
|
592
|
+
if (!options.dryRun) {
|
|
593
|
+
subheader('Generating .mcp.json');
|
|
594
|
+
|
|
595
|
+
const existingMcpJson = loadMcpJson();
|
|
596
|
+
const newMcpJson = generateMcpJson(serversToInstall, existingMcpJson);
|
|
597
|
+
saveMcpJson(newMcpJson);
|
|
598
|
+
log(`Generated ${MCP_JSON_PATH} for team sharing`, 'success');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Summary
|
|
602
|
+
header('Installation Summary');
|
|
603
|
+
|
|
604
|
+
const successful = results.filter((r) => r.success);
|
|
605
|
+
const failed = results.filter((r) => !r.success);
|
|
606
|
+
|
|
607
|
+
console.log(`${colors.green}Successful: ${successful.length}${colors.reset}`);
|
|
608
|
+
for (const result of successful) {
|
|
609
|
+
console.log(` ${colors.green}✓${colors.reset} ${result.server} (${result.duration}ms)`);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (failed.length > 0) {
|
|
613
|
+
console.log(`\n${colors.red}Failed: ${failed.length}${colors.reset}`);
|
|
614
|
+
for (const result of failed) {
|
|
615
|
+
console.log(` ${colors.red}✗${colors.reset} ${result.server}: ${result.message}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Next steps
|
|
620
|
+
if (!options.dryRun) {
|
|
621
|
+
subheader('Next Steps');
|
|
622
|
+
|
|
623
|
+
console.log(`
|
|
624
|
+
1. ${colors.cyan}Verify installation:${colors.reset}
|
|
625
|
+
claude mcp list
|
|
626
|
+
|
|
627
|
+
2. ${colors.cyan}Test a server:${colors.reset}
|
|
628
|
+
claude mcp get <server-name>
|
|
629
|
+
|
|
630
|
+
3. ${colors.cyan}Set missing environment variables:${colors.reset}
|
|
631
|
+
Create a .env file with required variables
|
|
632
|
+
|
|
633
|
+
4. ${colors.cyan}Share with team:${colors.reset}
|
|
634
|
+
Commit .mcp.json to version control
|
|
635
|
+
|
|
636
|
+
5. ${colors.cyan}Debug issues:${colors.reset}
|
|
637
|
+
claude --mcp-debug
|
|
638
|
+
`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Exit code
|
|
642
|
+
if (failed.length > 0 && !options.dryRun) {
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Run
|
|
648
|
+
main().catch((err) => {
|
|
649
|
+
console.error(`${colors.red}Fatal error:${colors.reset}`, err);
|
|
650
|
+
process.exit(1);
|
|
651
|
+
});
|