@syke1/mcp-server 1.0.0 → 1.1.0

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/dist/index.js CHANGED
@@ -68,7 +68,6 @@ function resolveFilePath(fileArg, projectRoot, sourceDir) {
68
68
  let licenseStatus = { plan: "free", source: "default" };
69
69
  // Free tier limits
70
70
  const FREE_MAX_FILES = 50;
71
- const FREE_MAX_HUB_FILES = 3;
72
71
  /**
73
72
  * Check if a resolved file path is within the free tier limit.
74
73
  * Free set = first 50 files sorted alphabetically by relative path.
@@ -325,11 +324,15 @@ async function main() {
325
324
  return { content: [{ type: "text", text: lines.join("\n") }] };
326
325
  }
327
326
  case "get_hub_files": {
327
+ // Pro-only feature
328
+ if (licenseStatus.plan !== "pro") {
329
+ return {
330
+ content: [{ type: "text", text: "get_hub_files requires SYKE Pro. Upgrade at https://syke.cloud/dashboard/\n\nSet SYKE_LICENSE_KEY in your MCP config to activate Pro features." }],
331
+ };
332
+ }
328
333
  const requestedN = args.top_n || 10;
329
334
  const graph = (0, graph_1.getGraph)(currentProjectRoot, currentPackageName);
330
- // Free: limited to top 3, Pro: unlimited
331
- const maxN = licenseStatus.plan === "pro" ? requestedN : Math.min(requestedN, FREE_MAX_HUB_FILES);
332
- const hubs = (0, analyze_impact_1.getHubFiles)(graph, maxN);
335
+ const hubs = (0, analyze_impact_1.getHubFiles)(graph, requestedN);
333
336
  const lines = [
334
337
  `## Hub Files (Top ${hubs.length})`,
335
338
  "",
@@ -341,10 +344,6 @@ async function main() {
341
344
  });
342
345
  lines.push("");
343
346
  lines.push(`Total files in graph: ${graph.files.size}`);
344
- if (licenseStatus.plan !== "pro" && requestedN > FREE_MAX_HUB_FILES) {
345
- lines.push("");
346
- lines.push(`Free tier shows top ${FREE_MAX_HUB_FILES} hub files. Upgrade to Pro for full ranking: https://syke.cloud/dashboard/`);
347
- }
348
347
  return { content: [{ type: "text", text: lines.join("\n") }] };
349
348
  }
350
349
  case "refresh_graph": {
@@ -390,6 +389,12 @@ async function main() {
390
389
  };
391
390
  }
392
391
  case "check_warnings": {
392
+ // Pro-only feature (real-time monitoring)
393
+ if (licenseStatus.plan !== "pro") {
394
+ return {
395
+ content: [{ type: "text", text: "check_warnings requires SYKE Pro (real-time monitoring feature). Upgrade at https://syke.cloud/dashboard/\n\nSet SYKE_LICENSE_KEY in your MCP config to activate Pro features." }],
396
+ };
397
+ }
393
398
  const shouldAck = args.acknowledge || false;
394
399
  const warnings = (0, server_1.getUnacknowledgedWarnings)();
395
400
  if (warnings.length === 0) {
@@ -451,7 +456,7 @@ async function main() {
451
456
  console.error(`[syke] Pro activated for: ${licenseStatus.email || "unknown"}`);
452
457
  }
453
458
  else {
454
- console.error(`[syke] Free tier: ${FREE_MAX_FILES} file limit, ai_analyze disabled`);
459
+ console.error(`[syke] Free tier: ${FREE_MAX_FILES} file limit, ai_analyze/get_hub_files/check_warnings disabled`);
455
460
  console.error(`[syke] Upgrade at https://syke.cloud/dashboard/`);
456
461
  }
457
462
  console.error(`[syke] Project root: ${currentProjectRoot}`);
@@ -246,6 +246,12 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
246
246
  }
247
247
  }
248
248
  app.get("/api/events", (_req, res) => {
249
+ // Pro-only: real-time monitoring via SSE
250
+ const license = getLicenseStatus?.();
251
+ if (!license || license.plan !== "pro") {
252
+ res.status(403).json({ error: "Real-time monitoring requires SYKE Pro.", upgrade: "https://syke.cloud/dashboard/" });
253
+ return;
254
+ }
249
255
  res.writeHead(200, {
250
256
  "Content-Type": "text/event-stream",
251
257
  "Cache-Control": "no-cache",
@@ -335,6 +341,9 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
335
341
  // GET /api/graph — Cytoscape.js compatible JSON
336
342
  app.get("/api/graph", (_req, res) => {
337
343
  const graph = getGraphFn();
344
+ const license = getLicenseStatus?.();
345
+ const isPro = license?.plan === "pro";
346
+ const FREE_GRAPH_LIMIT = 50;
338
347
  const nodes = [];
339
348
  const edges = [];
340
349
  // ── Compute depth for each file (BFS from roots) ──
@@ -355,7 +364,11 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
355
364
  queue.push([dep, d + 1]);
356
365
  }
357
366
  }
358
- for (const file of graph.files) {
367
+ // Free tier: limit to first 50 files sorted alphabetically
368
+ const allFiles = [...graph.files].sort();
369
+ const visibleFiles = isPro ? allFiles : allFiles.slice(0, FREE_GRAPH_LIMIT);
370
+ const visibleSet = new Set(visibleFiles);
371
+ for (const file of visibleFiles) {
359
372
  const rel = path.relative(graph.sourceDir, file).replace(/\\/g, "/");
360
373
  const revDeps = graph.reverse.get(file) || [];
361
374
  const dependentCount = revDeps.length;
@@ -392,13 +405,18 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
392
405
  });
393
406
  }
394
407
  for (const [file, deps] of graph.forward) {
408
+ // Only include edges where both source and target are in the visible set
409
+ if (!visibleSet.has(file))
410
+ continue;
395
411
  const from = path.relative(graph.sourceDir, file).replace(/\\/g, "/");
396
412
  for (const d of deps) {
413
+ if (!visibleSet.has(d))
414
+ continue;
397
415
  const to = path.relative(graph.sourceDir, d).replace(/\\/g, "/");
398
416
  edges.push({ data: { source: from, target: to } });
399
417
  }
400
418
  }
401
- res.json({ nodes, edges });
419
+ res.json({ nodes, edges, totalFiles: graph.files.size, limited: !isPro && graph.files.size > FREE_GRAPH_LIMIT });
402
420
  });
403
421
  // GET /api/impact/:file — Impact analysis for a specific file
404
422
  app.get("/api/impact/*splat", (req, res) => {
@@ -443,15 +461,16 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
443
461
  res.status(500).json({ error: err.message || "AI analysis failed" });
444
462
  }
445
463
  });
446
- // GET /api/hub-files — Top hub files ranking (Free: top 3, Pro: unlimited)
464
+ // GET /api/hub-files — Top hub files ranking (Pro only)
447
465
  app.get("/api/hub-files", (req, res) => {
448
- const requested = parseInt(req.query.top) || 10;
449
466
  const license = getLicenseStatus?.();
450
- const isPro = license?.plan === "pro";
451
- const top = isPro ? requested : Math.min(requested, 3);
467
+ if (!license || license.plan !== "pro") {
468
+ return res.status(403).json({ error: "Hub files ranking requires SYKE Pro.", upgrade: "https://syke.cloud/dashboard/" });
469
+ }
470
+ const requested = parseInt(req.query.top) || 10;
452
471
  const graph = getGraphFn();
453
- const hubs = (0, analyze_impact_1.getHubFiles)(graph, top);
454
- res.json({ hubs, totalFiles: graph.files.size, limited: !isPro, plan: license?.plan || "free" });
472
+ const hubs = (0, analyze_impact_1.getHubFiles)(graph, requested);
473
+ res.json({ hubs, totalFiles: graph.files.size, limited: false, plan: "pro" });
455
474
  });
456
475
  // POST /api/connected-code — Batch load code from file + connected nodes
457
476
  app.post("/api/connected-code", (req, res) => {
@@ -517,8 +536,12 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
517
536
  res.status(500).json({ error: err.message });
518
537
  }
519
538
  });
520
- // GET /api/cycles — Detect circular dependencies
539
+ // GET /api/cycles — Detect circular dependencies (Pro only)
521
540
  app.get("/api/cycles", (_req, res) => {
541
+ const license = getLicenseStatus?.();
542
+ if (!license || license.plan !== "pro") {
543
+ return res.status(403).json({ error: "This feature requires SYKE Pro.", upgrade: "https://syke.cloud/dashboard/" });
544
+ }
522
545
  const graph = getGraphFn();
523
546
  const toRel = (f) => path.relative(graph.sourceDir, f).replace(/\\/g, "/");
524
547
  const cycles = [];
@@ -601,8 +624,12 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
601
624
  }
602
625
  res.json({ path: pathResult, distance: pathResult.length - 1 });
603
626
  });
604
- // GET /api/simulate-delete/:file — Simulate file removal
627
+ // GET /api/simulate-delete/:file — Simulate file removal (Pro only)
605
628
  app.get("/api/simulate-delete/*splat", (req, res) => {
629
+ const license = getLicenseStatus?.();
630
+ if (!license || license.plan !== "pro") {
631
+ return res.status(403).json({ error: "This feature requires SYKE Pro.", upgrade: "https://syke.cloud/dashboard/" });
632
+ }
606
633
  const splat = req.params.splat;
607
634
  const fileParam = Array.isArray(splat) ? splat.join("/") : splat;
608
635
  if (!fileParam)
@@ -718,13 +745,24 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
718
745
  res.status(500).json({ error: err.message });
719
746
  }
720
747
  });
721
- // POST /api/switch-project — Switch to a different project folder
748
+ // POST /api/switch-project — Switch to a different project folder (Pro only)
722
749
  app.post("/api/switch-project", (req, res) => {
723
750
  const { projectRoot } = req.body;
724
751
  if (!projectRoot || typeof projectRoot !== "string") {
725
752
  return res.status(400).json({ error: "projectRoot is required" });
726
753
  }
727
754
  const normalized = path.normalize(projectRoot);
755
+ // Free tier: only 1 project allowed — block switch to a different project
756
+ const license = getLicenseStatus?.();
757
+ if (!license || license.plan !== "pro") {
758
+ const currentRoot = getProjectRoot ? path.normalize(getProjectRoot()) : null;
759
+ if (currentRoot && normalized !== currentRoot) {
760
+ return res.status(403).json({
761
+ error: "Multiple projects require SYKE Pro. Free tier supports 1 project.",
762
+ upgrade: "https://syke.cloud/dashboard/",
763
+ });
764
+ }
765
+ }
728
766
  if (!fs.existsSync(normalized) || !fs.statSync(normalized).isDirectory()) {
729
767
  return res.status(400).json({ error: `Directory not found: ${normalized}` });
730
768
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syke1/mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "AI code impact analysis MCP server — dependency graphs, cascade detection, and a mandatory build gate for AI coding agents",
5
5
  "main": "dist/index.js",
6
6
  "bin": {