vibecheck-mcp-server 3.1.3 → 3.1.6

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/index.js CHANGED
@@ -89,6 +89,9 @@ import { TRUTH_FIREWALL_TOOLS, handleTruthFirewallTool } from "./truth-firewall-
89
89
  // Import Consolidated Tools (15 focused tools - recommended surface)
90
90
  import { CONSOLIDATED_TOOLS, handleConsolidatedTool } from "./consolidated-tools.js";
91
91
 
92
+ // Import tier auth for entitlement checking
93
+ import { checkFeatureAccess } from "./tier-auth.js";
94
+
92
95
  // ============================================================================
93
96
  // TOOL DEFINITIONS - Public Tools (Clean Product Surface)
94
97
  // ============================================================================
@@ -300,7 +303,7 @@ const TOOLS = USE_CONSOLIDATED_TOOLS ? [
300
303
  // 3. GATE - Enforce truth in CI
301
304
  {
302
305
  name: "vibecheck.gate",
303
- description: "🚦 Enforce truth in CI — fail builds on policy violations",
306
+ description: "🚦 Enforce truth in CI — fail builds on policy violations (STARTER tier)",
304
307
  inputSchema: {
305
308
  type: "object",
306
309
  properties: {
@@ -327,7 +330,7 @@ const TOOLS = USE_CONSOLIDATED_TOOLS ? [
327
330
  {
328
331
  name: "vibecheck.fix",
329
332
  description:
330
- "šŸ”§ Fix Missions v1 — AI-powered surgical fixes with proof verification loop",
333
+ "šŸ”§ Fix Missions v1 — AI-powered surgical fixes with proof verification loop. --apply and --autopilot require PRO tier ($99/mo).",
331
334
  inputSchema: {
332
335
  type: "object",
333
336
  properties: {
@@ -396,7 +399,7 @@ const TOOLS = USE_CONSOLIDATED_TOOLS ? [
396
399
  // 7. PROVE - One Command Reality Proof (orchestrates ctx → reality → ship → fix)
397
400
  {
398
401
  name: "vibecheck.prove",
399
- description: "šŸ”¬ One Command Reality Proof — orchestrates ctx → reality → ship → fix loop until SHIP or stuck",
402
+ description: "šŸ”¬ One Command Reality Proof — orchestrates ctx → reality → ship → fix loop until SHIP or stuck (PRO tier)",
400
403
  inputSchema: {
401
404
  type: "object",
402
405
  properties: {
@@ -593,11 +596,11 @@ const TOOLS = USE_CONSOLIDATED_TOOLS ? [
593
596
  },
594
597
  },
595
598
 
596
- // 13. AUTOPILOT PLAN - Generate fix plan (Pro/Compliance)
599
+ // 13. AUTOPILOT PLAN - Generate fix plan (PRO tier)
597
600
  {
598
601
  name: "vibecheck.autopilot_plan",
599
602
  description:
600
- "šŸ¤– Autopilot Plan — scan codebase, group issues into fix packs, estimate risk (Pro/Compliance)",
603
+ "šŸ¤– Autopilot Plan — scan codebase, group issues into fix packs, estimate risk (PRO tier)",
601
604
  inputSchema: {
602
605
  type: "object",
603
606
  properties: {
@@ -620,11 +623,11 @@ const TOOLS = USE_CONSOLIDATED_TOOLS ? [
620
623
  },
621
624
  },
622
625
 
623
- // 14. AUTOPILOT APPLY - Apply fixes (Pro/Compliance)
626
+ // 14. AUTOPILOT APPLY - Apply fixes (PRO tier)
624
627
  {
625
628
  name: "vibecheck.autopilot_apply",
626
629
  description:
627
- "šŸ”§ Autopilot Apply — apply fix packs with verification, re-scan to confirm (Pro/Compliance)",
630
+ "šŸ”§ Autopilot Apply — apply fix packs with verification, re-scan to confirm (PRO tier)",
628
631
  inputSchema: {
629
632
  type: "object",
630
633
  properties: {
@@ -661,7 +664,7 @@ const TOOLS = USE_CONSOLIDATED_TOOLS ? [
661
664
  {
662
665
  name: "vibecheck.badge",
663
666
  description:
664
- "šŸ… Ship Badge — generate a badge for README/PR showing scan status",
667
+ "šŸ… Ship Badge — generate a badge for README/PR showing scan status (STARTER tier)",
665
668
  inputSchema: {
666
669
  type: "object",
667
670
  properties: {
@@ -1046,8 +1049,27 @@ class VibecheckMCP {
1046
1049
  return { content: [{ type: "text", text }] };
1047
1050
  }
1048
1051
 
1049
- error(text) {
1050
- return { content: [{ type: "text", text: `āŒ ${text}` }], isError: true };
1052
+ error(text, options = {}) {
1053
+ const { code, suggestion, nextSteps = [] } = options;
1054
+
1055
+ let errorText = `āŒ ${text}`;
1056
+
1057
+ if (code) {
1058
+ errorText += `\n\n**Error Code:** \`${code}\``;
1059
+ }
1060
+
1061
+ if (suggestion) {
1062
+ errorText += `\n\nšŸ’” **Suggestion:** ${suggestion}`;
1063
+ }
1064
+
1065
+ if (nextSteps.length > 0) {
1066
+ errorText += `\n\n**Next Steps:**\n`;
1067
+ nextSteps.forEach((step, i) => {
1068
+ errorText += `${i + 1}. ${step}\n`;
1069
+ });
1070
+ }
1071
+
1072
+ return { content: [{ type: "text", text: errorText }], isError: true };
1051
1073
  }
1052
1074
 
1053
1075
  // Validate project path exists and is accessible
@@ -1055,11 +1077,29 @@ class VibecheckMCP {
1055
1077
  try {
1056
1078
  const stats = require("fs").statSync(projectPath);
1057
1079
  if (!stats.isDirectory()) {
1058
- return { valid: false, error: `Path is not a directory: ${projectPath}` };
1080
+ return {
1081
+ valid: false,
1082
+ error: `Path is not a directory: ${projectPath}`,
1083
+ suggestion: "Provide a directory path, not a file",
1084
+ nextSteps: [
1085
+ "Check the path you provided",
1086
+ "Ensure it points to a project directory",
1087
+ ],
1088
+ };
1059
1089
  }
1060
1090
  return { valid: true };
1061
1091
  } catch (e) {
1062
- return { valid: false, error: `Cannot access path: ${projectPath} (${e.code || e.message})` };
1092
+ return {
1093
+ valid: false,
1094
+ error: `Cannot access path: ${projectPath}`,
1095
+ code: e.code || "PATH_ACCESS_ERROR",
1096
+ suggestion: "Check that the path exists and you have read permissions",
1097
+ nextSteps: [
1098
+ "Verify the path is correct",
1099
+ "Check file permissions",
1100
+ "Ensure the directory exists",
1101
+ ],
1102
+ };
1063
1103
  }
1064
1104
  }
1065
1105
 
@@ -1067,6 +1107,16 @@ class VibecheckMCP {
1067
1107
  // SCAN
1068
1108
  // ============================================================================
1069
1109
  async handleScan(projectPath, args) {
1110
+ // Validate project path first
1111
+ const validation = this.validateProjectPath(projectPath);
1112
+ if (!validation.valid) {
1113
+ return this.error(validation.error, {
1114
+ code: validation.code || "INVALID_PATH",
1115
+ suggestion: validation.suggestion,
1116
+ nextSteps: validation.nextSteps || [],
1117
+ });
1118
+ }
1119
+
1070
1120
  const profile = args?.profile || "quick";
1071
1121
  const format = args?.format || "text";
1072
1122
  const only = args?.only;
@@ -1105,18 +1155,38 @@ class VibecheckMCP {
1105
1155
  }
1106
1156
 
1107
1157
  output += `\nšŸ“„ **Report:** .vibecheck/report.html\n`;
1158
+ output += "\n---\n_Context Enhanced by vibecheck AI_\n";
1159
+ return this.success(output);
1108
1160
  } catch (err) {
1109
- output += `\nāš ļø Scan error: ${err.message}\n`;
1161
+ return this.error(`Scan failed: ${err.message}`, {
1162
+ code: "SCAN_ERROR",
1163
+ suggestion: "Check that the project path is valid and contains scanable code",
1164
+ nextSteps: [
1165
+ "Verify the project path is correct",
1166
+ "Ensure you have read permissions",
1167
+ "Check that required dependencies are installed",
1168
+ "Try running: vibecheck scan --help",
1169
+ ],
1170
+ });
1110
1171
  }
1111
-
1112
- output += "\n---\n_Context Enhanced by vibecheck AI_\n";
1113
- return this.success(output);
1114
1172
  }
1115
1173
 
1116
1174
  // ============================================================================
1117
1175
  // GATE
1118
1176
  // ============================================================================
1119
1177
  async handleGate(projectPath, args) {
1178
+ // Check tier access (STARTER tier required)
1179
+ const access = await checkFeatureAccess("gate", args?.apiKey);
1180
+ if (!access.hasAccess) {
1181
+ return {
1182
+ content: [{
1183
+ type: "text",
1184
+ text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nCurrent tier: ${access.tier}\nUpgrade at: ${access.upgradeUrl}`
1185
+ }],
1186
+ isError: true
1187
+ };
1188
+ }
1189
+
1120
1190
  const policy = args?.policy || "strict";
1121
1191
 
1122
1192
  let output = "# 🚦 vibecheck Gate\n\n";
@@ -1148,6 +1218,20 @@ class VibecheckMCP {
1148
1218
  // FIX MISSIONS v1
1149
1219
  // ============================================================================
1150
1220
  async handleFix(projectPath, args) {
1221
+ // Check tier access for --apply and --autopilot (PRO tier required)
1222
+ if (args?.apply || args?.autopilot) {
1223
+ const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
1224
+ if (!access.hasAccess) {
1225
+ return {
1226
+ content: [{
1227
+ type: "text",
1228
+ text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nCurrent tier: ${access.tier}\nUpgrade at: ${access.upgradeUrl}\n\nNote: --prompt-only mode is available for FREE tier.`
1229
+ }],
1230
+ isError: true
1231
+ };
1232
+ }
1233
+ }
1234
+
1151
1235
  const mode = args?.autopilot ? "Autopilot" :
1152
1236
  args?.apply ? "Apply" :
1153
1237
  args?.promptOnly ? "Prompt Only" : "Plan";
@@ -1319,6 +1403,18 @@ class VibecheckMCP {
1319
1403
  // PROVE - One Command Reality Proof
1320
1404
  // ============================================================================
1321
1405
  async handleProve(projectPath, args) {
1406
+ // Check tier access (PRO tier required)
1407
+ const access = await checkFeatureAccess("prove", args?.apiKey);
1408
+ if (!access.hasAccess) {
1409
+ return {
1410
+ content: [{
1411
+ type: "text",
1412
+ text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nCurrent tier: ${access.tier}\nUpgrade at: ${access.upgradeUrl}`
1413
+ }],
1414
+ isError: true
1415
+ };
1416
+ }
1417
+
1322
1418
  let output = "# šŸ”¬ vibecheck prove\n\n";
1323
1419
  output += `**URL:** ${args?.url || "(static only)"}\n`;
1324
1420
  output += `**Max Fix Rounds:** ${args?.maxFixRounds || 3}\n\n`;
@@ -1787,9 +1883,21 @@ class VibecheckMCP {
1787
1883
  }
1788
1884
 
1789
1885
  // ============================================================================
1790
- // AUTOPILOT PLAN - Generate fix plan (Pro/Compliance)
1886
+ // AUTOPILOT PLAN - Generate fix plan (PRO tier)
1791
1887
  // ============================================================================
1792
1888
  async handleAutopilotPlan(projectPath, args) {
1889
+ // Check tier access (PRO tier required)
1890
+ const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
1891
+ if (!access.hasAccess) {
1892
+ return {
1893
+ content: [{
1894
+ type: "text",
1895
+ text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nCurrent tier: ${access.tier}\nUpgrade at: ${access.upgradeUrl}`
1896
+ }],
1897
+ isError: true
1898
+ };
1899
+ }
1900
+
1793
1901
  let output = "# šŸ¤– vibecheck Autopilot Plan\n\n";
1794
1902
  output += `**Path:** ${projectPath}\n`;
1795
1903
  output += `**Profile:** ${args?.profile || "ship"}\n\n`;
@@ -1865,9 +1973,21 @@ class VibecheckMCP {
1865
1973
  }
1866
1974
 
1867
1975
  // ============================================================================
1868
- // AUTOPILOT APPLY - Apply fixes (Pro/Compliance)
1976
+ // AUTOPILOT APPLY - Apply fixes (PRO tier)
1869
1977
  // ============================================================================
1870
1978
  async handleAutopilotApply(projectPath, args) {
1979
+ // Check tier access (PRO tier required)
1980
+ const access = await checkFeatureAccess("fix.apply_patches", args?.apiKey);
1981
+ if (!access.hasAccess) {
1982
+ return {
1983
+ content: [{
1984
+ type: "text",
1985
+ text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nCurrent tier: ${access.tier}\nUpgrade at: ${access.upgradeUrl}`
1986
+ }],
1987
+ isError: true
1988
+ };
1989
+ }
1990
+
1871
1991
  let output = "# šŸ”§ vibecheck Autopilot Apply\n\n";
1872
1992
  output += `**Path:** ${projectPath}\n`;
1873
1993
  output += `**Profile:** ${args?.profile || "ship"}\n`;
@@ -1919,6 +2039,18 @@ class VibecheckMCP {
1919
2039
  // BADGE - Generate ship badge
1920
2040
  // ============================================================================
1921
2041
  async handleBadge(projectPath, args) {
2042
+ // Check tier access (STARTER tier required)
2043
+ const access = await checkFeatureAccess("badge", args?.apiKey);
2044
+ if (!access.hasAccess) {
2045
+ return {
2046
+ content: [{
2047
+ type: "text",
2048
+ text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nCurrent tier: ${access.tier}\nUpgrade at: ${access.upgradeUrl}`
2049
+ }],
2050
+ isError: true
2051
+ };
2052
+ }
2053
+
1922
2054
  const format = args?.format || "svg";
1923
2055
 
1924
2056
  let output = "# šŸ… vibecheck Badge\n\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibecheck-mcp-server",
3
- "version": "3.1.3",
3
+ "version": "3.1.6",
4
4
  "description": "Professional MCP server for vibecheck - Intelligent development environment vibechecks",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -21,7 +21,7 @@
21
21
  "architecture"
22
22
  ],
23
23
  "dependencies": {
24
- "@modelcontextprotocol/sdk": "^0.5.0"
24
+ "@modelcontextprotocol/sdk": "^1.0.0"
25
25
  },
26
26
  "engines": {
27
27
  "node": ">=18.0.0"
package/premium-tools.js CHANGED
@@ -417,19 +417,19 @@ export async function handlePremiumTool(name, args, logger) {
417
417
 
418
418
  // Map premium tools to required features (all require starter+)
419
419
  const featureMap = {
420
- 'run_ship': 'smells', // ship check requires starter+
421
- 'run_reality': 'smells', // reality mode requires starter+
422
- 'run_mockproof': 'smells', // mockproof requires starter+
423
- 'run_airlock': 'breaking', // supply chain analysis requires pro+
424
- 'get_last_run': 'verify', // basic access
425
- 'open_artifact': 'verify', // basic access
426
- 'rerun_last_check': 'verify', // basic access
427
- 'run_doctor': 'verify', // basic access
428
- 'edit_policies': 'breaking', // policy editing requires pro+
429
- 'explain_finding': 'quality', // explanations require starter+
430
- 'policy_patch': 'smells', // patching requires starter+
431
- 'enter_fix_mode': 'smells', // fix mode requires starter+
432
- 'get_status': 'verify' // status check is free
420
+ 'run_ship': 'ship', // ship check is FREE (static-only)
421
+ 'run_reality': 'reality.preview', // reality preview is FREE, full requires STARTER+
422
+ 'run_mockproof': 'reality.full', // mockproof requires STARTER+
423
+ 'run_airlock': 'prove', // supply chain analysis requires PRO
424
+ 'get_last_run': 'scan', // basic access - FREE
425
+ 'open_artifact': 'scan', // basic access - FREE
426
+ 'rerun_last_check': 'scan', // basic access - FREE
427
+ 'run_doctor': 'doctor', // doctor is FREE
428
+ 'edit_policies': 'gate', // policy editing requires STARTER
429
+ 'explain_finding': 'scan', // explanations are FREE
430
+ 'policy_patch': 'gate', // patching requires STARTER
431
+ 'enter_fix_mode': 'fix.plan_only', // fix mode planning is FREE, apply requires PRO
432
+ 'get_status': 'status' // status check is FREE
433
433
  };
434
434
 
435
435
  const requiredFeature = featureMap[name];
package/tier-auth.js CHANGED
@@ -8,27 +8,144 @@ import fs from "fs/promises";
8
8
  import path from "path";
9
9
  import os from "os";
10
10
 
11
- // Tier definitions
11
+ // Tier definitions - MUST MATCH CLI entitlements-v2.js
12
12
  export const TIERS = {
13
13
  free: {
14
- name: 'Free',
15
- features: ['verify', 'quality', 'hallucination'],
16
- limits: { scans: 10, projects: 1 }
14
+ name: 'FREE',
15
+ price: 0,
16
+ order: 0,
17
+ features: [
18
+ // Core commands
19
+ 'scan', 'ship', 'ship.static',
20
+ // Setup
21
+ 'init', 'init.local', 'doctor', 'status', 'install', 'preflight', 'watch', 'watch.local',
22
+ // AI Truth
23
+ 'ctx', 'guard', 'context', 'mdc', 'contracts',
24
+ // Quality
25
+ 'verify', 'quality', 'polish', 'checkpoint', 'checkpoint.basic',
26
+ // Fix (plan only)
27
+ 'fix', 'fix.plan_only',
28
+ // Reality (preview)
29
+ 'reality', 'reality.preview',
30
+ // MCP (help only)
31
+ 'mcp.help_only',
32
+ // Report (html/md only)
33
+ 'report', 'report.html_md',
34
+ ],
35
+ limits: {
36
+ scans: 50,
37
+ shipChecks: 20,
38
+ realityMaxPages: 5,
39
+ realityMaxClicks: 20,
40
+ fixApplyPatches: false,
41
+ mcpRateLimit: 10, // requests per minute
42
+ },
43
+ // MCP tools allowed on FREE
44
+ mcpTools: ['vibecheck.status', 'vibecheck.get_truthpack'],
17
45
  },
18
46
  starter: {
19
- name: 'Starter',
20
- features: ['verify', 'quality', 'hallucination', 'smells', 'breaking'],
21
- limits: { scans: 100, projects: 3 }
47
+ name: 'STARTER',
48
+ price: 39, // Updated pricing
49
+ order: 1,
50
+ features: [
51
+ // All FREE features plus...
52
+ // Init connect
53
+ 'init.connect',
54
+ // Scan autofix
55
+ 'scan.autofix',
56
+ // CI/CD
57
+ 'gate', 'pr', 'badge', 'launch', 'dashboard_sync',
58
+ // Watch PR
59
+ 'watch.pr',
60
+ // Report formats
61
+ 'report.sarif_csv',
62
+ // Reality basic
63
+ 'reality.basic',
64
+ // MCP read-only
65
+ 'mcp', 'mcp.read_only',
66
+ // Ship full
67
+ 'ship.full',
68
+ ],
69
+ limits: {
70
+ scans: -1,
71
+ shipChecks: -1,
72
+ realityMaxPages: 50,
73
+ realityMaxClicks: 200,
74
+ fixApplyPatches: false,
75
+ mcpRateLimit: 60, // requests per minute
76
+ },
77
+ // MCP tools allowed on STARTER (read-only safe tools)
78
+ mcpTools: [
79
+ 'vibecheck.status',
80
+ 'vibecheck.get_truthpack',
81
+ 'vibecheck.scan',
82
+ 'vibecheck.list_routes',
83
+ 'vibecheck.list_env',
84
+ 'vibecheck.get_findings',
85
+ 'vibecheck.contracts_diff',
86
+ 'vibecheck.validate_claim',
87
+ 'vibecheck.compile_context',
88
+ ],
22
89
  },
23
90
  pro: {
24
- name: 'Professional',
25
- features: ['verify', 'quality', 'hallucination', 'smells', 'breaking', 'mdc'],
26
- limits: { scans: 1000, projects: 10 }
91
+ name: 'PRO',
92
+ price: 99,
93
+ order: 2,
94
+ features: [
95
+ // All STARTER features plus...
96
+ // Prove
97
+ 'prove',
98
+ // Fix apply
99
+ 'fix.apply_patches', 'fix.loop',
100
+ // Checkpoint advanced
101
+ 'checkpoint.hallucination',
102
+ // Reality advanced
103
+ 'reality.full', 'reality.advanced_auth_boundary',
104
+ // Premium
105
+ 'replay', 'share', 'ai-test', 'permissions', 'graph',
106
+ // MCP full
107
+ 'mcp.full',
108
+ ],
109
+ limits: {
110
+ scans: -1,
111
+ shipChecks: -1,
112
+ realityMaxPages: -1,
113
+ realityMaxClicks: -1,
114
+ fixApplyPatches: true,
115
+ mcpRateLimit: -1, // unlimited
116
+ },
117
+ // MCP tools allowed on PRO (full suite)
118
+ mcpTools: [
119
+ // All STARTER tools plus...
120
+ 'vibecheck.generate_mission',
121
+ 'vibecheck.verify_patch',
122
+ 'vibecheck.explain_evidence',
123
+ 'vibecheck.fix',
124
+ 'vibecheck.proof',
125
+ 'vibecheck.prove',
126
+ 'vibecheck.ship',
127
+ 'vibecheck.reality',
128
+ 'vibecheck.permissions',
129
+ 'vibecheck.graph',
130
+ ],
27
131
  },
28
- enterprise: {
29
- name: 'Enterprise',
30
- features: ['verify', 'quality', 'hallucination', 'smells', 'breaking', 'mdc'],
31
- limits: { scans: -1, projects: -1 } // unlimited
132
+ compliance: {
133
+ name: 'COMPLIANCE',
134
+ price: 0, // Enterprise/on-prem
135
+ order: 3,
136
+ features: [
137
+ // All PRO features plus...
138
+ 'report.compliance_packs',
139
+ ],
140
+ limits: {
141
+ scans: -1,
142
+ shipChecks: -1,
143
+ realityMaxPages: -1,
144
+ realityMaxClicks: -1,
145
+ fixApplyPatches: true,
146
+ mcpRateLimit: -1,
147
+ },
148
+ mcpTools: ['*'], // All tools
32
149
  }
33
150
  };
34
151
 
@@ -47,20 +164,25 @@ async function loadUserConfig() {
47
164
 
48
165
  /**
49
166
  * Determine tier from API key
167
+ * Matches CLI entitlements-v2.js logic
50
168
  */
51
169
  function getTierFromApiKey(apiKey) {
52
170
  if (!apiKey) return 'free';
53
171
 
172
+ // Check API key prefix patterns (matches CLI)
54
173
  if (apiKey.startsWith('gr_starter_')) return 'starter';
55
174
  if (apiKey.startsWith('gr_pro_')) return 'pro';
56
- if (apiKey.startsWith('gr_ent_')) return 'enterprise';
175
+ if (apiKey.startsWith('gr_compliance_') || apiKey.startsWith('gr_ent_')) return 'compliance';
57
176
  if (apiKey.startsWith('gr_free_')) return 'free';
58
177
 
178
+ // Try to fetch from API (same as CLI)
179
+ // For now, default to free - API lookup would happen in production
59
180
  return 'free'; // default for unknown keys
60
181
  }
61
182
 
62
183
  /**
63
184
  * Check if user has access to a specific feature
185
+ * Matches CLI entitlements-v2.js logic
64
186
  */
65
187
  export async function checkFeatureAccess(featureName, providedApiKey = null) {
66
188
  // Try to load user config
@@ -76,25 +198,47 @@ export async function checkFeatureAccess(featureName, providedApiKey = null) {
76
198
  };
77
199
  }
78
200
 
79
- const tier = getTierFromApiKey(apiKey);
80
- const tierConfig = TIERS[tier];
201
+ const currentTier = getTierFromApiKey(apiKey);
202
+ const currentTierConfig = TIERS[currentTier];
81
203
 
82
- if (!tierConfig.features.includes(featureName)) {
83
- const requiredTier = Object.entries(TIERS).find(([_, config]) =>
84
- config.features.includes(featureName)
85
- )?.[0];
86
-
204
+ // Find which tier has this feature
205
+ let requiredTier = null;
206
+ let requiredTierConfig = null;
207
+
208
+ for (const [tierName, tierConfig] of Object.entries(TIERS)) {
209
+ if (tierConfig.features.includes(featureName)) {
210
+ requiredTier = tierName;
211
+ requiredTierConfig = tierConfig;
212
+ break;
213
+ }
214
+ }
215
+
216
+ // If feature not found in any tier, deny access
217
+ if (!requiredTier) {
87
218
  return {
88
219
  hasAccess: false,
89
- tier,
90
- reason: `${featureName} requires ${requiredTier} tier or higher`,
220
+ tier: currentTier,
221
+ reason: `${featureName} is not available in any tier`,
222
+ upgradeUrl: 'https://vibecheckai.dev/pricing'
223
+ };
224
+ }
225
+
226
+ // Check if current tier meets minimum requirement (using order)
227
+ const hasAccess = currentTierConfig.order >= requiredTierConfig.order;
228
+
229
+ if (!hasAccess) {
230
+ return {
231
+ hasAccess: false,
232
+ tier: currentTier,
233
+ requiredTier,
234
+ reason: `${featureName} requires ${requiredTierConfig.name} tier ($${requiredTierConfig.price}/mo) or higher. Current tier: ${currentTierConfig.name}`,
91
235
  upgradeUrl: 'https://vibecheckai.dev/pricing'
92
236
  };
93
237
  }
94
238
 
95
239
  return {
96
240
  hasAccess: true,
97
- tier,
241
+ tier: currentTier,
98
242
  reason: 'Access granted'
99
243
  };
100
244
  }
@@ -122,6 +266,93 @@ export function withTierCheck(featureName, handler) {
122
266
  };
123
267
  }
124
268
 
269
+ /**
270
+ * Check if user has access to a specific MCP tool
271
+ * MCP tools have specific tier requirements separate from CLI features
272
+ */
273
+ export async function checkMcpToolAccess(toolName, providedApiKey = null) {
274
+ const userConfig = await loadUserConfig();
275
+ const apiKey = providedApiKey || userConfig?.apiKey;
276
+
277
+ const currentTier = getTierFromApiKey(apiKey);
278
+ const currentTierConfig = TIERS[currentTier];
279
+
280
+ // Check if tool is allowed for current tier
281
+ const allowedTools = [];
282
+
283
+ // Accumulate tools from current tier and all lower tiers
284
+ for (const [tierName, tierConfig] of Object.entries(TIERS)) {
285
+ if (tierConfig.order <= currentTierConfig.order) {
286
+ if (tierConfig.mcpTools) {
287
+ if (tierConfig.mcpTools.includes('*')) {
288
+ // Compliance tier - all tools allowed
289
+ return {
290
+ hasAccess: true,
291
+ tier: currentTier,
292
+ reason: 'Full MCP access'
293
+ };
294
+ }
295
+ allowedTools.push(...tierConfig.mcpTools);
296
+ }
297
+ }
298
+ }
299
+
300
+ // Check if tool is in allowed list
301
+ const hasAccess = allowedTools.includes(toolName);
302
+
303
+ if (!hasAccess) {
304
+ // Find which tier has this tool
305
+ let requiredTier = null;
306
+ for (const [tierName, tierConfig] of Object.entries(TIERS)) {
307
+ if (tierConfig.mcpTools?.includes(toolName) || tierConfig.mcpTools?.includes('*')) {
308
+ requiredTier = tierName;
309
+ break;
310
+ }
311
+ }
312
+
313
+ const requiredTierConfig = requiredTier ? TIERS[requiredTier] : null;
314
+
315
+ return {
316
+ hasAccess: false,
317
+ tier: currentTier,
318
+ requiredTier,
319
+ reason: requiredTierConfig
320
+ ? `${toolName} requires ${requiredTierConfig.name} tier ($${requiredTierConfig.price}/mo). Current: ${currentTierConfig.name}`
321
+ : `${toolName} is not available`,
322
+ upgradeUrl: 'https://vibecheckai.dev/pricing'
323
+ };
324
+ }
325
+
326
+ return {
327
+ hasAccess: true,
328
+ tier: currentTier,
329
+ reason: 'Access granted'
330
+ };
331
+ }
332
+
333
+ /**
334
+ * Middleware for MCP tool handlers with tool-specific checking
335
+ */
336
+ export function withMcpToolCheck(toolName, handler) {
337
+ return async (args) => {
338
+ const access = await checkMcpToolAccess(toolName, args?.apiKey);
339
+
340
+ if (!access.hasAccess) {
341
+ return {
342
+ content: [{
343
+ type: "text",
344
+ text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nUpgrade at: ${access.upgradeUrl}`
345
+ }],
346
+ isError: true
347
+ };
348
+ }
349
+
350
+ // Add tier info to args for the handler
351
+ args._tier = access.tier;
352
+ return handler(args);
353
+ };
354
+ }
355
+
125
356
  /**
126
357
  * Get current user info
127
358
  */
@@ -136,12 +367,46 @@ export async function getUserInfo() {
136
367
  }
137
368
 
138
369
  const tier = getTierFromApiKey(config.apiKey);
370
+ const tierConfig = TIERS[tier];
371
+
139
372
  return {
140
373
  authenticated: true,
141
374
  tier,
142
375
  email: config.email,
143
376
  authenticatedAt: config.authenticatedAt,
144
- features: TIERS[tier].features,
145
- limits: TIERS[tier].limits
377
+ features: tierConfig.features,
378
+ limits: tierConfig.limits,
379
+ mcpTools: tierConfig.mcpTools,
380
+ };
381
+ }
382
+
383
+ /**
384
+ * Get list of MCP tools available for current tier
385
+ */
386
+ export async function getAvailableMcpTools(providedApiKey = null) {
387
+ const userConfig = await loadUserConfig();
388
+ const apiKey = providedApiKey || userConfig?.apiKey;
389
+
390
+ const currentTier = getTierFromApiKey(apiKey);
391
+ const currentTierConfig = TIERS[currentTier];
392
+
393
+ const allowedTools = new Set();
394
+
395
+ // Accumulate tools from current tier and all lower tiers
396
+ for (const [tierName, tierConfig] of Object.entries(TIERS)) {
397
+ if (tierConfig.order <= currentTierConfig.order) {
398
+ if (tierConfig.mcpTools) {
399
+ if (tierConfig.mcpTools.includes('*')) {
400
+ return { tier: currentTier, tools: ['*'], unlimited: true };
401
+ }
402
+ tierConfig.mcpTools.forEach(t => allowedTools.add(t));
403
+ }
404
+ }
405
+ }
406
+
407
+ return {
408
+ tier: currentTier,
409
+ tools: Array.from(allowedTools),
410
+ unlimited: false
146
411
  };
147
412
  }
@@ -278,16 +278,16 @@ export const VIBECHECK_TOOLS = [
278
278
  export async function handleVibecheckTool(toolName, args) {
279
279
  const projectPath = path.resolve(args.projectPath || ".");
280
280
 
281
- // Map tools to required features
281
+ // Map tools to required features (matches CLI entitlements-v2.js)
282
282
  const featureMap = {
283
- "vibecheck.verify": "verify",
284
- "vibecheck.quality": "quality",
285
- "vibecheck.smells": "smells",
286
- "vibecheck.hallucination": "hallucination",
287
- "vibecheck.breaking": "breaking",
288
- "vibecheck.mdc": "mdc",
289
- "vibecheck.coverage": "quality", // map to quality tier
290
- "vibecheck.autofix": "smells" // map to smells tier (fix requires starter+)
283
+ "vibecheck.verify": "verify", // FREE
284
+ "vibecheck.quality": "quality", // FREE
285
+ "vibecheck.smells": "smells", // STARTER
286
+ "vibecheck.hallucination": "hallucination", // FREE
287
+ "vibecheck.breaking": "breaking", // STARTER
288
+ "vibecheck.mdc": "mdc", // FREE
289
+ "vibecheck.coverage": "quality", // FREE
290
+ "vibecheck.autofix": "fix.apply_patches" // PRO (auto-apply requires PRO)
291
291
  };
292
292
 
293
293
  const requiredFeature = featureMap[toolName];