start-vibing 2.0.6 → 2.0.8

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.
@@ -0,0 +1,628 @@
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(server: MCPServerConfig, config: MCPConfig): { valid: boolean; warnings: string[] } {
194
+ const warnings: string[] = [];
195
+
196
+ // Check if publisher is trusted
197
+ if (!config.security.trustedPublishers.includes(server.publisher)) {
198
+ warnings.push(`Publisher '${server.publisher}' is not in trusted list`);
199
+ }
200
+
201
+ // Check if verified
202
+ if (!server.verified) {
203
+ warnings.push('Server is not verified');
204
+ }
205
+
206
+ // Check for missing repository
207
+ if (!server.repository) {
208
+ warnings.push('No repository URL provided');
209
+ }
210
+
211
+ return {
212
+ valid: warnings.length === 0,
213
+ warnings,
214
+ };
215
+ }
216
+
217
+ // Install a single MCP server using claude mcp add
218
+ async function installServer(
219
+ serverId: string,
220
+ server: MCPServerConfig,
221
+ scope: string,
222
+ dryRun: boolean
223
+ ): Promise<InstallResult> {
224
+ const startTime = Date.now();
225
+
226
+ try {
227
+ // Check if claude CLI is available
228
+ if (!commandExists('claude')) {
229
+ return {
230
+ server: serverId,
231
+ success: false,
232
+ message: 'Claude CLI not found. Please install Claude Code first.',
233
+ duration: Date.now() - startTime,
234
+ };
235
+ }
236
+
237
+ // Build the command based on transport type
238
+ let args: string[];
239
+
240
+ if (server.transport === 'http' && server.config.url) {
241
+ // Remote HTTP server
242
+ args = ['mcp', 'add', '--transport', 'http', '-s', scope, serverId, server.config.url];
243
+ } else if (server.transport === 'http' && server.config.remote?.url) {
244
+ // Remote server with remote config
245
+ args = ['mcp', 'add', '--transport', 'http', '-s', scope, serverId, server.config.remote.url];
246
+ } else if (server.config.command && server.config.args) {
247
+ // Local stdio server
248
+ args = ['mcp', 'add', '-s', scope, serverId, '--', server.config.command, ...server.config.args];
249
+ } else {
250
+ return {
251
+ server: serverId,
252
+ success: false,
253
+ message: 'Invalid server configuration',
254
+ duration: Date.now() - startTime,
255
+ };
256
+ }
257
+
258
+ if (dryRun) {
259
+ return {
260
+ server: serverId,
261
+ success: true,
262
+ message: `Would run: claude ${args.join(' ')}`,
263
+ duration: Date.now() - startTime,
264
+ };
265
+ }
266
+
267
+ // Execute the command
268
+ return new Promise((resolve) => {
269
+ const proc = spawn('claude', args, {
270
+ shell: true,
271
+ stdio: 'pipe',
272
+ });
273
+
274
+ let stdout = '';
275
+ let stderr = '';
276
+
277
+ proc.stdout?.on('data', (data) => {
278
+ stdout += data.toString();
279
+ });
280
+
281
+ proc.stderr?.on('data', (data) => {
282
+ stderr += data.toString();
283
+ });
284
+
285
+ proc.on('close', (code) => {
286
+ const duration = Date.now() - startTime;
287
+ if (code === 0) {
288
+ resolve({
289
+ server: serverId,
290
+ success: true,
291
+ message: 'Installed successfully',
292
+ duration,
293
+ });
294
+ } else {
295
+ resolve({
296
+ server: serverId,
297
+ success: false,
298
+ message: stderr || stdout || `Exit code: ${code}`,
299
+ duration,
300
+ });
301
+ }
302
+ });
303
+
304
+ proc.on('error', (err) => {
305
+ resolve({
306
+ server: serverId,
307
+ success: false,
308
+ message: err.message,
309
+ duration: Date.now() - startTime,
310
+ });
311
+ });
312
+
313
+ // Timeout
314
+ setTimeout(() => {
315
+ proc.kill();
316
+ resolve({
317
+ server: serverId,
318
+ success: false,
319
+ message: 'Installation timed out',
320
+ duration: Date.now() - startTime,
321
+ });
322
+ }, 60000);
323
+ });
324
+ } catch (err) {
325
+ return {
326
+ server: serverId,
327
+ success: false,
328
+ message: err instanceof Error ? err.message : 'Unknown error',
329
+ duration: Date.now() - startTime,
330
+ };
331
+ }
332
+ }
333
+
334
+ // Install servers in parallel with limit
335
+ async function installServersParallel(
336
+ servers: Array<{ id: string; config: MCPServerConfig }>,
337
+ scope: string,
338
+ parallelLimit: number,
339
+ dryRun: boolean
340
+ ): Promise<InstallResult[]> {
341
+ const results: InstallResult[] = [];
342
+ let completed = 0;
343
+
344
+ // Process in batches
345
+ for (let i = 0; i < servers.length; i += parallelLimit) {
346
+ const batch = servers.slice(i, i + parallelLimit);
347
+
348
+ const batchResults = await Promise.all(
349
+ batch.map(async ({ id, config: serverConfig }) => {
350
+ const result = await installServer(id, serverConfig, scope, dryRun);
351
+ completed++;
352
+
353
+ // Update progress
354
+ const status = result.success
355
+ ? `${colors.green}OK${colors.reset}`
356
+ : `${colors.red}FAIL${colors.reset}`;
357
+ console.log(` ${progressBar(completed, servers.length)} ${id}: ${status}`);
358
+
359
+ return result;
360
+ })
361
+ );
362
+
363
+ results.push(...batchResults);
364
+ }
365
+
366
+ return results;
367
+ }
368
+
369
+ // Generate .mcp.json for project sharing
370
+ function generateMcpJson(
371
+ servers: Array<{ id: string; config: MCPServerConfig }>,
372
+ existingConfig: Record<string, MCPJsonEntry>
373
+ ): Record<string, MCPJsonEntry> {
374
+ const mcpJson = { ...existingConfig };
375
+
376
+ for (const { id, config: server } of servers) {
377
+ if (server.transport === 'stdio' && server.config.command && server.config.args) {
378
+ const entry: MCPJsonEntry = {
379
+ command: server.config.command,
380
+ args: server.config.args,
381
+ };
382
+
383
+ // Add environment variables
384
+ if (server.envVars.length > 0) {
385
+ entry.env = {};
386
+ for (const envVar of server.envVars) {
387
+ entry.env[envVar] = `\${${envVar}}`;
388
+ }
389
+ }
390
+
391
+ mcpJson[id] = entry;
392
+ }
393
+ }
394
+
395
+ return mcpJson;
396
+ }
397
+
398
+ // Main function
399
+ async function main(): Promise<void> {
400
+ const args = process.argv.slice(2);
401
+ const options = {
402
+ tier: 'core' as string,
403
+ dryRun: false,
404
+ force: false,
405
+ interactive: false,
406
+ };
407
+
408
+ // Parse arguments
409
+ for (const arg of args) {
410
+ if (arg.startsWith('--tier=')) {
411
+ options.tier = arg.split('=')[1] || 'core';
412
+ } else if (arg === '--dry-run') {
413
+ options.dryRun = true;
414
+ } else if (arg === '--force') {
415
+ options.force = true;
416
+ } else if (arg === '--interactive') {
417
+ options.interactive = true;
418
+ } else if (arg === '--help' || arg === '-h') {
419
+ console.log(`
420
+ ${colors.bright}MCP Auto-Installer for Claude Code${colors.reset}
421
+
422
+ Usage: bun .claude/scripts/setup-mcps.ts [options]
423
+
424
+ Options:
425
+ --tier=<tier> Install specific tier: core, productivity, infrastructure, all
426
+ (default: core)
427
+ --dry-run Show what would be installed without actually installing
428
+ --force Reinstall even if already configured
429
+ --interactive Prompt before each installation
430
+ --help, -h Show this help message
431
+
432
+ Examples:
433
+ bun .claude/scripts/setup-mcps.ts # Install core MCPs
434
+ bun .claude/scripts/setup-mcps.ts --tier=all # Install all MCPs
435
+ bun .claude/scripts/setup-mcps.ts --dry-run # Preview installation
436
+ bun .claude/scripts/setup-mcps.ts --tier=productivity --force
437
+ `);
438
+ process.exit(0);
439
+ }
440
+ }
441
+
442
+ header('MCP Auto-Installer for Claude Code');
443
+
444
+ // Load configuration
445
+ log('Loading MCP configuration...');
446
+ let config: MCPConfig;
447
+ try {
448
+ config = loadConfig();
449
+ log(`Found ${Object.keys(config.servers).length} configured MCP servers`, 'success');
450
+ } catch (err) {
451
+ log(`Failed to load config: ${err instanceof Error ? err.message : 'Unknown error'}`, 'error');
452
+ process.exit(1);
453
+ }
454
+
455
+ // Check prerequisites
456
+ subheader('Checking Prerequisites');
457
+
458
+ if (!commandExists('claude')) {
459
+ log('Claude CLI not found. Please install Claude Code first.', 'error');
460
+ log('Visit: https://claude.ai/code', 'info');
461
+ process.exit(1);
462
+ }
463
+ log('Claude CLI found', 'success');
464
+
465
+ if (!commandExists('npx')) {
466
+ log('npx not found. Please install Node.js.', 'error');
467
+ process.exit(1);
468
+ }
469
+ log('npx found', 'success');
470
+
471
+ // Determine which servers to install
472
+ subheader('Selecting Servers');
473
+
474
+ const tiersToInstall =
475
+ options.tier === 'all' ? Object.keys(config.tiers) : [options.tier];
476
+
477
+ const serversToInstall: Array<{ id: string; config: MCPServerConfig }> = [];
478
+
479
+ for (const tier of tiersToInstall) {
480
+ const tierConfig = config.tiers[tier];
481
+ if (!tierConfig) {
482
+ log(`Unknown tier: ${tier}`, 'warning');
483
+ continue;
484
+ }
485
+
486
+ log(`${colors.bright}Tier: ${tier}${colors.reset} - ${tierConfig.description}`);
487
+
488
+ for (const serverId of tierConfig.servers) {
489
+ const server = config.servers[serverId];
490
+ if (!server) {
491
+ log(` Server not found: ${serverId}`, 'warning');
492
+ continue;
493
+ }
494
+
495
+ console.log(` ${colors.cyan}${serverId}${colors.reset}: ${server.description}`);
496
+ serversToInstall.push({ id: serverId, config: server });
497
+ }
498
+ }
499
+
500
+ if (serversToInstall.length === 0) {
501
+ log('No servers selected for installation', 'warning');
502
+ process.exit(0);
503
+ }
504
+
505
+ log(`\nTotal servers to install: ${serversToInstall.length}`, 'info');
506
+
507
+ // Security validation
508
+ subheader('Security Validation');
509
+
510
+ const securityIssues: Array<{ server: string; warnings: string[] }> = [];
511
+
512
+ for (const { id, config: server } of serversToInstall) {
513
+ const validation = validateSecurity(server, config);
514
+ if (!validation.valid) {
515
+ securityIssues.push({ server: id, warnings: validation.warnings });
516
+ }
517
+ }
518
+
519
+ if (securityIssues.length > 0) {
520
+ log('Security warnings found:', 'warning');
521
+ for (const issue of securityIssues) {
522
+ console.log(` ${colors.yellow}${issue.server}${colors.reset}:`);
523
+ for (const warning of issue.warnings) {
524
+ console.log(` - ${warning}`);
525
+ }
526
+ }
527
+ console.log();
528
+ } else {
529
+ log('All servers passed security validation', 'success');
530
+ }
531
+
532
+ // Environment variables check
533
+ subheader('Environment Variables');
534
+
535
+ const requiredEnvVars = new Set<string>();
536
+ for (const { config: server } of serversToInstall) {
537
+ for (const envVar of server.envVars) {
538
+ requiredEnvVars.add(envVar);
539
+ }
540
+ }
541
+
542
+ if (requiredEnvVars.size > 0) {
543
+ log('The following environment variables are required:', 'info');
544
+ for (const envVar of requiredEnvVars) {
545
+ const isSet = !!process.env[envVar];
546
+ const status = isSet
547
+ ? `${colors.green}SET${colors.reset}`
548
+ : `${colors.yellow}NOT SET${colors.reset}`;
549
+ console.log(` ${envVar}: ${status}`);
550
+ }
551
+ console.log();
552
+ log('Tip: Set these in your .env file or system environment', 'info');
553
+ } else {
554
+ log('No environment variables required for selected servers', 'success');
555
+ }
556
+
557
+ // Installation
558
+ subheader(`${options.dryRun ? 'Dry Run - ' : ''}Installing MCP Servers`);
559
+
560
+ console.log();
561
+ const results = await installServersParallel(
562
+ serversToInstall,
563
+ config.installation.scope,
564
+ config.installation.parallelLimit,
565
+ options.dryRun
566
+ );
567
+
568
+ // Generate .mcp.json for team sharing
569
+ if (!options.dryRun) {
570
+ subheader('Generating .mcp.json');
571
+
572
+ const existingMcpJson = loadMcpJson();
573
+ const newMcpJson = generateMcpJson(serversToInstall, existingMcpJson);
574
+ saveMcpJson(newMcpJson);
575
+ log(`Generated ${MCP_JSON_PATH} for team sharing`, 'success');
576
+ }
577
+
578
+ // Summary
579
+ header('Installation Summary');
580
+
581
+ const successful = results.filter((r) => r.success);
582
+ const failed = results.filter((r) => !r.success);
583
+
584
+ console.log(`${colors.green}Successful: ${successful.length}${colors.reset}`);
585
+ for (const result of successful) {
586
+ console.log(` ${colors.green}✓${colors.reset} ${result.server} (${result.duration}ms)`);
587
+ }
588
+
589
+ if (failed.length > 0) {
590
+ console.log(`\n${colors.red}Failed: ${failed.length}${colors.reset}`);
591
+ for (const result of failed) {
592
+ console.log(` ${colors.red}✗${colors.reset} ${result.server}: ${result.message}`);
593
+ }
594
+ }
595
+
596
+ // Next steps
597
+ if (!options.dryRun) {
598
+ subheader('Next Steps');
599
+
600
+ console.log(`
601
+ 1. ${colors.cyan}Verify installation:${colors.reset}
602
+ claude mcp list
603
+
604
+ 2. ${colors.cyan}Test a server:${colors.reset}
605
+ claude mcp get <server-name>
606
+
607
+ 3. ${colors.cyan}Set missing environment variables:${colors.reset}
608
+ Create a .env file with required variables
609
+
610
+ 4. ${colors.cyan}Share with team:${colors.reset}
611
+ Commit .mcp.json to version control
612
+
613
+ 5. ${colors.cyan}Debug issues:${colors.reset}
614
+ claude --mcp-debug
615
+ `);
616
+ }
617
+
618
+ // Exit code
619
+ if (failed.length > 0 && !options.dryRun) {
620
+ process.exit(1);
621
+ }
622
+ }
623
+
624
+ // Run
625
+ main().catch((err) => {
626
+ console.error(`${colors.red}Fatal error:${colors.reset}`, err);
627
+ process.exit(1);
628
+ });
@@ -48,7 +48,9 @@
48
48
  "Bash(git add:*)",
49
49
  "Bash(git commit:*)",
50
50
  "Bash(python .claude/hooks/*)",
51
- "Bash(bun .claude/hooks/*)"
51
+ "Bash(bun .claude/hooks/*)",
52
+ "Bash(bun .claude/scripts/*)",
53
+ "Bash(claude mcp:*)"
52
54
  ]
53
55
  },
54
56