@syke1/mcp-server 1.3.1 → 1.3.3

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
@@ -86,6 +86,15 @@ function isFileInFreeSet(resolvedPath, graph) {
86
86
  return idx >= 0 && idx < FREE_MAX_FILES;
87
87
  }
88
88
  const PRO_UPGRADE_MSG = "This file exceeds the Free tier limit (50 files). Upgrade to Pro for unlimited analysis: https://syke.cloud/dashboard/";
89
+ function getProToolError(toolName) {
90
+ if (licenseStatus.error) {
91
+ return `${toolName}: ${licenseStatus.error}`;
92
+ }
93
+ if (licenseStatus.expiresAt) {
94
+ return `${toolName}: Trial expired. Upgrade at https://syke.cloud/dashboard/`;
95
+ }
96
+ return `${toolName} requires SYKE Pro. Set SYKE_LICENSE_KEY in your MCP config or sign up at https://syke.cloud`;
97
+ }
89
98
  async function main() {
90
99
  // Check license before starting (graceful fallback for hosted environments like Smithery)
91
100
  try {
@@ -111,7 +120,7 @@ async function main() {
111
120
  };
112
121
  process.on("SIGINT", shutdown);
113
122
  process.on("SIGTERM", shutdown);
114
- const server = new index_js_1.Server({ name: "syke", version: "1.3.0" }, { capabilities: { tools: {} } });
123
+ const server = new index_js_1.Server({ name: "syke", version: "1.3.3" }, { capabilities: { tools: {} } });
115
124
  // List tools
116
125
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
117
126
  tools: [
@@ -218,6 +227,16 @@ async function main() {
218
227
  },
219
228
  ],
220
229
  }));
230
+ // Dashboard URL footer — shown only on the first successful tool call
231
+ let firstToolCall = true;
232
+ const DASHBOARD_FOOTER = `\n\n---\nšŸ“Š SYKE Dashboard: http://localhost:${WEB_PORT}`;
233
+ function appendDashboardFooter(text) {
234
+ if (firstToolCall && currentProjectRoot) {
235
+ firstToolCall = false;
236
+ return text + DASHBOARD_FOOTER;
237
+ }
238
+ return text;
239
+ }
221
240
  // Handle tool calls
222
241
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
223
242
  const { name, arguments: args } = request.params;
@@ -228,7 +247,7 @@ async function main() {
228
247
  const result = (0, gate_build_1.gateCheck)(graph, files);
229
248
  return {
230
249
  content: [
231
- { type: "text", text: (0, gate_build_1.formatGateResult)(result) },
250
+ { type: "text", text: appendDashboardFooter((0, gate_build_1.formatGateResult)(result)) },
232
251
  ],
233
252
  isError: result.verdict === "FAIL",
234
253
  };
@@ -270,7 +289,7 @@ async function main() {
270
289
  lines.push(`- ${d}`);
271
290
  }
272
291
  }
273
- return { content: [{ type: "text", text: lines.join("\n") }] };
292
+ return { content: [{ type: "text", text: appendDashboardFooter(lines.join("\n")) }] };
274
293
  }
275
294
  case "check_safe": {
276
295
  const file = args.file;
@@ -295,7 +314,7 @@ async function main() {
295
314
  content: [
296
315
  {
297
316
  type: "text",
298
- text: `${result.riskLevel} — ${rel} impacts ${result.totalImpacted} file(s)`,
317
+ text: appendDashboardFooter(`${result.riskLevel} — ${rel} impacts ${result.totalImpacted} file(s)`),
299
318
  },
300
319
  ],
301
320
  };
@@ -339,7 +358,7 @@ async function main() {
339
358
  // Pro-only feature
340
359
  if (licenseStatus.plan !== "pro") {
341
360
  return {
342
- 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." }],
361
+ content: [{ type: "text", text: getProToolError("get_hub_files") }],
343
362
  };
344
363
  }
345
364
  const requestedN = args.top_n || 10;
@@ -373,12 +392,7 @@ async function main() {
373
392
  // Pro-only feature
374
393
  if (licenseStatus.plan !== "pro") {
375
394
  return {
376
- content: [
377
- {
378
- type: "text",
379
- text: "ai_analyze requires SYKE Pro. Upgrade at https://syke.cloud/dashboard/\n\nSet SYKE_LICENSE_KEY in your MCP config to activate Pro features.",
380
- },
381
- ],
395
+ content: [{ type: "text", text: getProToolError("ai_analyze") }],
382
396
  };
383
397
  }
384
398
  const file = args.file;
@@ -397,14 +411,14 @@ async function main() {
397
411
  const impactResult = (0, analyze_impact_1.analyzeImpact)(resolved, graph);
398
412
  const aiResult = await (0, analyzer_1.analyzeWithAI)(resolved, impactResult, graph);
399
413
  return {
400
- content: [{ type: "text", text: aiResult }],
414
+ content: [{ type: "text", text: appendDashboardFooter(aiResult) }],
401
415
  };
402
416
  }
403
417
  case "check_warnings": {
404
418
  // Pro-only feature (real-time monitoring)
405
419
  if (licenseStatus.plan !== "pro") {
406
420
  return {
407
- 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." }],
421
+ content: [{ type: "text", text: getProToolError("check_warnings") }],
408
422
  };
409
423
  }
410
424
  const shouldAck = args.acknowledge || false;
@@ -448,7 +462,7 @@ async function main() {
448
462
  lines.push(`${count} warning(s) acknowledged.`);
449
463
  }
450
464
  return {
451
- content: [{ type: "text", text: lines.join("\n") }],
465
+ content: [{ type: "text", text: appendDashboardFooter(lines.join("\n")) }],
452
466
  };
453
467
  }
454
468
  default:
@@ -461,8 +475,11 @@ async function main() {
461
475
  }
462
476
  });
463
477
  // Pre-warm the graph (skip if no project root — e.g. Smithery scan)
464
- console.error(`[syke] Starting SYKE MCP Server v1.3.0`);
478
+ console.error(`[syke] Starting SYKE MCP Server v1.3.3`);
465
479
  console.error(`[syke] License: ${licenseStatus.plan.toUpperCase()} (${licenseStatus.source})`);
480
+ if (licenseStatus.expiresAt) {
481
+ console.error(`[syke] Expires: ${licenseStatus.expiresAt}`);
482
+ }
466
483
  // Log AI provider status
467
484
  const aiProvider = (0, provider_1.getAIProvider)();
468
485
  if (aiProvider) {
@@ -553,7 +570,7 @@ main().catch((err) => {
553
570
  * See: https://smithery.ai/docs/deploy#sandbox-server
554
571
  */
555
572
  function createSandboxServer() {
556
- const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.3.0" }, { capabilities: { tools: {} } });
573
+ const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.3.3" }, { capabilities: { tools: {} } });
557
574
  sandboxServer.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
558
575
  tools: [
559
576
  {
@@ -255,6 +255,10 @@ async function checkLicense() {
255
255
  }
256
256
  // Try online validation (with device binding)
257
257
  const result = await validateOnline(key);
258
+ if (!result.valid) {
259
+ const reason = result.error || "invalid key or expired";
260
+ console.error(`[syke] License validation failed: ${reason}`);
261
+ }
258
262
  if (result.valid) {
259
263
  // Update cache
260
264
  writeCache({
@@ -51,6 +51,27 @@ document.addEventListener("DOMContentLoaded", async () => {
51
51
  initSSE();
52
52
  });
53
53
 
54
+ // ═══════════════════════════════════════════
55
+ // WELCOME OVERLAY
56
+ // ═══════════════════════════════════════════
57
+ function showWelcomeOverlay() {
58
+ const overlay = document.getElementById("welcome-overlay");
59
+ if (overlay) overlay.classList.remove("hidden");
60
+
61
+ const btn = document.getElementById("btn-welcome-open");
62
+ if (btn && !btn._bound) {
63
+ btn.addEventListener("click", () => {
64
+ document.getElementById("btn-change-project").click();
65
+ });
66
+ btn._bound = true;
67
+ }
68
+ }
69
+
70
+ function hideWelcomeOverlay() {
71
+ const overlay = document.getElementById("welcome-overlay");
72
+ if (overlay) overlay.classList.add("hidden");
73
+ }
74
+
54
75
  // ═══════════════════════════════════════════
55
76
  // GRAPH LOADING
56
77
  // ═══════════════════════════════════════════
@@ -59,6 +80,12 @@ async function loadGraph() {
59
80
  const raw = await res.json();
60
81
  console.log("[SYKE]", raw.nodes.length, "nodes", raw.edges.length, "edges");
61
82
 
83
+ if (raw.nodes.length === 0) {
84
+ showWelcomeOverlay();
85
+ return;
86
+ }
87
+ hideWelcomeOverlay();
88
+
62
89
  const nodes = raw.nodes.map(n => {
63
90
  const layer = n.data.layer || "UTIL";
64
91
  const c = LAYER_CENTERS[layer] || LAYER_CENTERS.UTIL;
@@ -56,6 +56,34 @@
56
56
  <input type="text" id="search-input" placeholder="SEARCH TARGET...">
57
57
  </div>
58
58
  <div id="3d-graph"></div>
59
+ <!-- Welcome Overlay (shown when no project loaded) -->
60
+ <div id="welcome-overlay" class="welcome-overlay hidden">
61
+ <div class="welcome-content">
62
+ <div class="welcome-logo">
63
+ <div class="welcome-pulse-ring"><span class="welcome-pulse-dot"></span></div>
64
+ <span class="welcome-title">SYKE</span>
65
+ </div>
66
+ <p class="welcome-subtitle">CODE IMPACT RADAR</p>
67
+ <p class="welcome-msg">Open a project to get started</p>
68
+ <button id="btn-welcome-open" class="welcome-open-btn">OPEN PROJECT</button>
69
+ <div class="welcome-steps">
70
+ <div class="welcome-step">
71
+ <span class="step-num">1</span>
72
+ <span class="step-text">Open Project</span>
73
+ </div>
74
+ <div class="welcome-step-arrow">&rarr;</div>
75
+ <div class="welcome-step">
76
+ <span class="step-num">2</span>
77
+ <span class="step-text">Explore Graph</span>
78
+ </div>
79
+ <div class="welcome-step-arrow">&rarr;</div>
80
+ <div class="welcome-step">
81
+ <span class="step-num">3</span>
82
+ <span class="step-text">Monitor Changes</span>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
59
87
  <!-- HTML overlay for node labels -->
60
88
  <div id="node-labels"></div>
61
89
  <!-- Layer Legend (clickable filter) -->
@@ -1825,3 +1825,150 @@ main {
1825
1825
  font-size: 11px;
1826
1826
  letter-spacing: 2px;
1827
1827
  }
1828
+
1829
+ /* ═══════════════════════════════════════════ */
1830
+ /* Welcome Overlay */
1831
+ /* ═══════════════════════════════════════════ */
1832
+ .welcome-overlay {
1833
+ position: absolute;
1834
+ top: 0; left: 0; right: 0; bottom: 0;
1835
+ display: flex;
1836
+ align-items: center;
1837
+ justify-content: center;
1838
+ z-index: 50;
1839
+ background: radial-gradient(ellipse at center, rgba(13,26,48,0.95) 0%, rgba(5,10,24,0.98) 70%);
1840
+ backdrop-filter: blur(2px);
1841
+ }
1842
+
1843
+ .welcome-overlay.hidden { display: none; }
1844
+
1845
+ .welcome-content {
1846
+ display: flex;
1847
+ flex-direction: column;
1848
+ align-items: center;
1849
+ gap: 16px;
1850
+ animation: welcome-fade-in 0.8s ease-out;
1851
+ }
1852
+
1853
+ @keyframes welcome-fade-in {
1854
+ 0% { opacity: 0; transform: translateY(20px); }
1855
+ 100% { opacity: 1; transform: translateY(0); }
1856
+ }
1857
+
1858
+ .welcome-logo {
1859
+ display: flex;
1860
+ align-items: center;
1861
+ gap: 16px;
1862
+ }
1863
+
1864
+ .welcome-pulse-ring {
1865
+ position: relative;
1866
+ width: 28px;
1867
+ height: 28px;
1868
+ display: flex;
1869
+ align-items: center;
1870
+ justify-content: center;
1871
+ }
1872
+
1873
+ .welcome-pulse-ring::before {
1874
+ content: '';
1875
+ position: absolute;
1876
+ width: 28px; height: 28px;
1877
+ border-radius: 50%;
1878
+ border: 1px solid var(--risk-high);
1879
+ animation: pulse-ring 2s ease-out infinite;
1880
+ }
1881
+
1882
+ .welcome-pulse-dot {
1883
+ width: 10px;
1884
+ height: 10px;
1885
+ border-radius: 50%;
1886
+ background: var(--risk-high);
1887
+ box-shadow: 0 0 12px var(--risk-high);
1888
+ animation: pulse-glow 2s ease-in-out infinite;
1889
+ }
1890
+
1891
+ .welcome-title {
1892
+ font-size: 42px;
1893
+ font-weight: 700;
1894
+ letter-spacing: 12px;
1895
+ color: var(--accent);
1896
+ text-shadow: var(--glow-cyan), 0 0 40px rgba(0,212,255,0.2);
1897
+ }
1898
+
1899
+ .welcome-subtitle {
1900
+ font-size: 11px;
1901
+ color: var(--text-secondary);
1902
+ letter-spacing: 6px;
1903
+ margin-top: -8px;
1904
+ }
1905
+
1906
+ .welcome-msg {
1907
+ font-size: 14px;
1908
+ color: var(--text-primary);
1909
+ letter-spacing: 1px;
1910
+ margin-top: 8px;
1911
+ }
1912
+
1913
+ .welcome-open-btn {
1914
+ padding: 12px 40px;
1915
+ background: rgba(0,212,255,0.1);
1916
+ color: var(--accent);
1917
+ border: 1px solid var(--accent);
1918
+ border-radius: 3px;
1919
+ cursor: pointer;
1920
+ font-size: 13px;
1921
+ font-family: inherit;
1922
+ font-weight: 700;
1923
+ letter-spacing: 4px;
1924
+ transition: all 0.3s;
1925
+ margin-top: 8px;
1926
+ }
1927
+
1928
+ .welcome-open-btn:hover {
1929
+ background: rgba(0,212,255,0.2);
1930
+ box-shadow: var(--glow-cyan), 0 0 40px rgba(0,212,255,0.15);
1931
+ transform: translateY(-1px);
1932
+ }
1933
+
1934
+ .welcome-steps {
1935
+ display: flex;
1936
+ align-items: center;
1937
+ gap: 12px;
1938
+ margin-top: 24px;
1939
+ padding: 16px 24px;
1940
+ background: rgba(0,0,0,0.3);
1941
+ border: 1px solid var(--border);
1942
+ border-radius: 4px;
1943
+ }
1944
+
1945
+ .welcome-step {
1946
+ display: flex;
1947
+ align-items: center;
1948
+ gap: 8px;
1949
+ }
1950
+
1951
+ .step-num {
1952
+ width: 22px;
1953
+ height: 22px;
1954
+ border-radius: 50%;
1955
+ border: 1px solid var(--accent-dim);
1956
+ display: flex;
1957
+ align-items: center;
1958
+ justify-content: center;
1959
+ font-size: 10px;
1960
+ font-weight: 700;
1961
+ color: var(--accent);
1962
+ }
1963
+
1964
+ .step-text {
1965
+ font-size: 11px;
1966
+ color: var(--text-secondary);
1967
+ letter-spacing: 1px;
1968
+ }
1969
+
1970
+ .welcome-step-arrow {
1971
+ color: var(--text-secondary);
1972
+ font-size: 14px;
1973
+ opacity: 0.5;
1974
+ }
@@ -26,4 +26,7 @@ export interface SwitchProjectResult {
26
26
  }
27
27
  export declare function createWebServer(getGraphFn: () => DependencyGraph, fileCache?: FileCache, switchProjectFn?: (newRoot: string) => SwitchProjectResult, getProjectRoot?: () => string, getPackageName?: () => string, getLicenseStatus?: () => {
28
28
  plan: string;
29
+ expiresAt?: string;
30
+ error?: string;
31
+ source?: string;
29
32
  }): import("express-serve-static-core").Express;
@@ -245,11 +245,26 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
245
245
  }
246
246
  }
247
247
  }
248
+ /**
249
+ * Build a specific error message for Pro-only features based on license state.
250
+ */
251
+ function getProFeatureError(featureName) {
252
+ const license = getLicenseStatus?.();
253
+ const upgrade = "https://syke.cloud/dashboard/";
254
+ if (license?.error) {
255
+ return { error: `${featureName}: ${license.error}`, requiresPro: true, upgrade };
256
+ }
257
+ if (license?.source === "online" && license?.expiresAt) {
258
+ // Had a license but it's no longer valid (expired trial or subscription)
259
+ return { error: `Trial expired. Upgrade at ${upgrade}`, requiresPro: true, upgrade };
260
+ }
261
+ return { error: `${featureName} requires SYKE Pro. Set SYKE_LICENSE_KEY or sign up at https://syke.cloud`, requiresPro: true, upgrade };
262
+ }
248
263
  app.get("/api/events", (_req, res) => {
249
264
  // Pro-only: real-time monitoring via SSE
250
265
  const license = getLicenseStatus?.();
251
266
  if (!license || license.plan !== "pro") {
252
- res.status(403).json({ error: "Real-time monitoring requires SYKE Pro.", upgrade: "https://syke.cloud/dashboard/" });
267
+ res.status(403).json(getProFeatureError("Real-time monitoring"));
253
268
  return;
254
269
  }
255
270
  res.writeHead(200, {
@@ -438,10 +453,7 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
438
453
  // License check — Pro only
439
454
  const license = getLicenseStatus?.();
440
455
  if (!license || license.plan !== "pro") {
441
- return res.status(403).json({
442
- error: "AI analysis requires SYKE Pro. Upgrade at https://syke.cloud/dashboard/",
443
- requiresPro: true,
444
- });
456
+ return res.status(403).json(getProFeatureError("AI analysis"));
445
457
  }
446
458
  const { file } = req.body;
447
459
  if (!file) {
@@ -465,7 +477,7 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
465
477
  app.get("/api/hub-files", (req, res) => {
466
478
  const license = getLicenseStatus?.();
467
479
  if (!license || license.plan !== "pro") {
468
- return res.status(403).json({ error: "Hub files ranking requires SYKE Pro.", upgrade: "https://syke.cloud/dashboard/" });
480
+ return res.status(403).json(getProFeatureError("Hub files ranking"));
469
481
  }
470
482
  const requested = parseInt(req.query.top) || 10;
471
483
  const graph = getGraphFn();
@@ -540,7 +552,7 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
540
552
  app.get("/api/cycles", (_req, res) => {
541
553
  const license = getLicenseStatus?.();
542
554
  if (!license || license.plan !== "pro") {
543
- return res.status(403).json({ error: "This feature requires SYKE Pro.", upgrade: "https://syke.cloud/dashboard/" });
555
+ return res.status(403).json(getProFeatureError("Cycle detection"));
544
556
  }
545
557
  const graph = getGraphFn();
546
558
  const toRel = (f) => path.relative(graph.sourceDir, f).replace(/\\/g, "/");
@@ -628,7 +640,7 @@ function createWebServer(getGraphFn, fileCache, switchProjectFn, getProjectRoot,
628
640
  app.get("/api/simulate-delete/*splat", (req, res) => {
629
641
  const license = getLicenseStatus?.();
630
642
  if (!license || license.plan !== "pro") {
631
- return res.status(403).json({ error: "This feature requires SYKE Pro.", upgrade: "https://syke.cloud/dashboard/" });
643
+ return res.status(403).json(getProFeatureError("Delete simulation"));
632
644
  }
633
645
  const splat = req.params.splat;
634
646
  const fileParam = Array.isArray(splat) ? splat.join("/") : splat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syke1/mcp-server",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
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",