@syke1/mcp-server 1.7.1 → 1.8.1

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,7 @@
1
+ /**
2
+ * Project Scanner — Cortex-only tool.
3
+ * Scans the entire project dependency graph and generates a comprehensive
4
+ * onboarding document using AI (BYOK).
5
+ */
6
+ import { DependencyGraph } from "../graph";
7
+ export declare function scanProject(graph: DependencyGraph): Promise<string>;
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ /**
3
+ * Project Scanner — Cortex-only tool.
4
+ * Scans the entire project dependency graph and generates a comprehensive
5
+ * onboarding document using AI (BYOK).
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.scanProject = scanProject;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const provider_1 = require("./provider");
45
+ const context_extractor_1 = require("./context-extractor");
46
+ // ── Config file names to look for ──
47
+ const CONFIG_FILE_NAMES = [
48
+ "package.json", "tsconfig.json", "tsconfig.base.json",
49
+ "Cargo.toml", "go.mod", "go.sum",
50
+ "pubspec.yaml", "build.gradle", "pom.xml",
51
+ "Gemfile", "requirements.txt", "pyproject.toml", "setup.py",
52
+ "Makefile", "CMakeLists.txt",
53
+ "docker-compose.yml", "Dockerfile",
54
+ ".env.example",
55
+ ];
56
+ const MAX_CONFIG_LINES = 200;
57
+ // ── Core ──
58
+ async function scanProject(graph) {
59
+ const provider = (0, provider_1.getAIProvider)();
60
+ if (!provider) {
61
+ return "scan_project requires an AI API key.\n\nSet one of: GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY.";
62
+ }
63
+ const ctx = collectProjectContext(graph);
64
+ const systemPrompt = buildScanSystemPrompt();
65
+ const userPrompt = buildScanUserPrompt(ctx);
66
+ try {
67
+ return await provider.analyze(systemPrompt, userPrompt);
68
+ }
69
+ catch (err) {
70
+ return `AI analysis error: ${err.message || err}`;
71
+ }
72
+ }
73
+ // ── Context Collection ──
74
+ function collectProjectContext(graph) {
75
+ const projectRoot = graph.projectRoot;
76
+ // 1. Hub files — sorted by reverse dependency count
77
+ const hubFiles = getHubFiles(graph, 10);
78
+ // 2. Entry points — files with 0 reverse dependencies
79
+ const entryPoints = getEntryPoints(graph);
80
+ // 3. Circular dependencies (SCC clusters with size > 1)
81
+ const circularDependencies = getCircularDeps(graph);
82
+ // 4. Directory structure (depth-limited tree)
83
+ const directoryStructure = buildDirectoryTree(graph);
84
+ // 5. Config files
85
+ const configFiles = readConfigFiles(projectRoot);
86
+ // 6. Key file signatures (top 5 hub files)
87
+ const keyFileSignatures = buildKeySignatures(hubFiles.slice(0, 5), graph);
88
+ return {
89
+ projectRoot,
90
+ languages: graph.languages,
91
+ totalFiles: graph.files.size,
92
+ directoryStructure,
93
+ hubFiles,
94
+ entryPoints,
95
+ circularDependencies,
96
+ configFiles,
97
+ keyFileSignatures,
98
+ };
99
+ }
100
+ function getHubFiles(graph, topN) {
101
+ const counts = [];
102
+ for (const file of graph.files) {
103
+ const revDeps = graph.reverse.get(file) || [];
104
+ counts.push({ path: file, count: revDeps.length });
105
+ }
106
+ counts.sort((a, b) => b.count - a.count);
107
+ return counts.slice(0, topN).map((c) => ({
108
+ path: c.path,
109
+ relativePath: path.relative(graph.sourceDir, c.path).replace(/\\/g, "/"),
110
+ dependentCount: c.count,
111
+ }));
112
+ }
113
+ function getEntryPoints(graph) {
114
+ const entries = [];
115
+ for (const file of graph.files) {
116
+ const revDeps = graph.reverse.get(file) || [];
117
+ if (revDeps.length === 0) {
118
+ entries.push(path.relative(graph.sourceDir, file).replace(/\\/g, "/"));
119
+ }
120
+ }
121
+ return entries.slice(0, 20); // cap at 20
122
+ }
123
+ function getCircularDeps(graph) {
124
+ if (!graph.scc)
125
+ return [];
126
+ return graph.scc.components
127
+ .filter((c) => c.length > 1)
128
+ .map((cluster) => cluster.map((f) => path.relative(graph.sourceDir, f).replace(/\\/g, "/")));
129
+ }
130
+ function buildDirectoryTree(graph) {
131
+ // Build a set of relative directory paths from file list
132
+ const dirs = new Map(); // dir -> file count
133
+ for (const file of graph.files) {
134
+ const rel = path.relative(graph.sourceDir, file).replace(/\\/g, "/");
135
+ const parts = rel.split("/");
136
+ // Count files per top-level and second-level directory
137
+ for (let depth = 1; depth <= Math.min(parts.length - 1, 3); depth++) {
138
+ const dirPath = parts.slice(0, depth).join("/");
139
+ dirs.set(dirPath, (dirs.get(dirPath) || 0) + 1);
140
+ }
141
+ }
142
+ // Sort and format as tree
143
+ const sorted = [...dirs.entries()].sort(([a], [b]) => a.localeCompare(b));
144
+ const lines = [];
145
+ for (const [dirPath, count] of sorted) {
146
+ const depth = dirPath.split("/").length - 1;
147
+ const indent = " ".repeat(depth);
148
+ const name = dirPath.split("/").pop();
149
+ lines.push(`${indent}${name}/ (${count} files)`);
150
+ }
151
+ return lines.join("\n") || "(flat structure)";
152
+ }
153
+ function readConfigFiles(projectRoot) {
154
+ const configs = [];
155
+ for (const name of CONFIG_FILE_NAMES) {
156
+ const filePath = path.join(projectRoot, name);
157
+ try {
158
+ if (fs.existsSync(filePath)) {
159
+ const content = fs.readFileSync(filePath, "utf-8");
160
+ const lines = content.split("\n");
161
+ const truncated = lines.length > MAX_CONFIG_LINES
162
+ ? lines.slice(0, MAX_CONFIG_LINES).join("\n") + `\n... (${lines.length - MAX_CONFIG_LINES} more lines)`
163
+ : content;
164
+ configs.push({ path: filePath, name, content: truncated });
165
+ }
166
+ }
167
+ catch {
168
+ // skip unreadable files
169
+ }
170
+ }
171
+ return configs;
172
+ }
173
+ function buildKeySignatures(hubFiles, graph) {
174
+ const parts = [];
175
+ for (const hub of hubFiles) {
176
+ try {
177
+ const content = fs.readFileSync(hub.path, "utf-8");
178
+ const lang = graph.languages[0] || "typescript";
179
+ const summary = (0, context_extractor_1.buildSmartContext)(content, lang, 80);
180
+ parts.push(`### ${hub.relativePath} (${hub.dependentCount} dependents)\n${summary}`);
181
+ }
182
+ catch {
183
+ parts.push(`### ${hub.relativePath} (${hub.dependentCount} dependents)\n(unable to read)`);
184
+ }
185
+ }
186
+ return parts.join("\n\n");
187
+ }
188
+ // ── AI Prompts ──
189
+ function buildScanSystemPrompt() {
190
+ return `You are a senior software architect generating a comprehensive codebase onboarding document.
191
+ Based on the structural analysis data provided (dependency graph metrics, hub file rankings, circular dependencies, file signatures, and config files), generate a document following this exact structure:
192
+
193
+ ## 1. Project Overview
194
+ Brief description of what this project does, its tech stack, and key technologies.
195
+
196
+ ## 2. Architecture Overview
197
+ High-level architecture pattern (monolith, microservices, layered, etc.) and how components connect.
198
+
199
+ ## 3. Repository Structure
200
+ Explain the directory layout and what each major directory contains.
201
+
202
+ ## 4. Module / Component Map
203
+ Map out the key modules, their responsibilities, and how they relate to each other.
204
+
205
+ ## 5. Key Concepts & Domain Model
206
+ Core domain concepts and abstractions that a new developer must understand.
207
+
208
+ ## 6. Development Workflow
209
+ How to build, test, and run the project based on config files found.
210
+
211
+ ## 7. Architectural Decisions
212
+ Notable patterns, frameworks, or design choices evident from the code structure.
213
+
214
+ ## 8. Cross-Cutting Concerns
215
+ Shared utilities, common patterns, error handling, logging approaches found.
216
+
217
+ ## 9. Danger Zones & Common Tasks
218
+ High-risk files (hub files with many dependents), circular dependencies to be aware of, and safe patterns for common modifications.
219
+
220
+ ## 10. Quick Reference
221
+ Entry points, key file paths, and the most important files a new team member should read first.
222
+
223
+ Use the dependency graph data, hub file rankings, circular dependencies, entry points, and file signatures to make your analysis accurate and specific to THIS project.
224
+ Be concise but comprehensive. Output in Markdown. Do not include generic advice — only project-specific insights.`;
225
+ }
226
+ function buildScanUserPrompt(ctx) {
227
+ const sections = [];
228
+ sections.push(`# Project Scan Data`);
229
+ sections.push(`- **Root:** ${ctx.projectRoot}`);
230
+ sections.push(`- **Languages:** ${ctx.languages.join(", ")}`);
231
+ sections.push(`- **Total files in graph:** ${ctx.totalFiles}`);
232
+ sections.push(`\n## Directory Structure\n\`\`\`\n${ctx.directoryStructure}\n\`\`\``);
233
+ sections.push(`\n## Hub Files (Top ${ctx.hubFiles.length} by dependent count)`);
234
+ for (const h of ctx.hubFiles) {
235
+ sections.push(`- **${h.relativePath}** — ${h.dependentCount} dependents`);
236
+ }
237
+ sections.push(`\n## Entry Points (${ctx.entryPoints.length} files with 0 reverse deps)`);
238
+ for (const e of ctx.entryPoints) {
239
+ sections.push(`- ${e}`);
240
+ }
241
+ if (ctx.circularDependencies.length > 0) {
242
+ sections.push(`\n## Circular Dependencies (${ctx.circularDependencies.length} clusters)`);
243
+ for (let i = 0; i < ctx.circularDependencies.length; i++) {
244
+ sections.push(`### Cluster ${i + 1} (${ctx.circularDependencies[i].length} files)`);
245
+ for (const f of ctx.circularDependencies[i]) {
246
+ sections.push(`- ${f}`);
247
+ }
248
+ }
249
+ }
250
+ else {
251
+ sections.push(`\n## Circular Dependencies\nNone detected.`);
252
+ }
253
+ if (ctx.configFiles.length > 0) {
254
+ sections.push(`\n## Config Files`);
255
+ for (const cfg of ctx.configFiles) {
256
+ sections.push(`### ${cfg.name}\n\`\`\`\n${cfg.content}\n\`\`\``);
257
+ }
258
+ }
259
+ if (ctx.keyFileSignatures) {
260
+ sections.push(`\n## Key File Signatures (Top Hub Files)\n${ctx.keyFileSignatures}`);
261
+ }
262
+ return sections.join("\n");
263
+ }
package/dist/index.js CHANGED
@@ -76,20 +76,34 @@ function resolveFilePath(fileArg, projectRoot, sourceDir) {
76
76
  // License state — set at startup
77
77
  let licenseStatus = { plan: "free", source: "default" };
78
78
  // Free tier limits
79
- const FREE_MAX_FILES = 50;
79
+ const FREE_MAX_FILES = 200;
80
80
  function isPro() {
81
81
  if (!(0, validator_1._verifyStatus)(licenseStatus)) {
82
82
  licenseStatus = { plan: "free", source: "default" };
83
83
  return false;
84
84
  }
85
- return licenseStatus.plan === "pro" || licenseStatus.plan === "pro_trial";
85
+ return ["pro", "pro_trial", "cortex"].includes(licenseStatus.plan);
86
+ }
87
+ function isCortex() {
88
+ if (!(0, validator_1._verifyStatus)(licenseStatus)) {
89
+ licenseStatus = { plan: "free", source: "default" };
90
+ return false;
91
+ }
92
+ return licenseStatus.plan === "cortex";
93
+ }
94
+ function getCortexToolError(toolName) {
95
+ if (licenseStatus.error)
96
+ return `${toolName}: ${licenseStatus.error}`;
97
+ if (isPro())
98
+ return `${toolName} requires SYKE Cortex. Upgrade at https://syke.cloud/dashboard/`;
99
+ return `${toolName} requires SYKE Cortex. Sign up at https://syke.cloud`;
86
100
  }
87
101
  function getMaxFiles() {
88
102
  return isPro() ? undefined : FREE_MAX_FILES;
89
103
  }
90
104
  /**
91
105
  * Check if a resolved file path is within the free tier limit.
92
- * Free set = first 50 files sorted alphabetically by relative path.
106
+ * Free set = first 200 files sorted alphabetically by relative path.
93
107
  */
94
108
  function isFileInFreeSet(resolvedPath, graph) {
95
109
  if (isPro())
@@ -98,7 +112,7 @@ function isFileInFreeSet(resolvedPath, graph) {
98
112
  const idx = allFiles.indexOf(resolvedPath);
99
113
  return idx >= 0 && idx < FREE_MAX_FILES;
100
114
  }
101
- const PRO_UPGRADE_MSG = "This file exceeds the Free tier limit (50 files). Upgrade to Pro for unlimited analysis: https://syke.cloud/dashboard/";
115
+ const PRO_UPGRADE_MSG = "This file exceeds the Free tier limit (200 files). Upgrade to Pro for unlimited analysis: https://syke.cloud/dashboard/";
102
116
  function getProToolError(toolName) {
103
117
  if (licenseStatus.error) {
104
118
  return `${toolName}: ${licenseStatus.error}`;
@@ -133,7 +147,7 @@ async function main() {
133
147
  };
134
148
  process.on("SIGINT", shutdown);
135
149
  process.on("SIGTERM", shutdown);
136
- const server = new index_js_1.Server({ name: "syke", version: "1.7.1" }, { capabilities: { tools: {} } });
150
+ const server = new index_js_1.Server({ name: "syke", version: "1.8.1" }, { capabilities: { tools: {} } });
137
151
  // List tools
138
152
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
139
153
  tools: [
@@ -213,7 +227,7 @@ async function main() {
213
227
  },
214
228
  {
215
229
  name: "ai_analyze",
216
- description: "[PRO] Use AI (Gemini/OpenAI/Claude) to perform semantic analysis on a file. Reads the file's source code and its dependents to explain what might break when modified and how to safely make changes.",
230
+ description: "[CORTEX] Use AI (Gemini/OpenAI/Claude) to perform semantic analysis on a file. Reads the file's source code and its dependents to explain what might break when modified and how to safely make changes.",
217
231
  inputSchema: {
218
232
  type: "object",
219
233
  properties: {
@@ -238,6 +252,16 @@ async function main() {
238
252
  },
239
253
  },
240
254
  },
255
+ {
256
+ name: "scan_project",
257
+ description: "[CORTEX] Scan the entire project and generate a comprehensive onboarding document. "
258
+ + "Analyzes architecture, key files, dependencies, and patterns to help new team members "
259
+ + "understand the codebase quickly. Requires AI API key (BYOK).",
260
+ inputSchema: {
261
+ type: "object",
262
+ properties: {},
263
+ },
264
+ },
241
265
  ],
242
266
  }));
243
267
  // Dashboard URL footer — shown only on the first successful tool call
@@ -491,11 +515,16 @@ async function main() {
491
515
  };
492
516
  }
493
517
  case "ai_analyze": {
494
- // BYOK: allow if user has their own AI key, even on Free plan
518
+ // Cortex-only tool
519
+ if (!isCortex()) {
520
+ return {
521
+ content: [{ type: "text", text: getCortexToolError("ai_analyze") }],
522
+ };
523
+ }
495
524
  const hasAIKey = !!(0, provider_1.getAIProvider)();
496
- if (!isPro() && !hasAIKey) {
525
+ if (!hasAIKey) {
497
526
  return {
498
- content: [{ type: "text", text: getProToolError("ai_analyze") + "\n\nOr set GEMINI_KEY / OPENAI_KEY / ANTHROPIC_KEY to use ai_analyze with your own API key." }],
527
+ content: [{ type: "text", text: `ai_analyze requires an AI API key.\n\nSet one of: GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY in your environment.\nSee: https://syke.cloud/docs/ai-analyze` }],
499
528
  };
500
529
  }
501
530
  const file = args.file;
@@ -511,24 +540,11 @@ async function main() {
511
540
  ],
512
541
  };
513
542
  }
514
- if (!isFileInFreeSet(resolved, graph)) {
515
- return { content: [{ type: "text", text: PRO_UPGRADE_MSG }] };
516
- }
517
- // BYOK only: user must have their own AI key configured
518
- if (!hasAIKey) {
519
- return {
520
- content: [{ type: "text", text: `ai_analyze requires an AI API key.\n\nSet one of: GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY in your environment.\nSee: https://syke.cloud/docs/ai-analyze` }],
521
- };
522
- }
523
543
  const { analyzeImpact: localAnalyzeImpact } = await Promise.resolve().then(() => __importStar(require("./tools/analyze-impact")));
524
544
  const impactResult = await localAnalyzeImpact(resolved, graph);
525
545
  const aiResult = await (0, analyzer_1.analyzeWithAI)(resolved, impactResult, graph);
526
- let resultText = aiResult;
527
- if (!isPro() && graph.files.size > FREE_MAX_FILES) {
528
- resultText += `\n\n---\nFree tier: analyzing ${FREE_MAX_FILES}/${graph.files.size} files. Some dependencies may be missing. Upgrade to Pro for full analysis: https://syke.cloud/dashboard/`;
529
- }
530
546
  return {
531
- content: [{ type: "text", text: appendDashboardFooter(resultText) }],
547
+ content: [{ type: "text", text: appendDashboardFooter(aiResult) }],
532
548
  };
533
549
  }
534
550
  case "check_warnings": {
@@ -582,6 +598,26 @@ async function main() {
582
598
  content: [{ type: "text", text: appendDashboardFooter(lines.join("\n")) }],
583
599
  };
584
600
  }
601
+ case "scan_project": {
602
+ // Cortex-only tool
603
+ if (!isCortex()) {
604
+ return {
605
+ content: [{ type: "text", text: getCortexToolError("scan_project") }],
606
+ };
607
+ }
608
+ const hasAIKey = !!(0, provider_1.getAIProvider)();
609
+ if (!hasAIKey) {
610
+ return {
611
+ content: [{ type: "text", text: `scan_project requires an AI API key.\n\nSet one of: GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY in your environment.\nSee: https://syke.cloud/docs/ai-analyze` }],
612
+ };
613
+ }
614
+ const graph = (0, graph_1.getGraph)(currentProjectRoot, currentPackageName, getMaxFiles());
615
+ const { scanProject } = await Promise.resolve().then(() => __importStar(require("./ai/project-scanner")));
616
+ const result = await scanProject(graph);
617
+ return {
618
+ content: [{ type: "text", text: appendDashboardFooter(result) }],
619
+ };
620
+ }
585
621
  default:
586
622
  return {
587
623
  content: [
@@ -592,7 +628,7 @@ async function main() {
592
628
  }
593
629
  });
594
630
  // Pre-warm the graph (skip if no project root — e.g. Smithery scan)
595
- console.error(`[syke] Starting SYKE MCP Server v1.7.0`);
631
+ console.error(`[syke] Starting SYKE MCP Server v1.8.1`);
596
632
  console.error(`[syke] License: ${licenseStatus.plan.toUpperCase()} (${licenseStatus.source})`);
597
633
  if (licenseStatus.expiresAt) {
598
634
  console.error(`[syke] Expires: ${licenseStatus.expiresAt}`);
@@ -762,7 +798,7 @@ main().catch((err) => {
762
798
  * See: https://smithery.ai/docs/deploy#sandbox-server
763
799
  */
764
800
  function createSandboxServer() {
765
- const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.7.1" }, { capabilities: { tools: {} } });
801
+ const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.8.1" }, { capabilities: { tools: {} } });
766
802
  sandboxServer.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
767
803
  tools: [
768
804
  {
@@ -797,7 +833,7 @@ function createSandboxServer() {
797
833
  },
798
834
  {
799
835
  name: "ai_analyze",
800
- description: "Pro: AI semantic analysis (Gemini/OpenAI/Claude) of a file and its dependents.",
836
+ description: "Cortex: AI semantic analysis (Gemini/OpenAI/Claude) of a file and its dependents.",
801
837
  inputSchema: { type: "object", properties: { file: { type: "string", description: "File to analyze" } }, required: ["file"] },
802
838
  },
803
839
  {
@@ -805,6 +841,11 @@ function createSandboxServer() {
805
841
  description: "Pro: Real-time monitoring alerts for file changes.",
806
842
  inputSchema: { type: "object", properties: {} },
807
843
  },
844
+ {
845
+ name: "scan_project",
846
+ description: "Cortex: Scan entire project and generate onboarding document.",
847
+ inputSchema: { type: "object", properties: {} },
848
+ },
808
849
  ],
809
850
  }));
810
851
  sandboxServer.setRequestHandler(types_js_1.CallToolRequestSchema, async () => ({
@@ -1,5 +1,5 @@
1
1
  export interface LicenseStatus {
2
- plan: "free" | "pro" | "pro_trial";
2
+ plan: "free" | "pro" | "pro_trial" | "cortex";
3
3
  email?: string;
4
4
  expiresAt?: string;
5
5
  source: "online" | "cache" | "default";
@@ -245,6 +245,17 @@ function clearLicenseCache() {
245
245
  // silently fail
246
246
  }
247
247
  }
248
+ /**
249
+ * Map server plan string to client plan type.
250
+ * Cortex plans → "cortex", trial → "pro_trial", everything else paid → "pro".
251
+ */
252
+ function mapPlan(serverPlan) {
253
+ if (serverPlan === "pro_trial")
254
+ return "pro_trial";
255
+ if (serverPlan === "cortex_monthly" || serverPlan === "cortex_annual" || serverPlan === "cortex")
256
+ return "cortex";
257
+ return "pro"; // pro_monthly, pro_annual, pro_founding, master, etc.
258
+ }
248
259
  /**
249
260
  * Main license validation — called on MCP server startup
250
261
  */
@@ -260,7 +271,7 @@ async function checkLicense() {
260
271
  if (cache && cache.valid && (now - cache.cachedAt) < CACHE_MAX_AGE) {
261
272
  // Cache is fresh — still start heartbeat with cached session
262
273
  startHeartbeat(key, getDeviceFingerprint());
263
- const cachedPlan = cache.plan === "pro_trial" ? "pro_trial" : "pro";
274
+ const cachedPlan = mapPlan(cache.plan || "");
264
275
  return {
265
276
  plan: cachedPlan,
266
277
  email: cache.email,
@@ -285,12 +296,13 @@ async function checkLicense() {
285
296
  cachedAt: now,
286
297
  });
287
298
  startHeartbeat(realKey, getDeviceFingerprint());
299
+ const foundingPlan = mapPlan(result.plan || "");
288
300
  return {
289
- plan: "pro",
301
+ plan: foundingPlan,
290
302
  email: result.email,
291
303
  expiresAt: result.expiresAt,
292
304
  source: "online",
293
- _t: _signStatus("pro", "online"),
305
+ _t: _signStatus(foundingPlan, "online"),
294
306
  };
295
307
  }
296
308
  if (!result.valid) {
@@ -308,7 +320,7 @@ async function checkLicense() {
308
320
  });
309
321
  // Start heartbeat
310
322
  startHeartbeat(key, getDeviceFingerprint());
311
- const onlinePlan = result.plan === "pro_trial" ? "pro_trial" : "pro";
323
+ const onlinePlan = mapPlan(result.plan || "");
312
324
  return {
313
325
  plan: onlinePlan,
314
326
  email: result.email,
@@ -329,7 +341,7 @@ async function checkLicense() {
329
341
  // Online validation failed — check if we have a grace-period cache
330
342
  if (cache && cache.valid && (now - cache.cachedAt) < CACHE_GRACE_PERIOD) {
331
343
  startHeartbeat(key, getDeviceFingerprint());
332
- const gracePlan = cache.plan === "pro_trial" ? "pro_trial" : "pro";
344
+ const gracePlan = mapPlan(cache.plan || "");
333
345
  return {
334
346
  plan: gracePlan,
335
347
  email: cache.email,
@@ -408,7 +408,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
408
408
  app.get("/api/graph", (_req, res) => {
409
409
  const graph = getGraphFn();
410
410
  const isPro = isProPlan();
411
- const FREE_GRAPH_LIMIT = 50;
411
+ const FREE_GRAPH_LIMIT = 200;
412
412
  const nodes = [];
413
413
  const edges = [];
414
414
  // ── Compute depth for each file (BFS from roots) ──
@@ -429,7 +429,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
429
429
  queue.push([dep, d + 1]);
430
430
  }
431
431
  }
432
- // Free tier: limit to first 50 files sorted alphabetically
432
+ // Free tier: limit to first 200 files sorted alphabetically
433
433
  const allFiles = [...graph.files].sort();
434
434
  const visibleFiles = isPro ? allFiles : allFiles.slice(0, FREE_GRAPH_LIMIT);
435
435
  const visibleSet = new Set(visibleFiles);
@@ -521,14 +521,14 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
521
521
  if (!isPro) {
522
522
  const allFiles = [...graph.files].sort();
523
523
  const idx = allFiles.indexOf(resolved);
524
- if (idx < 0 || idx >= 50) {
525
- return res.status(403).json({ error: "This file exceeds the Free tier limit (50 files). Upgrade to Pro for unlimited analysis.", upgrade: "https://syke.cloud/dashboard/" });
524
+ if (idx < 0 || idx >= 200) {
525
+ return res.status(403).json({ error: "This file exceeds the Free tier limit (200 files). Upgrade to Pro for unlimited analysis.", upgrade: "https://syke.cloud/dashboard/" });
526
526
  }
527
527
  }
528
528
  const impactResult = await (0, analyze_impact_1.analyzeImpact)(resolved, graph);
529
529
  try {
530
530
  const aiResult = await (0, analyzer_1.analyzeWithAI)(resolved, impactResult, graph);
531
- const partial = !isPro && graph.files.size > 50;
531
+ const partial = !isPro && graph.files.size > 200;
532
532
  res.json({ file: impactResult.relativePath, analysis: aiResult, partial });
533
533
  }
534
534
  catch (err) {
@@ -792,7 +792,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
792
792
  planSource: license?.source || "default",
793
793
  expiresAt: license?.expiresAt || null,
794
794
  licenseKey: maskedKey,
795
- freeFileLimit: 50,
795
+ freeFileLimit: 200,
796
796
  sykeVersion,
797
797
  aiProvider: aiInfo.activeProvider,
798
798
  aiKeys: aiInfo.configured,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syke1/mcp-server",
3
- "version": "1.7.1",
3
+ "version": "1.8.1",
4
4
  "mcpName": "io.github.khalomsky/syke",
5
5
  "description": "AI code impact analysis MCP server — dependency graphs, cascade detection, and a mandatory build gate for AI coding agents",
6
6
  "main": "dist/index.js",