claudex-setup 1.10.0 → 1.10.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.2] - 2026-04-02
4
+
5
+ ### Fixed
6
+ - MCP recommendations are now less speculative: `postgres-mcp` requires explicit Postgres signals, `figma-mcp` only appears for design-system repos, and `mcp-security` is no longer auto-added just because multiple packs were suggested
7
+ - `sentry-mcp` now requires real observability signals or stricter operational domains instead of appearing for every frontend/backend repo
8
+ - design-system detection now respects `.storybook/` directories directly, improving frontend pack accuracy
9
+
10
+ ### Added
11
+ - MCP preflight warnings for `setup`, `plan`, and `apply` when selected packs require missing environment variables
12
+ - user-facing docs now reflect the actual 22 detected stacks
13
+
3
14
  ## [1.10.0] - 2026-04-01
4
15
 
5
16
  ### Added
package/README.md CHANGED
@@ -204,16 +204,16 @@ The exact applicable count can be lower on a given repo because stack-specific c
204
204
 
205
205
  ## Stack Detection
206
206
 
207
- Auto-detects and tailors output for 18 stacks:
207
+ Auto-detects and tailors output for 22 stacks:
208
208
 
209
209
  | | |
210
210
  |--|--|
211
211
  | **Frontend** | React, Vue, Angular, Next.js, Svelte |
212
212
  | **Backend** | Node.js, Python, Django, FastAPI |
213
213
  | **Mobile** | Flutter, Swift, Kotlin |
214
- | **Systems** | Rust, Go, Java, Ruby |
214
+ | **Systems** | Rust, Go, Java, Ruby, C++, Bazel |
215
215
  | **Language** | TypeScript |
216
- | **Infra** | Docker |
216
+ | **Infra** | Docker, Terraform, Kubernetes |
217
217
 
218
218
  ## GitHub Action
219
219
 
@@ -227,7 +227,7 @@ jobs:
227
227
  runs-on: ubuntu-latest
228
228
  steps:
229
229
  - uses: actions/checkout@v4
230
- - uses: DnaFin/claudex-setup@v1.10.0
230
+ - uses: DnaFin/claudex-setup@v1.10.2
231
231
  with:
232
232
  threshold: 50
233
233
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudex-setup",
3
- "version": "1.10.0",
3
+ "version": "1.10.2",
4
4
  "description": "Audit and improve Claude Code readiness with discover, plan, apply, governance, and benchmark workflows.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/analyze.js CHANGED
@@ -318,7 +318,7 @@ async function analyzeProject(options) {
318
318
  const maturity = detectMaturity(assets);
319
319
  const mainDirs = detectMainDirs(ctx);
320
320
  const recommendedDomainPacks = detectDomainPacks(ctx, stacks, assets);
321
- const recommendedMcpPacks = recommendMcpPacks(stacks, recommendedDomainPacks);
321
+ const recommendedMcpPacks = recommendMcpPacks(stacks, recommendedDomainPacks, { ctx, assets });
322
322
 
323
323
  const report = {
324
324
  mode,
@@ -243,14 +243,14 @@ function detectDomainPacks(ctx, stacks, assets = null) {
243
243
  }
244
244
 
245
245
  // Regulated-lite detection
246
- const isRegulated = ctx.files.includes('SECURITY.md') ||
247
- ctx.files.includes('COMPLIANCE.md') || ctx.hasDir('compliance') ||
246
+ const hasSecurityPolicy = ctx.files.includes('SECURITY.md');
247
+ const isRegulated = ctx.files.includes('COMPLIANCE.md') || ctx.hasDir('compliance') ||
248
248
  ctx.hasDir('audit') || ctx.hasDir('policies') ||
249
249
  (pkg.keywords && pkg.keywords.some(k => ['hipaa', 'fintech', 'compliance', 'regulated', 'sox', 'pci'].includes(k)));
250
250
  if (isRegulated && !isEnterpriseGoverned) {
251
251
  addMatch('regulated-lite', [
252
252
  'Detected compliance or regulatory signals without full enterprise governance.',
253
- ctx.files.includes('SECURITY.md') ? 'SECURITY.md present.' : null,
253
+ ctx.files.includes('COMPLIANCE.md') ? 'COMPLIANCE.md present.' : null,
254
254
  ctx.hasDir('compliance') ? 'Compliance directory detected.' : null,
255
255
  ]);
256
256
  }
@@ -293,7 +293,7 @@ function detectDomainPacks(ctx, stacks, assets = null) {
293
293
  // Design system detection
294
294
  const isDesignSystem = deps.storybook || deps['@storybook/react'] || deps['@storybook/vue3'] ||
295
295
  ctx.hasDir('tokens') || ctx.hasDir('design-tokens') ||
296
- (ctx.hasDir('components') && ctx.files.includes('.storybook'));
296
+ (ctx.hasDir('components') && ctx.hasDir('.storybook'));
297
297
  if (isDesignSystem) {
298
298
  addMatch('design-system', [
299
299
  'Detected design system or component library signals.',
@@ -315,7 +315,7 @@ function detectDomainPacks(ctx, stacks, assets = null) {
315
315
  }
316
316
 
317
317
  // Security-focused detection
318
- const isSecurityFocused = ctx.files.includes('SECURITY.md') &&
318
+ const isSecurityFocused = hasSecurityPolicy &&
319
319
  (hasBackend || deps.bcrypt || deps.jsonwebtoken || deps.passport || deps['next-auth']);
320
320
  if (isSecurityFocused && !isRegulated) {
321
321
  addMatch('security-focused', [
package/src/index.js CHANGED
@@ -5,7 +5,7 @@ const { buildProposalBundle, applyProposalBundle } = require('./plans');
5
5
  const { getGovernanceSummary } = require('./governance');
6
6
  const { runBenchmark } = require('./benchmark');
7
7
  const { DOMAIN_PACKS, detectDomainPacks } = require('./domain-packs');
8
- const { MCP_PACKS, getMcpPack, mergeMcpServers, recommendMcpPacks } = require('./mcp-packs');
8
+ const { MCP_PACKS, getMcpPack, mergeMcpServers, getMcpPackPreflight, recommendMcpPacks } = require('./mcp-packs');
9
9
 
10
10
  module.exports = {
11
11
  audit,
@@ -20,5 +20,6 @@ module.exports = {
20
20
  MCP_PACKS,
21
21
  getMcpPack,
22
22
  mergeMcpServers,
23
+ getMcpPackPreflight,
23
24
  recommendMcpPacks,
24
25
  };
package/src/mcp-packs.js CHANGED
@@ -81,7 +81,7 @@ const MCP_PACKS = [
81
81
  servers: {
82
82
  docker: {
83
83
  command: 'npx',
84
- args: ['-y', '@modelcontextprotocol/server-docker'],
84
+ args: ['-y', '@hypnosis/docker-mcp-server'],
85
85
  },
86
86
  },
87
87
  },
@@ -106,7 +106,7 @@ const MCP_PACKS = [
106
106
  servers: {
107
107
  linear: {
108
108
  command: 'npx',
109
- args: ['-y', '@linear/mcp-server'],
109
+ args: ['-y', '@mseep/linear-mcp'],
110
110
  env: { LINEAR_API_KEY: '${LINEAR_API_KEY}' },
111
111
  },
112
112
  },
@@ -132,7 +132,7 @@ const MCP_PACKS = [
132
132
  servers: {
133
133
  slack: {
134
134
  command: 'npx',
135
- args: ['-y', '@anthropic/slack-mcp-server'],
135
+ args: ['-y', 'slack-mcp-server'],
136
136
  env: { SLACK_BOT_TOKEN: '${SLACK_BOT_TOKEN}' },
137
137
  },
138
138
  },
@@ -145,7 +145,7 @@ const MCP_PACKS = [
145
145
  servers: {
146
146
  stripe: {
147
147
  command: 'npx',
148
- args: ['-y', '@stripe/mcp-server'],
148
+ args: ['-y', '@stripe/mcp'],
149
149
  env: { STRIPE_API_KEY: '${STRIPE_API_KEY}' },
150
150
  },
151
151
  },
@@ -158,7 +158,7 @@ const MCP_PACKS = [
158
158
  servers: {
159
159
  figma: {
160
160
  command: 'npx',
161
- args: ['-y', '@anthropic/figma-mcp-server'],
161
+ args: ['-y', 'claude-talk-to-figma-mcp'],
162
162
  env: { FIGMA_ACCESS_TOKEN: '${FIGMA_ACCESS_TOKEN}' },
163
163
  },
164
164
  },
@@ -183,7 +183,7 @@ const MCP_PACKS = [
183
183
  servers: {
184
184
  composio: {
185
185
  command: 'npx',
186
- args: ['-y', 'composio-mcp@latest'],
186
+ args: ['-y', '@composio/mcp'],
187
187
  env: { COMPOSIO_API_KEY: '${COMPOSIO_API_KEY}' },
188
188
  },
189
189
  },
@@ -208,7 +208,7 @@ const MCP_PACKS = [
208
208
  servers: {
209
209
  jira: {
210
210
  command: 'npx',
211
- args: ['-y', '@anthropic/jira-mcp-server'],
211
+ args: ['-y', 'jira-mcp'],
212
212
  env: { ATLASSIAN_API_TOKEN: '${ATLASSIAN_API_TOKEN}', ATLASSIAN_EMAIL: '${ATLASSIAN_EMAIL}' },
213
213
  },
214
214
  },
@@ -221,7 +221,7 @@ const MCP_PACKS = [
221
221
  servers: {
222
222
  ga4: {
223
223
  command: 'npx',
224
- args: ['-y', '@anthropic/ga4-mcp-server'],
224
+ args: ['-y', 'mcp-server-ga4'],
225
225
  env: { GA4_PROPERTY_ID: '${GA4_PROPERTY_ID}' },
226
226
  },
227
227
  },
@@ -260,7 +260,7 @@ const MCP_PACKS = [
260
260
  servers: {
261
261
  zendesk: {
262
262
  command: 'npx',
263
- args: ['-y', 'zendesk-mcp-server@latest'],
263
+ args: ['-y', 'zendesk-mcp'],
264
264
  env: { ZENDESK_API_TOKEN: '${ZENDESK_API_TOKEN}', ZENDESK_SUBDOMAIN: '${ZENDESK_SUBDOMAIN}' },
265
265
  },
266
266
  },
@@ -273,7 +273,7 @@ const MCP_PACKS = [
273
273
  servers: {
274
274
  infisical: {
275
275
  command: 'npx',
276
- args: ['-y', '@infisical/mcp-server'],
276
+ args: ['-y', '@infisical/mcp'],
277
277
  env: { INFISICAL_TOKEN: '${INFISICAL_TOKEN}' },
278
278
  },
279
279
  },
@@ -286,7 +286,7 @@ const MCP_PACKS = [
286
286
  servers: {
287
287
  shopify: {
288
288
  command: 'npx',
289
- args: ['-y', '@shopify/dev-mcp-server'],
289
+ args: ['-y', 'shopify-mcp'],
290
290
  env: { SHOPIFY_ACCESS_TOKEN: '${SHOPIFY_ACCESS_TOKEN}' },
291
291
  },
292
292
  },
@@ -299,7 +299,7 @@ const MCP_PACKS = [
299
299
  servers: {
300
300
  huggingface: {
301
301
  command: 'npx',
302
- args: ['-y', '@huggingface/mcp-server'],
302
+ args: ['-y', 'huggingface-mcp-server'],
303
303
  env: { HF_TOKEN: '${HF_TOKEN}' },
304
304
  },
305
305
  },
@@ -312,7 +312,7 @@ const MCP_PACKS = [
312
312
  servers: {
313
313
  blender: {
314
314
  command: 'npx',
315
- args: ['-y', 'blender-mcp@latest'],
315
+ args: ['-y', '@glutamateapp/blender-mcp-ts'],
316
316
  },
317
317
  },
318
318
  },
@@ -335,6 +335,71 @@ function clone(value) {
335
335
  return JSON.parse(JSON.stringify(value));
336
336
  }
337
337
 
338
+ function hasDependency(deps, name) {
339
+ return Object.prototype.hasOwnProperty.call(deps || {}, name);
340
+ }
341
+
342
+ function hasFileContentMatch(ctx, filePath, pattern) {
343
+ if (!ctx) return false;
344
+ const content = ctx.fileContent(filePath);
345
+ return !!(content && pattern.test(content));
346
+ }
347
+
348
+ function getProjectDependencies(ctx) {
349
+ if (!ctx) return {};
350
+ const pkg = ctx.jsonFile('package.json') || {};
351
+ return {
352
+ ...(pkg.dependencies || {}),
353
+ ...(pkg.devDependencies || {}),
354
+ };
355
+ }
356
+
357
+ function hasPostgresSignals(ctx, deps = {}) {
358
+ if (
359
+ hasDependency(deps, 'pg') ||
360
+ hasDependency(deps, 'postgres') ||
361
+ hasDependency(deps, 'pg-promise') ||
362
+ hasDependency(deps, 'postgres.js') ||
363
+ hasDependency(deps, 'slonik') ||
364
+ hasDependency(deps, '@neondatabase/serverless') ||
365
+ hasDependency(deps, '@vercel/postgres')
366
+ ) {
367
+ return true;
368
+ }
369
+
370
+ return (
371
+ hasFileContentMatch(ctx, 'prisma/schema.prisma', /provider\s*=\s*["']postgresql["']/i) ||
372
+ hasFileContentMatch(ctx, 'docker-compose.yml', /\bpostgres\b/i) ||
373
+ hasFileContentMatch(ctx, 'docker-compose.yaml', /\bpostgres\b/i) ||
374
+ hasFileContentMatch(ctx, 'compose.yml', /\bpostgres\b/i) ||
375
+ hasFileContentMatch(ctx, 'compose.yaml', /\bpostgres\b/i) ||
376
+ hasFileContentMatch(ctx, '.env', /postgres(?:ql)?:\/\//i) ||
377
+ hasFileContentMatch(ctx, '.env.local', /postgres(?:ql)?:\/\//i) ||
378
+ hasFileContentMatch(ctx, '.env.example', /postgres(?:ql)?:\/\//i)
379
+ );
380
+ }
381
+
382
+ function hasObservabilitySignals(ctx, deps = {}) {
383
+ if (
384
+ hasDependency(deps, '@sentry/nextjs') ||
385
+ hasDependency(deps, '@sentry/node') ||
386
+ hasDependency(deps, '@sentry/react') ||
387
+ hasDependency(deps, '@sentry/vue') ||
388
+ hasDependency(deps, '@sentry/browser')
389
+ ) {
390
+ return true;
391
+ }
392
+
393
+ return (
394
+ hasFileContentMatch(ctx, 'sentry.client.config.js', /\S/) ||
395
+ hasFileContentMatch(ctx, 'sentry.client.config.ts', /\S/) ||
396
+ hasFileContentMatch(ctx, 'sentry.server.config.js', /\S/) ||
397
+ hasFileContentMatch(ctx, 'sentry.server.config.ts', /\S/) ||
398
+ hasFileContentMatch(ctx, 'instrumentation.ts', /sentry/i) ||
399
+ hasFileContentMatch(ctx, 'instrumentation.js', /sentry/i)
400
+ );
401
+ }
402
+
338
403
  function getMcpPack(key) {
339
404
  return MCP_PACKS.find(pack => pack.key === key) || null;
340
405
  }
@@ -360,9 +425,46 @@ function mergeMcpServers(existing = {}, packKeys = []) {
360
425
  return merged;
361
426
  }
362
427
 
363
- function recommendMcpPacks(stacks = [], domainPacks = []) {
428
+ function getRequiredEnvVars(packKeys = []) {
429
+ const required = new Set();
430
+ for (const key of normalizeMcpPackKeys(packKeys)) {
431
+ const pack = getMcpPack(key);
432
+ if (!pack) continue;
433
+ for (const serverConfig of Object.values(pack.servers || {})) {
434
+ for (const envKey of Object.keys(serverConfig.env || {})) {
435
+ required.add(envKey);
436
+ }
437
+ }
438
+ }
439
+ return [...required].sort();
440
+ }
441
+
442
+ function getMcpPackPreflight(packKeys = [], env = process.env) {
443
+ return normalizeMcpPackKeys(packKeys)
444
+ .map((key) => {
445
+ const pack = getMcpPack(key);
446
+ if (!pack) return null;
447
+ const requiredEnvVars = getRequiredEnvVars([key]);
448
+ if (requiredEnvVars.length === 0) return null;
449
+ const missingEnvVars = requiredEnvVars.filter((envKey) => {
450
+ const value = env && Object.prototype.hasOwnProperty.call(env, envKey) ? env[envKey] : '';
451
+ return !`${value || ''}`.trim();
452
+ });
453
+ return {
454
+ key,
455
+ label: pack.label,
456
+ requiredEnvVars,
457
+ missingEnvVars,
458
+ };
459
+ })
460
+ .filter(Boolean);
461
+ }
462
+
463
+ function recommendMcpPacks(stacks = [], domainPacks = [], options = {}) {
364
464
  const recommended = new Set();
365
465
  const stackKeys = new Set(stacks.map(stack => stack.key));
466
+ const ctx = options.ctx || null;
467
+ const deps = getProjectDependencies(ctx);
366
468
 
367
469
  for (const pack of domainPacks) {
368
470
  for (const key of pack.recommendedMcpPacks || []) {
@@ -384,8 +486,8 @@ function recommendMcpPacks(stacks = [], domainPacks = []) {
384
486
  recommended.add('github-mcp');
385
487
  }
386
488
 
387
- // Postgres MCP for data-heavy repos
388
- if (domainKeys.has('data-pipeline') || domainKeys.has('backend-api')) {
489
+ // Postgres MCP only when there are explicit Postgres signals
490
+ if ((domainKeys.has('data-pipeline') || domainKeys.has('backend-api')) && hasPostgresSignals(ctx, deps)) {
389
491
  recommended.add('postgres-mcp');
390
492
  }
391
493
 
@@ -404,13 +506,21 @@ function recommendMcpPacks(stacks = [], domainPacks = []) {
404
506
  recommended.add('docker-mcp');
405
507
  }
406
508
 
407
- // Sentry for production backend/frontend
408
- if (domainKeys.has('backend-api') || domainKeys.has('frontend-ui')) {
509
+ // Sentry when the repo already shows observability signals or has stricter operational needs
510
+ if (
511
+ (domainKeys.has('backend-api') || domainKeys.has('frontend-ui')) &&
512
+ (
513
+ hasObservabilitySignals(ctx, deps) ||
514
+ domainKeys.has('enterprise-governed') ||
515
+ domainKeys.has('security-focused') ||
516
+ domainKeys.has('ecommerce')
517
+ )
518
+ ) {
409
519
  recommended.add('sentry-mcp');
410
520
  }
411
521
 
412
- // Figma for frontend-ui with design systems
413
- if (domainKeys.has('frontend-ui')) {
522
+ // Figma only when design-system signals are present
523
+ if (domainKeys.has('design-system')) {
414
524
  recommended.add('figma-mcp');
415
525
  }
416
526
 
@@ -450,11 +560,6 @@ function recommendMcpPacks(stacks = [], domainPacks = []) {
450
560
  recommended.add('infisical-secrets');
451
561
  }
452
562
 
453
- // Security scanner when 2+ MCP servers recommended
454
- if (recommended.size >= 2) {
455
- recommended.add('mcp-security');
456
- }
457
-
458
563
  return MCP_PACKS
459
564
  .filter(pack => recommended.has(pack.key))
460
565
  .map(pack => clone(pack));
@@ -465,5 +570,7 @@ module.exports = {
465
570
  getMcpPack,
466
571
  normalizeMcpPackKeys,
467
572
  mergeMcpServers,
573
+ getRequiredEnvVars,
574
+ getMcpPackPreflight,
468
575
  recommendMcpPacks,
469
576
  };
package/src/plans.js CHANGED
@@ -7,6 +7,7 @@ const { ProjectContext } = require('./context');
7
7
  const { TECHNIQUES, STACKS } = require('./techniques');
8
8
  const { TEMPLATES } = require('./setup');
9
9
  const { buildSettingsForProfile } = require('./governance');
10
+ const { getMcpPackPreflight } = require('./mcp-packs');
10
11
  const { writeActivityArtifact, writeRollbackArtifact } = require('./activity');
11
12
 
12
13
  const TEMPLATE_DIR_MAP = {
@@ -374,6 +375,8 @@ async function buildProposalBundle(options) {
374
375
  const ctx = new ProjectContext(options.dir);
375
376
  const stacks = ctx.detectStacks(STACKS);
376
377
  const report = await analyzeProject({ ...options, mode: 'augment' });
378
+ const mcpPreflightWarnings = getMcpPackPreflight(options.mcpPacks || [])
379
+ .filter(item => item.missingEnvVars.length > 0);
377
380
  const groups = getFailedTemplateGroups(ctx, options.only || []);
378
381
  const proposals = [];
379
382
 
@@ -406,6 +409,7 @@ async function buildProposalBundle(options) {
406
409
  strengthsPreserved: report.strengthsPreserved,
407
410
  topNextActions: report.topNextActions,
408
411
  riskNotes: report.riskNotes,
412
+ mcpPreflightWarnings,
409
413
  proposals,
410
414
  };
411
415
  }
@@ -422,6 +426,14 @@ function printProposalBundle(bundle, options = {}) {
422
426
  console.log(` ${bundle.projectSummary.name} | maturity=${bundle.projectSummary.maturity} | score=${bundle.projectSummary.score}/100`);
423
427
  console.log('');
424
428
 
429
+ if (bundle.mcpPreflightWarnings && bundle.mcpPreflightWarnings.length > 0) {
430
+ console.log(' MCP Preflight Warnings');
431
+ for (const warning of bundle.mcpPreflightWarnings) {
432
+ console.log(` - ${warning.label}: missing ${warning.missingEnvVars.join(', ')}`);
433
+ }
434
+ console.log('');
435
+ }
436
+
425
437
  if (bundle.proposals.length === 0) {
426
438
  console.log(' No templated proposals are needed right now.');
427
439
  console.log('');
@@ -523,6 +535,8 @@ function resolvePlan(bundle, options) {
523
535
  async function applyProposalBundle(options) {
524
536
  const liveBundle = options.planFile ? null : await buildProposalBundle(options);
525
537
  const bundle = resolvePlan(liveBundle, options);
538
+ const mcpPreflightWarnings = getMcpPackPreflight(options.mcpPacks || [])
539
+ .filter(item => item.missingEnvVars.length > 0);
526
540
  const selectedIds = options.only && options.only.length > 0
527
541
  ? new Set(options.only)
528
542
  : null;
@@ -589,6 +603,7 @@ async function applyProposalBundle(options) {
589
603
  dryRun: options.dryRun === true,
590
604
  rollbackArtifact: rollback ? rollback.relativePath : null,
591
605
  activityArtifact: activity ? activity.relativePath : null,
606
+ mcpPreflightWarnings,
592
607
  };
593
608
  }
594
609
 
@@ -607,6 +622,12 @@ function printApplyResult(result, options = {}) {
607
622
  console.log(` Applied proposal bundles: ${result.appliedProposalIds.join(', ') || 'none'}`);
608
623
  console.log(` Created files: ${result.createdFiles.join(', ') || 'none'}`);
609
624
  console.log(` Patched files: ${result.patchedFiles.join(', ') || 'none'}`);
625
+ if (result.mcpPreflightWarnings && result.mcpPreflightWarnings.length > 0) {
626
+ console.log(' MCP preflight warnings:');
627
+ for (const warning of result.mcpPreflightWarnings) {
628
+ console.log(` - ${warning.label}: missing ${warning.missingEnvVars.join(', ')}`);
629
+ }
630
+ }
610
631
  if (result.rollbackArtifact) {
611
632
  console.log(` Rollback: ${result.rollbackArtifact}`);
612
633
  }
package/src/setup.js CHANGED
@@ -9,6 +9,7 @@ const { TECHNIQUES, STACKS } = require('./techniques');
9
9
  const { ProjectContext } = require('./context');
10
10
  const { audit } = require('./audit');
11
11
  const { buildSettingsForProfile } = require('./governance');
12
+ const { getMcpPackPreflight } = require('./mcp-packs');
12
13
 
13
14
  // ============================================================
14
15
  // Helper: detect project scripts from package.json
@@ -1117,6 +1118,8 @@ async function setup(options) {
1117
1118
  const silent = options.silent === true;
1118
1119
  const writtenFiles = [];
1119
1120
  const preservedFiles = [];
1121
+ const mcpPreflightWarnings = getMcpPackPreflight(options.mcpPacks || [])
1122
+ .filter(item => item.missingEnvVars.length > 0);
1120
1123
 
1121
1124
  function log(message = '') {
1122
1125
  if (!silent) {
@@ -1243,6 +1246,15 @@ async function setup(options) {
1243
1246
  }
1244
1247
 
1245
1248
  log('');
1249
+ if (mcpPreflightWarnings.length > 0) {
1250
+ log('\x1b[33m MCP Preflight Warnings\x1b[0m');
1251
+ for (const warning of mcpPreflightWarnings) {
1252
+ log(` - ${warning.label}: missing ${warning.missingEnvVars.join(', ')}`);
1253
+ log(' \x1b[2m Settings were generated with placeholders, but this MCP server will not start until those env vars are set.\x1b[0m');
1254
+ }
1255
+ log('');
1256
+ }
1257
+
1246
1258
  log(' Run \x1b[1mnpx claudex-setup audit\x1b[0m to check your score.');
1247
1259
  log('');
1248
1260
 
@@ -1252,6 +1264,7 @@ async function setup(options) {
1252
1264
  writtenFiles,
1253
1265
  preservedFiles,
1254
1266
  stacks,
1267
+ mcpPreflightWarnings,
1255
1268
  };
1256
1269
  }
1257
1270
 
package/src/techniques.js CHANGED
@@ -696,8 +696,12 @@ const TECHNIQUES = {
696
696
  id: 1801,
697
697
  name: '2+ MCP servers for rich tooling',
698
698
  check: (ctx) => {
699
+ let count = 0;
699
700
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
700
- return !!(settings && settings.mcpServers && Object.keys(settings.mcpServers).length >= 2);
701
+ if (settings && settings.mcpServers) count += Object.keys(settings.mcpServers).length;
702
+ const mcpJson = ctx.jsonFile('.mcp.json');
703
+ if (mcpJson && mcpJson.mcpServers) count += Object.keys(mcpJson.mcpServers).length;
704
+ return count >= 2;
701
705
  },
702
706
  impact: 'medium',
703
707
  rating: 4,