@leanmcp/cli 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +758 -543
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,13 +3,13 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3
3
 
4
4
  // src/index.ts
5
5
  import { Command } from "commander";
6
- import chalk6 from "chalk";
7
- import fs7 from "fs-extra";
8
- import path7 from "path";
9
- import ora6 from "ora";
6
+ import chalk7 from "chalk";
7
+ import fs8 from "fs-extra";
8
+ import path8 from "path";
9
+ import ora7 from "ora";
10
10
  import { createRequire } from "module";
11
11
  import { confirm as confirm3 } from "@inquirer/prompts";
12
- import { spawn as spawn3 } from "child_process";
12
+ import { spawn as spawn4 } from "child_process";
13
13
 
14
14
  // src/commands/dev.ts
15
15
  import { spawn } from "child_process";
@@ -41,7 +41,7 @@ async function scanUIApp(projectDir) {
41
41
  for (const relativeFile of tsFiles) {
42
42
  const filePath = path.join(mcpDir, relativeFile);
43
43
  const content = await fs.readFile(filePath, "utf-8");
44
- if (!content.includes("@UIApp") || !content.includes("@leanmcp/ui")) {
44
+ if (!content.includes("@UIApp") && !content.includes("@GPTApp") || !content.includes("@leanmcp/ui")) {
45
45
  continue;
46
46
  }
47
47
  const uiApps = parseUIAppDecorators(content, filePath);
@@ -55,11 +55,13 @@ function parseUIAppDecorators(content, filePath) {
55
55
  const classMatch = content.match(/export\s+class\s+(\w+)/);
56
56
  const serviceName = classMatch ? classMatch[1] : "Unknown";
57
57
  const importMap = parseImports(content, filePath);
58
- const uiAppRegex = /@UIApp\s*\(\s*\{([^}]+)\}\s*\)\s*(?:async\s+)?(\w+)/g;
58
+ const uiAppRegex = /@(UIApp|GPTApp)\s*\(\s*\{([\s\S]+?)\}\s*\)\s*(?:async\s+)?(\w+)/g;
59
59
  let match;
60
60
  while ((match = uiAppRegex.exec(content)) !== null) {
61
- const decoratorBody = match[1];
62
- const methodName = match[2];
61
+ const decoratorName = match[1];
62
+ const decoratorBody = match[2];
63
+ const methodName = match[3];
64
+ const isGPTApp = decoratorName === "GPTApp";
63
65
  let componentPath;
64
66
  let componentName;
65
67
  const stringMatch = decoratorBody.match(/component\s*:\s*['"]([^'"]+)['"]/);
@@ -88,14 +90,32 @@ function parseUIAppDecorators(content, filePath) {
88
90
  }
89
91
  if (!componentPath) continue;
90
92
  const servicePrefix = serviceName.replace(/Service$/i, "").toLowerCase();
91
- const resourceUri = `ui://${servicePrefix}/${methodName}`;
93
+ let resourceUri = `ui://${servicePrefix}/${methodName}`;
94
+ const uriMatch = decoratorBody.match(/uri\s*:\s*['"]([^'"]+)['"]/);
95
+ if (uriMatch) {
96
+ resourceUri = uriMatch[1];
97
+ }
98
+ let gptOptions = void 0;
99
+ if (isGPTApp) {
100
+ gptOptions = {};
101
+ if (decoratorBody.includes("widgetAccessible: true")) gptOptions.widgetAccessible = true;
102
+ if (decoratorBody.includes("prefersBorder: true")) gptOptions.prefersBorder = true;
103
+ const visibilityMatch = decoratorBody.match(/visibility\s*:\s*['"](public|private)['"]/);
104
+ if (visibilityMatch) gptOptions.visibility = visibilityMatch[1];
105
+ const domainMatch = decoratorBody.match(/widgetDomain\s*:\s*['"]([^'"]+)['"]/);
106
+ if (domainMatch) gptOptions.widgetDomain = domainMatch[1];
107
+ const descriptionMatch = decoratorBody.match(/widgetDescription\s*:\s*['"]([^'"]+)['"]/);
108
+ if (descriptionMatch) gptOptions.widgetDescription = descriptionMatch[1];
109
+ }
92
110
  results.push({
93
111
  servicePath: filePath,
94
112
  componentPath,
95
113
  componentName,
96
114
  resourceUri,
97
115
  methodName,
98
- serviceName
116
+ serviceName,
117
+ isGPTApp,
118
+ gptOptions
99
119
  });
100
120
  }
101
121
  return results;
@@ -313,7 +333,30 @@ module.exports = {
313
333
  }
314
334
  `);
315
335
  const relativeComponentPath = path2.relative(tempDir, componentPath).replace(/\\/g, "/");
316
- await fs2.writeFile(entryJs, `
336
+ const isGPTApp = uiApp.isGPTApp;
337
+ const entryContent = isGPTApp ? `
338
+ import React, { StrictMode } from 'react';
339
+ import { createRoot } from 'react-dom/client';
340
+ import { GPTAppProvider, Toaster } from '@leanmcp/ui';
341
+ import '@leanmcp/ui/styles.css';
342
+ import './styles.css';
343
+ import { ${componentName} } from '${relativeComponentPath.replace(/\.tsx?$/, "")}';
344
+
345
+ function App() {
346
+ return (
347
+ <GPTAppProvider appName="${componentName}">
348
+ <${componentName} />
349
+ <Toaster />
350
+ </GPTAppProvider>
351
+ );
352
+ }
353
+
354
+ createRoot(document.getElementById('root')!).render(
355
+ <StrictMode>
356
+ <App />
357
+ </StrictMode>
358
+ );
359
+ ` : `
317
360
  import React, { StrictMode } from 'react';
318
361
  import { createRoot } from 'react-dom/client';
319
362
  import { AppProvider, Toaster } from '@leanmcp/ui';
@@ -340,7 +383,8 @@ createRoot(document.getElementById('root')!).render(
340
383
  <App />
341
384
  </StrictMode>
342
385
  );
343
- `);
386
+ `;
387
+ await fs2.writeFile(entryJs, entryContent);
344
388
  try {
345
389
  const reactPath = resolveReactDependency(projectDir, "react");
346
390
  const reactDomPath = resolveReactDependency(projectDir, "react-dom");
@@ -477,7 +521,15 @@ async function devCommand() {
477
521
  for (const app of uiApps) {
478
522
  const result = await buildUIComponent(app, cwd, true);
479
523
  if (result.success) {
480
- manifest[app.resourceUri] = result.htmlPath;
524
+ if (app.isGPTApp) {
525
+ manifest[app.resourceUri] = {
526
+ htmlPath: result.htmlPath,
527
+ isGPTApp: true,
528
+ gptMeta: app.gptOptions
529
+ };
530
+ } else {
531
+ manifest[app.resourceUri] = result.htmlPath;
532
+ }
481
533
  } else {
482
534
  errors.push(`${app.componentName}: ${result.error}`);
483
535
  }
@@ -543,7 +595,15 @@ async function devCommand() {
543
595
  console.log(chalk.cyan(`${action} ${app.componentName}...`));
544
596
  const result = await buildUIComponent(app, cwd, true);
545
597
  if (result.success) {
546
- manifest[app.resourceUri] = result.htmlPath;
598
+ if (app.isGPTApp) {
599
+ manifest[app.resourceUri] = {
600
+ htmlPath: result.htmlPath,
601
+ isGPTApp: true,
602
+ gptMeta: app.gptOptions
603
+ };
604
+ } else {
605
+ manifest[app.resourceUri] = result.htmlPath;
606
+ }
547
607
  if (await fs3.pathExists(app.componentPath)) {
548
608
  componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
549
609
  }
@@ -556,37 +616,51 @@ async function devCommand() {
556
616
  previousUIApps = currentUIApps;
557
617
  }, 150);
558
618
  });
619
+ const isWindows = process.platform === "win32";
559
620
  let isCleaningUp = false;
560
621
  const cleanup = /* @__PURE__ */ __name(() => {
561
622
  if (isCleaningUp) return;
562
623
  isCleaningUp = true;
563
624
  console.log(chalk.gray("\nShutting down..."));
564
- if (watcher) watcher.close();
565
- devServer.kill("SIGTERM");
625
+ if (watcher) {
626
+ watcher.close();
627
+ watcher = null;
628
+ }
629
+ if (!isWindows && !devServer.killed) {
630
+ devServer.kill("SIGTERM");
631
+ }
566
632
  }, "cleanup");
567
- process.on("SIGINT", cleanup);
568
- process.on("SIGTERM", cleanup);
569
- devServer.on("exit", (code) => {
570
- if (watcher) watcher.close();
571
- process.exit(code ?? 0);
633
+ process.once("SIGINT", cleanup);
634
+ process.once("SIGTERM", cleanup);
635
+ devServer.on("error", (err) => {
636
+ console.error(chalk.red(`Dev server error: ${err.message}`));
637
+ });
638
+ devServer.on("exit", (code, signal) => {
639
+ if (watcher) {
640
+ watcher.close();
641
+ watcher = null;
642
+ }
643
+ setImmediate(() => {
644
+ process.exit(code ?? (signal ? 1 : 0));
645
+ });
572
646
  });
573
647
  }
574
648
  __name(devCommand, "devCommand");
575
649
 
576
- // src/commands/start.ts
650
+ // src/commands/build.ts
577
651
  import { spawn as spawn2 } from "child_process";
578
652
  import chalk2 from "chalk";
579
653
  import ora2 from "ora";
580
654
  import path4 from "path";
581
655
  import fs4 from "fs-extra";
582
- async function startCommand() {
656
+ async function buildCommand() {
583
657
  const cwd = process.cwd();
584
658
  if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
585
659
  console.error(chalk2.red("ERROR: Not a LeanMCP project (main.ts not found)."));
586
660
  console.error(chalk2.gray("Run this command from your project root."));
587
661
  process.exit(1);
588
662
  }
589
- console.log(chalk2.cyan("\n\u{1F680} LeanMCP Production Build\n"));
663
+ console.log(chalk2.cyan("\n\u{1F528} LeanMCP Build\n"));
590
664
  const scanSpinner = ora2("Scanning for @UIApp components...").start();
591
665
  const uiApps = await scanUIApp(cwd);
592
666
  if (uiApps.length === 0) {
@@ -601,7 +675,11 @@ async function startCommand() {
601
675
  for (const app of uiApps) {
602
676
  const result = await buildUIComponent(app, cwd, false);
603
677
  if (result.success) {
604
- manifest[app.resourceUri] = result.htmlPath;
678
+ manifest[app.resourceUri] = {
679
+ htmlPath: result.htmlPath,
680
+ isGPTApp: app.isGPTApp,
681
+ gptMeta: app.gptOptions
682
+ };
605
683
  } else {
606
684
  errors.push(`${app.componentName}: ${result.error}`);
607
685
  }
@@ -642,34 +720,125 @@ async function startCommand() {
642
720
  console.error(chalk2.red(error instanceof Error ? error.message : String(error)));
643
721
  process.exit(1);
644
722
  }
645
- console.log(chalk2.cyan("\nStarting production server...\n"));
646
- const server = spawn2("node", [
723
+ console.log(chalk2.green("\nBuild complete!"));
724
+ console.log(chalk2.gray("\nTo start the server:"));
725
+ console.log(chalk2.cyan(" npm run start:node\n"));
726
+ }
727
+ __name(buildCommand, "buildCommand");
728
+
729
+ // src/commands/start.ts
730
+ import { spawn as spawn3 } from "child_process";
731
+ import chalk3 from "chalk";
732
+ import ora3 from "ora";
733
+ import path5 from "path";
734
+ import fs5 from "fs-extra";
735
+ async function startCommand() {
736
+ const cwd = process.cwd();
737
+ if (!await fs5.pathExists(path5.join(cwd, "main.ts"))) {
738
+ console.error(chalk3.red("ERROR: Not a LeanMCP project (main.ts not found)."));
739
+ console.error(chalk3.gray("Run this command from your project root."));
740
+ process.exit(1);
741
+ }
742
+ console.log(chalk3.cyan("\n\u{1F680} LeanMCP Production Build\n"));
743
+ const scanSpinner = ora3("Scanning for @UIApp components...").start();
744
+ const uiApps = await scanUIApp(cwd);
745
+ if (uiApps.length === 0) {
746
+ scanSpinner.succeed("No @UIApp components found");
747
+ } else {
748
+ scanSpinner.succeed(`Found ${uiApps.length} @UIApp component(s)`);
749
+ }
750
+ const manifest = {};
751
+ if (uiApps.length > 0) {
752
+ const buildSpinner = ora3("Building UI components...").start();
753
+ const errors = [];
754
+ for (const app of uiApps) {
755
+ const result = await buildUIComponent(app, cwd, false);
756
+ if (result.success) {
757
+ if (app.isGPTApp) {
758
+ manifest[app.resourceUri] = {
759
+ htmlPath: result.htmlPath,
760
+ isGPTApp: true,
761
+ gptMeta: app.gptOptions
762
+ };
763
+ } else {
764
+ manifest[app.resourceUri] = result.htmlPath;
765
+ }
766
+ } else {
767
+ errors.push(`${app.componentName}: ${result.error}`);
768
+ }
769
+ }
770
+ await writeUIManifest(manifest, cwd);
771
+ if (errors.length > 0) {
772
+ buildSpinner.fail("Build failed");
773
+ for (const error of errors) {
774
+ console.error(chalk3.red(` \u2717 ${error}`));
775
+ }
776
+ process.exit(1);
777
+ }
778
+ buildSpinner.succeed("UI components built");
779
+ }
780
+ const tscSpinner = ora3("Compiling TypeScript...").start();
781
+ try {
782
+ await new Promise((resolve, reject) => {
783
+ const tsc = spawn3("npx", [
784
+ "tsc"
785
+ ], {
786
+ cwd,
787
+ stdio: "pipe",
788
+ shell: true
789
+ });
790
+ let stderr = "";
791
+ tsc.stderr?.on("data", (data) => {
792
+ stderr += data;
793
+ });
794
+ tsc.on("close", (code) => {
795
+ if (code === 0) resolve();
796
+ else reject(new Error(stderr || `tsc exited with code ${code}`));
797
+ });
798
+ tsc.on("error", reject);
799
+ });
800
+ tscSpinner.succeed("TypeScript compiled");
801
+ } catch (error) {
802
+ tscSpinner.fail("TypeScript compilation failed");
803
+ console.error(chalk3.red(error instanceof Error ? error.message : String(error)));
804
+ process.exit(1);
805
+ }
806
+ console.log(chalk3.cyan("\nStarting production server...\n"));
807
+ const server = spawn3("node", [
647
808
  "dist/main.js"
648
809
  ], {
649
810
  cwd,
650
811
  stdio: "inherit",
651
812
  shell: true
652
813
  });
814
+ const isWindows = process.platform === "win32";
653
815
  let isCleaningUp = false;
654
816
  const cleanup = /* @__PURE__ */ __name(() => {
655
817
  if (isCleaningUp) return;
656
818
  isCleaningUp = true;
657
- console.log(chalk2.gray("\nShutting down..."));
658
- server.kill("SIGTERM");
819
+ console.log(chalk3.gray("\nShutting down..."));
820
+ if (!isWindows && !server.killed) {
821
+ server.kill("SIGTERM");
822
+ }
659
823
  }, "cleanup");
660
- process.on("SIGINT", cleanup);
661
- process.on("SIGTERM", cleanup);
662
- server.on("exit", (code) => {
663
- process.exit(code ?? 0);
824
+ process.once("SIGINT", cleanup);
825
+ process.once("SIGTERM", cleanup);
826
+ server.on("error", (err) => {
827
+ console.error(chalk3.red(`Server error: ${err.message}`));
828
+ });
829
+ server.on("exit", (code, signal) => {
830
+ setImmediate(() => {
831
+ process.exit(code ?? (signal ? 1 : 0));
832
+ });
664
833
  });
665
834
  }
666
835
  __name(startCommand, "startCommand");
667
836
 
668
837
  // src/commands/login.ts
669
- import chalk3 from "chalk";
670
- import ora3 from "ora";
671
- import path5 from "path";
672
- import fs5 from "fs-extra";
838
+ import chalk4 from "chalk";
839
+ import ora4 from "ora";
840
+ import path6 from "path";
841
+ import fs6 from "fs-extra";
673
842
  import os from "os";
674
843
  import { input, confirm } from "@inquirer/prompts";
675
844
  var DEBUG_MODE = false;
@@ -679,16 +848,16 @@ function setDebugMode(enabled) {
679
848
  __name(setDebugMode, "setDebugMode");
680
849
  function debug(message, ...args) {
681
850
  if (DEBUG_MODE) {
682
- console.log(chalk3.gray(`[DEBUG] ${message}`), ...args);
851
+ console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
683
852
  }
684
853
  }
685
854
  __name(debug, "debug");
686
- var CONFIG_DIR = path5.join(os.homedir(), ".leanmcp");
687
- var CONFIG_FILE = path5.join(CONFIG_DIR, "config.json");
855
+ var CONFIG_DIR = path6.join(os.homedir(), ".leanmcp");
856
+ var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
688
857
  async function loadConfig() {
689
858
  try {
690
- if (await fs5.pathExists(CONFIG_FILE)) {
691
- return await fs5.readJSON(CONFIG_FILE);
859
+ if (await fs6.pathExists(CONFIG_FILE)) {
860
+ return await fs6.readJSON(CONFIG_FILE);
692
861
  }
693
862
  } catch (error) {
694
863
  }
@@ -696,8 +865,8 @@ async function loadConfig() {
696
865
  }
697
866
  __name(loadConfig, "loadConfig");
698
867
  async function saveConfig(config) {
699
- await fs5.ensureDir(CONFIG_DIR);
700
- await fs5.writeJSON(CONFIG_FILE, config, {
868
+ await fs6.ensureDir(CONFIG_DIR);
869
+ await fs6.writeJSON(CONFIG_FILE, config, {
701
870
  spaces: 2
702
871
  });
703
872
  }
@@ -735,24 +904,24 @@ Allowed URLs:
735
904
  }
736
905
  __name(getApiUrl, "getApiUrl");
737
906
  async function loginCommand() {
738
- console.log(chalk3.cyan("\nLeanMCP Login\n"));
907
+ console.log(chalk4.cyan("\nLeanMCP Login\n"));
739
908
  const existingConfig = await loadConfig();
740
909
  if (existingConfig.apiKey) {
741
- console.log(chalk3.yellow("You are already logged in."));
910
+ console.log(chalk4.yellow("You are already logged in."));
742
911
  const shouldRelogin = await confirm({
743
912
  message: "Do you want to replace the existing API key?",
744
913
  default: false
745
914
  });
746
915
  if (!shouldRelogin) {
747
- console.log(chalk3.gray("\nLogin cancelled. Existing API key preserved."));
916
+ console.log(chalk4.gray("\nLogin cancelled. Existing API key preserved."));
748
917
  return;
749
918
  }
750
919
  }
751
- console.log(chalk3.white("To authenticate, you need an API key from LeanMCP.\n"));
752
- console.log(chalk3.cyan("Steps:"));
753
- console.log(chalk3.gray(" 1. Go to: ") + chalk3.blue.underline("https://ship.leanmcp.com/api-keys"));
754
- console.log(chalk3.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
755
- console.log(chalk3.gray(" 3. Copy the API key and paste it below\n"));
920
+ console.log(chalk4.white("To authenticate, you need an API key from LeanMCP.\n"));
921
+ console.log(chalk4.cyan("Steps:"));
922
+ console.log(chalk4.gray(" 1. Go to: ") + chalk4.blue.underline("https://ship.leanmcp.com/api-keys"));
923
+ console.log(chalk4.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
924
+ console.log(chalk4.gray(" 3. Copy the API key and paste it below\n"));
756
925
  const apiKey = await input({
757
926
  message: "Enter your API key:",
758
927
  validate: /* @__PURE__ */ __name((value) => {
@@ -765,7 +934,7 @@ async function loginCommand() {
765
934
  return true;
766
935
  }, "validate")
767
936
  });
768
- const spinner = ora3("Validating API key...").start();
937
+ const spinner = ora4("Validating API key...").start();
769
938
  try {
770
939
  const apiUrl = await getApiUrl();
771
940
  const validateUrl = `${apiUrl}/api-keys/validate`;
@@ -785,10 +954,10 @@ async function loginCommand() {
785
954
  const errorText = await response.text();
786
955
  debug("Error response:", errorText);
787
956
  spinner.fail("Invalid API key");
788
- console.error(chalk3.red("\nThe API key is invalid or has expired."));
789
- console.log(chalk3.gray("Please check your API key and try again."));
957
+ console.error(chalk4.red("\nThe API key is invalid or has expired."));
958
+ console.log(chalk4.gray("Please check your API key and try again."));
790
959
  if (DEBUG_MODE) {
791
- console.log(chalk3.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
960
+ console.log(chalk4.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
792
961
  }
793
962
  process.exit(1);
794
963
  }
@@ -798,24 +967,24 @@ async function loginCommand() {
798
967
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
799
968
  });
800
969
  spinner.succeed("API key validated and saved");
801
- console.log(chalk3.green("\nLogin successful!"));
802
- console.log(chalk3.gray(` Config saved to: ${CONFIG_FILE}
970
+ console.log(chalk4.green("\nLogin successful!"));
971
+ console.log(chalk4.gray(` Config saved to: ${CONFIG_FILE}
803
972
  `));
804
- console.log(chalk3.cyan("You can now use:"));
805
- console.log(chalk3.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
806
- console.log(chalk3.gray(" leanmcp logout - Remove your API key"));
973
+ console.log(chalk4.cyan("You can now use:"));
974
+ console.log(chalk4.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
975
+ console.log(chalk4.gray(" leanmcp logout - Remove your API key"));
807
976
  } catch (error) {
808
977
  spinner.fail("Failed to validate API key");
809
978
  debug("Error:", error);
810
979
  if (error instanceof Error && error.message.includes("fetch")) {
811
- console.error(chalk3.red("\nCould not connect to LeanMCP servers."));
812
- console.log(chalk3.gray("Please check your internet connection and try again."));
980
+ console.error(chalk4.red("\nCould not connect to LeanMCP servers."));
981
+ console.log(chalk4.gray("Please check your internet connection and try again."));
813
982
  } else {
814
- console.error(chalk3.red(`
983
+ console.error(chalk4.red(`
815
984
  Error: ${error instanceof Error ? error.message : String(error)}`));
816
985
  }
817
986
  if (DEBUG_MODE) {
818
- console.log(chalk3.gray(`
987
+ console.log(chalk4.gray(`
819
988
  Debug: Full error: ${error}`));
820
989
  }
821
990
  process.exit(1);
@@ -823,10 +992,10 @@ Debug: Full error: ${error}`));
823
992
  }
824
993
  __name(loginCommand, "loginCommand");
825
994
  async function logoutCommand() {
826
- console.log(chalk3.cyan("\nLeanMCP Logout\n"));
995
+ console.log(chalk4.cyan("\nLeanMCP Logout\n"));
827
996
  const config = await loadConfig();
828
997
  if (!config.apiKey) {
829
- console.log(chalk3.yellow("You are not currently logged in."));
998
+ console.log(chalk4.yellow("You are not currently logged in."));
830
999
  return;
831
1000
  }
832
1001
  const shouldLogout = await confirm({
@@ -834,7 +1003,7 @@ async function logoutCommand() {
834
1003
  default: false
835
1004
  });
836
1005
  if (!shouldLogout) {
837
- console.log(chalk3.gray("\nLogout cancelled."));
1006
+ console.log(chalk4.gray("\nLogout cancelled."));
838
1007
  return;
839
1008
  }
840
1009
  await saveConfig({
@@ -842,33 +1011,33 @@ async function logoutCommand() {
842
1011
  apiKey: void 0,
843
1012
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
844
1013
  });
845
- console.log(chalk3.green("\nLogged out successfully!"));
846
- console.log(chalk3.gray(` API key removed from: ${CONFIG_FILE}`));
1014
+ console.log(chalk4.green("\nLogged out successfully!"));
1015
+ console.log(chalk4.gray(` API key removed from: ${CONFIG_FILE}`));
847
1016
  }
848
1017
  __name(logoutCommand, "logoutCommand");
849
1018
  async function whoamiCommand() {
850
1019
  const config = await loadConfig();
851
1020
  if (!config.apiKey) {
852
- console.log(chalk3.yellow("\nYou are not logged in."));
853
- console.log(chalk3.gray("Run `leanmcp login` to authenticate.\n"));
1021
+ console.log(chalk4.yellow("\nYou are not logged in."));
1022
+ console.log(chalk4.gray("Run `leanmcp login` to authenticate.\n"));
854
1023
  return;
855
1024
  }
856
- console.log(chalk3.cyan("\nLeanMCP Authentication Status\n"));
857
- console.log(chalk3.green("Logged in"));
858
- console.log(chalk3.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
859
- console.log(chalk3.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
1025
+ console.log(chalk4.cyan("\nLeanMCP Authentication Status\n"));
1026
+ console.log(chalk4.green("Logged in"));
1027
+ console.log(chalk4.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
1028
+ console.log(chalk4.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
860
1029
  if (config.lastUpdated) {
861
- console.log(chalk3.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
1030
+ console.log(chalk4.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
862
1031
  }
863
1032
  console.log();
864
1033
  }
865
1034
  __name(whoamiCommand, "whoamiCommand");
866
1035
 
867
1036
  // src/commands/deploy.ts
868
- import chalk4 from "chalk";
869
- import ora4 from "ora";
870
- import path6 from "path";
871
- import fs6 from "fs-extra";
1037
+ import chalk5 from "chalk";
1038
+ import ora5 from "ora";
1039
+ import path7 from "path";
1040
+ import fs7 from "fs-extra";
872
1041
  import os2 from "os";
873
1042
  import archiver from "archiver";
874
1043
  import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
@@ -1058,7 +1227,7 @@ function setDeployDebugMode(enabled) {
1058
1227
  __name(setDeployDebugMode, "setDeployDebugMode");
1059
1228
  function debug2(message, ...args) {
1060
1229
  if (DEBUG_MODE2) {
1061
- console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
1230
+ console.log(chalk5.gray(`[DEBUG] ${message}`), ...args);
1062
1231
  }
1063
1232
  }
1064
1233
  __name(debug2, "debug");
@@ -1095,7 +1264,7 @@ var API_ENDPOINTS = {
1095
1264
  };
1096
1265
  async function createZipArchive(folderPath, outputPath) {
1097
1266
  return new Promise((resolve, reject) => {
1098
- const output = fs6.createWriteStream(outputPath);
1267
+ const output = fs7.createWriteStream(outputPath);
1099
1268
  const archive = archiver("zip", {
1100
1269
  zlib: {
1101
1270
  level: 9
@@ -1182,26 +1351,26 @@ async function waitForDeployment(apiUrl, apiKey, deploymentId, spinner) {
1182
1351
  __name(waitForDeployment, "waitForDeployment");
1183
1352
  async function deployCommand(folderPath, options = {}) {
1184
1353
  const deployStartTime = Date.now();
1185
- console.log(chalk4.cyan("\nLeanMCP Deploy\n"));
1354
+ console.log(chalk5.cyan("\nLeanMCP Deploy\n"));
1186
1355
  debug2("Starting deployment...");
1187
1356
  const apiKey = await getApiKey();
1188
1357
  if (!apiKey) {
1189
- console.error(chalk4.red("Not logged in."));
1190
- console.log(chalk4.gray("Run `leanmcp login` first to authenticate.\n"));
1358
+ console.error(chalk5.red("Not logged in."));
1359
+ console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1191
1360
  process.exit(1);
1192
1361
  }
1193
1362
  const apiUrl = await getApiUrl();
1194
1363
  debug2("API URL:", apiUrl);
1195
- const absolutePath = path6.resolve(process.cwd(), folderPath);
1196
- if (!await fs6.pathExists(absolutePath)) {
1197
- console.error(chalk4.red(`Folder not found: ${absolutePath}`));
1364
+ const absolutePath = path7.resolve(process.cwd(), folderPath);
1365
+ if (!await fs7.pathExists(absolutePath)) {
1366
+ console.error(chalk5.red(`Folder not found: ${absolutePath}`));
1198
1367
  process.exit(1);
1199
1368
  }
1200
- const hasMainTs = await fs6.pathExists(path6.join(absolutePath, "main.ts"));
1201
- const hasPackageJson = await fs6.pathExists(path6.join(absolutePath, "package.json"));
1369
+ const hasMainTs = await fs7.pathExists(path7.join(absolutePath, "main.ts"));
1370
+ const hasPackageJson = await fs7.pathExists(path7.join(absolutePath, "package.json"));
1202
1371
  if (!hasMainTs && !hasPackageJson) {
1203
- console.error(chalk4.red("Not a valid project folder."));
1204
- console.log(chalk4.gray("Expected main.ts or package.json in the folder.\n"));
1372
+ console.error(chalk5.red("Not a valid project folder."));
1373
+ console.log(chalk5.gray("Expected main.ts or package.json in the folder.\n"));
1205
1374
  process.exit(1);
1206
1375
  }
1207
1376
  debug2("Fetching existing projects...");
@@ -1222,17 +1391,17 @@ async function deployCommand(folderPath, options = {}) {
1222
1391
  let projectName;
1223
1392
  let existingProject = null;
1224
1393
  let isUpdate = false;
1225
- let folderName = path6.basename(absolutePath);
1394
+ let folderName = path7.basename(absolutePath);
1226
1395
  if (hasPackageJson) {
1227
1396
  try {
1228
- const pkg2 = await fs6.readJSON(path6.join(absolutePath, "package.json"));
1397
+ const pkg2 = await fs7.readJSON(path7.join(absolutePath, "package.json"));
1229
1398
  folderName = pkg2.name || folderName;
1230
1399
  } catch (e) {
1231
1400
  }
1232
1401
  }
1233
1402
  const matchingProject = existingProjects.find((p) => p.name === folderName);
1234
1403
  if (matchingProject) {
1235
- console.log(chalk4.yellow(`Project '${folderName}' already exists.
1404
+ console.log(chalk5.yellow(`Project '${folderName}' already exists.
1236
1405
  `));
1237
1406
  const choice = await select({
1238
1407
  message: "What would you like to do?",
@@ -1252,26 +1421,26 @@ async function deployCommand(folderPath, options = {}) {
1252
1421
  ]
1253
1422
  });
1254
1423
  if (choice === "cancel") {
1255
- console.log(chalk4.gray("\nDeployment cancelled.\n"));
1424
+ console.log(chalk5.gray("\nDeployment cancelled.\n"));
1256
1425
  return;
1257
1426
  }
1258
1427
  if (choice === "update") {
1259
1428
  existingProject = matchingProject;
1260
1429
  projectName = matchingProject.name;
1261
1430
  isUpdate = true;
1262
- console.log(chalk4.yellow("\nWARNING: This will replace the existing deployment."));
1263
- console.log(chalk4.gray("The previous version will be overwritten.\n"));
1431
+ console.log(chalk5.yellow("\nWARNING: This will replace the existing deployment."));
1432
+ console.log(chalk5.gray("The previous version will be overwritten.\n"));
1264
1433
  } else {
1265
1434
  projectName = generateProjectName();
1266
- console.log(chalk4.cyan(`
1267
- Generated project name: ${chalk4.bold(projectName)}
1435
+ console.log(chalk5.cyan(`
1436
+ Generated project name: ${chalk5.bold(projectName)}
1268
1437
  `));
1269
1438
  }
1270
1439
  } else {
1271
1440
  projectName = generateProjectName();
1272
- console.log(chalk4.cyan(`Generated project name: ${chalk4.bold(projectName)}`));
1441
+ console.log(chalk5.cyan(`Generated project name: ${chalk5.bold(projectName)}`));
1273
1442
  }
1274
- console.log(chalk4.gray(`Path: ${absolutePath}
1443
+ console.log(chalk5.gray(`Path: ${absolutePath}
1275
1444
  `));
1276
1445
  let subdomain = options.subdomain;
1277
1446
  if (!subdomain) {
@@ -1293,7 +1462,7 @@ Generated project name: ${chalk4.bold(projectName)}
1293
1462
  }, "validate")
1294
1463
  });
1295
1464
  }
1296
- const checkSpinner = ora4("Checking subdomain availability...").start();
1465
+ const checkSpinner = ora5("Checking subdomain availability...").start();
1297
1466
  try {
1298
1467
  debug2("Checking subdomain:", subdomain);
1299
1468
  const checkResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.checkSubdomain}/${subdomain}`, {
@@ -1305,7 +1474,7 @@ Generated project name: ${chalk4.bold(projectName)}
1305
1474
  const result = await checkResponse.json();
1306
1475
  if (!result.available) {
1307
1476
  checkSpinner.fail(`Subdomain '${subdomain}' is already taken`);
1308
- console.log(chalk4.gray("\nPlease choose a different subdomain.\n"));
1477
+ console.log(chalk5.gray("\nPlease choose a different subdomain.\n"));
1309
1478
  process.exit(1);
1310
1479
  }
1311
1480
  }
@@ -1314,17 +1483,17 @@ Generated project name: ${chalk4.bold(projectName)}
1314
1483
  checkSpinner.warn("Could not verify subdomain availability");
1315
1484
  }
1316
1485
  if (!options.skipConfirm) {
1317
- console.log(chalk4.cyan("\nDeployment Details:"));
1318
- console.log(chalk4.gray(` Project: ${projectName}`));
1319
- console.log(chalk4.gray(` Subdomain: ${subdomain}`));
1320
- console.log(chalk4.gray(` URL: https://${subdomain}.leanmcp.dev
1486
+ console.log(chalk5.cyan("\nDeployment Details:"));
1487
+ console.log(chalk5.gray(` Project: ${projectName}`));
1488
+ console.log(chalk5.gray(` Subdomain: ${subdomain}`));
1489
+ console.log(chalk5.gray(` URL: https://${subdomain}.leanmcp.dev
1321
1490
  `));
1322
1491
  const shouldDeploy = await confirm2({
1323
1492
  message: "Proceed with deployment?",
1324
1493
  default: true
1325
1494
  });
1326
1495
  if (!shouldDeploy) {
1327
- console.log(chalk4.gray("\nDeployment cancelled.\n"));
1496
+ console.log(chalk5.gray("\nDeployment cancelled.\n"));
1328
1497
  return;
1329
1498
  }
1330
1499
  }
@@ -1332,9 +1501,9 @@ Generated project name: ${chalk4.bold(projectName)}
1332
1501
  let projectId;
1333
1502
  if (isUpdate && existingProject) {
1334
1503
  projectId = existingProject.id;
1335
- console.log(chalk4.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
1504
+ console.log(chalk5.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
1336
1505
  } else {
1337
- const projectSpinner = ora4("Creating project...").start();
1506
+ const projectSpinner = ora5("Creating project...").start();
1338
1507
  try {
1339
1508
  debug2("Step 1: Creating project:", projectName);
1340
1509
  const createResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
@@ -1356,14 +1525,14 @@ Generated project name: ${chalk4.bold(projectName)}
1356
1525
  projectSpinner.succeed(`Project created: ${projectId.substring(0, 8)}...`);
1357
1526
  } catch (error) {
1358
1527
  projectSpinner.fail("Failed to create project");
1359
- console.error(chalk4.red(`
1528
+ console.error(chalk5.red(`
1360
1529
  ${error instanceof Error ? error.message : String(error)}`));
1361
1530
  process.exit(1);
1362
1531
  }
1363
1532
  }
1364
- const uploadSpinner = ora4("Packaging and uploading...").start();
1533
+ const uploadSpinner = ora5("Packaging and uploading...").start();
1365
1534
  try {
1366
- const tempZip = path6.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
1535
+ const tempZip = path7.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
1367
1536
  const zipSize = await createZipArchive(absolutePath, tempZip);
1368
1537
  uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
1369
1538
  debug2("Step 2a: Getting upload URL for project:", projectId);
@@ -1390,7 +1559,7 @@ ${error instanceof Error ? error.message : String(error)}`));
1390
1559
  throw new Error("Backend did not return upload URL");
1391
1560
  }
1392
1561
  debug2("Step 2b: Uploading to S3...");
1393
- const zipBuffer = await fs6.readFile(tempZip);
1562
+ const zipBuffer = await fs7.readFile(tempZip);
1394
1563
  const s3Response = await fetch(uploadUrl, {
1395
1564
  method: "PUT",
1396
1565
  body: zipBuffer,
@@ -1413,15 +1582,15 @@ ${error instanceof Error ? error.message : String(error)}`));
1413
1582
  s3Location
1414
1583
  })
1415
1584
  });
1416
- await fs6.remove(tempZip);
1585
+ await fs7.remove(tempZip);
1417
1586
  uploadSpinner.succeed("Project uploaded");
1418
1587
  } catch (error) {
1419
1588
  uploadSpinner.fail("Failed to upload");
1420
- console.error(chalk4.red(`
1589
+ console.error(chalk5.red(`
1421
1590
  ${error instanceof Error ? error.message : String(error)}`));
1422
1591
  process.exit(1);
1423
1592
  }
1424
- const buildSpinner = ora4("Building...").start();
1593
+ const buildSpinner = ora5("Building...").start();
1425
1594
  const buildStartTime = Date.now();
1426
1595
  let buildId;
1427
1596
  let imageUri;
@@ -1445,11 +1614,11 @@ ${error instanceof Error ? error.message : String(error)}`));
1445
1614
  buildSpinner.succeed(`Build complete (${buildDuration}s)`);
1446
1615
  } catch (error) {
1447
1616
  buildSpinner.fail("Build failed");
1448
- console.error(chalk4.red(`
1617
+ console.error(chalk5.red(`
1449
1618
  ${error instanceof Error ? error.message : String(error)}`));
1450
1619
  process.exit(1);
1451
1620
  }
1452
- const deploySpinner = ora4("Deploying to LeanMCP...").start();
1621
+ const deploySpinner = ora5("Deploying to LeanMCP...").start();
1453
1622
  let deploymentId;
1454
1623
  let functionUrl;
1455
1624
  try {
@@ -1475,11 +1644,11 @@ ${error instanceof Error ? error.message : String(error)}`));
1475
1644
  deploySpinner.succeed("Deployed");
1476
1645
  } catch (error) {
1477
1646
  deploySpinner.fail("Deployment failed");
1478
- console.error(chalk4.red(`
1647
+ console.error(chalk5.red(`
1479
1648
  ${error instanceof Error ? error.message : String(error)}`));
1480
1649
  process.exit(1);
1481
1650
  }
1482
- const mappingSpinner = ora4("Configuring subdomain...").start();
1651
+ const mappingSpinner = ora5("Configuring subdomain...").start();
1483
1652
  try {
1484
1653
  debug2("Step 5: Creating subdomain mapping:", subdomain);
1485
1654
  const mappingResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createMapping}`, {
@@ -1503,38 +1672,43 @@ ${error instanceof Error ? error.message : String(error)}`));
1503
1672
  } catch (error) {
1504
1673
  mappingSpinner.warn("Subdomain mapping may need manual setup");
1505
1674
  }
1506
- console.log(chalk4.green("\n" + "=".repeat(60)));
1507
- console.log(chalk4.green.bold(" DEPLOYMENT SUCCESSFUL!"));
1508
- console.log(chalk4.green("=".repeat(60) + "\n"));
1509
- console.log(chalk4.white(" Your MCP server is now live:\n"));
1510
- console.log(chalk4.cyan(` URL: `) + chalk4.white.bold(`https://${subdomain}.leanmcp.dev`));
1675
+ console.log(chalk5.green("\n" + "=".repeat(60)));
1676
+ console.log(chalk5.green.bold(" DEPLOYMENT SUCCESSFUL!"));
1677
+ console.log(chalk5.green("=".repeat(60) + "\n"));
1678
+ console.log(chalk5.white(" Your MCP server is now live:\n"));
1679
+ console.log(chalk5.cyan(` URL: `) + chalk5.white.bold(`https://${subdomain}.leanmcp.dev`));
1511
1680
  console.log();
1512
- console.log(chalk4.gray(" Test endpoints:"));
1513
- console.log(chalk4.gray(` curl https://${subdomain}.leanmcp.dev/health`));
1514
- console.log(chalk4.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
1681
+ console.log(chalk5.gray(" Test endpoints:"));
1682
+ console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/health`));
1683
+ console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
1515
1684
  console.log();
1516
1685
  const totalDuration = Math.round((Date.now() - deployStartTime) / 1e3);
1517
- console.log(chalk4.gray(` Total time: ${totalDuration}s`));
1686
+ console.log(chalk5.gray(` Total time: ${totalDuration}s`));
1687
+ console.log();
1688
+ const dashboardBaseUrl = "https://ship.leanmcp.com";
1689
+ console.log(chalk5.cyan(" Dashboard links:"));
1690
+ console.log(chalk5.gray(` Project: ${dashboardBaseUrl}/projects/${projectId}`));
1691
+ console.log(chalk5.gray(` Build: ${dashboardBaseUrl}/builds/${buildId}`));
1692
+ console.log(chalk5.gray(` Deployment: ${dashboardBaseUrl}/deployments/${deploymentId}`));
1518
1693
  console.log();
1519
- console.log(chalk4.gray(` Project ID: ${projectId}`));
1520
- console.log(chalk4.gray(` Build ID: ${buildId}`));
1521
- console.log(chalk4.gray(` Deployment ID: ${deploymentId}`));
1694
+ console.log(chalk5.cyan(" Need help? Join our Discord:"));
1695
+ console.log(chalk5.blue(" https://discord.com/invite/DsRcA3GwPy"));
1522
1696
  console.log();
1523
1697
  }
1524
1698
  __name(deployCommand, "deployCommand");
1525
1699
 
1526
1700
  // src/commands/projects.ts
1527
- import chalk5 from "chalk";
1528
- import ora5 from "ora";
1701
+ import chalk6 from "chalk";
1702
+ import ora6 from "ora";
1529
1703
  var API_ENDPOINT = "/api/projects";
1530
1704
  async function projectsListCommand() {
1531
1705
  const apiKey = await getApiKey();
1532
1706
  if (!apiKey) {
1533
- console.error(chalk5.red("\nNot logged in."));
1534
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1707
+ console.error(chalk6.red("\nNot logged in."));
1708
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1535
1709
  process.exit(1);
1536
1710
  }
1537
- const spinner = ora5("Fetching projects...").start();
1711
+ const spinner = ora6("Fetching projects...").start();
1538
1712
  try {
1539
1713
  const apiUrl = await getApiUrl();
1540
1714
  const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
@@ -1548,25 +1722,25 @@ async function projectsListCommand() {
1548
1722
  const projects = await response.json();
1549
1723
  spinner.stop();
1550
1724
  if (projects.length === 0) {
1551
- console.log(chalk5.yellow("\nNo projects found."));
1552
- console.log(chalk5.gray("Create one with: leanmcp deploy <folder>\n"));
1725
+ console.log(chalk6.yellow("\nNo projects found."));
1726
+ console.log(chalk6.gray("Create one with: leanmcp deploy <folder>\n"));
1553
1727
  return;
1554
1728
  }
1555
- console.log(chalk5.cyan(`
1729
+ console.log(chalk6.cyan(`
1556
1730
  Your Projects (${projects.length})
1557
1731
  `));
1558
- console.log(chalk5.gray("\u2500".repeat(60)));
1732
+ console.log(chalk6.gray("\u2500".repeat(60)));
1559
1733
  for (const project of projects) {
1560
- const statusColor = project.status === "ACTIVE" ? chalk5.green : chalk5.yellow;
1561
- console.log(chalk5.white.bold(` ${project.name}`));
1562
- console.log(chalk5.gray(` ID: ${project.id}`));
1563
- console.log(chalk5.gray(` Status: `) + statusColor(project.status));
1564
- console.log(chalk5.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
1734
+ const statusColor = project.status === "ACTIVE" ? chalk6.green : chalk6.yellow;
1735
+ console.log(chalk6.white.bold(` ${project.name}`));
1736
+ console.log(chalk6.gray(` ID: ${project.id}`));
1737
+ console.log(chalk6.gray(` Status: `) + statusColor(project.status));
1738
+ console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
1565
1739
  console.log();
1566
1740
  }
1567
1741
  } catch (error) {
1568
1742
  spinner.fail("Failed to fetch projects");
1569
- console.error(chalk5.red(`
1743
+ console.error(chalk6.red(`
1570
1744
  ${error instanceof Error ? error.message : String(error)}`));
1571
1745
  process.exit(1);
1572
1746
  }
@@ -1575,11 +1749,11 @@ __name(projectsListCommand, "projectsListCommand");
1575
1749
  async function projectsGetCommand(projectId) {
1576
1750
  const apiKey = await getApiKey();
1577
1751
  if (!apiKey) {
1578
- console.error(chalk5.red("\nNot logged in."));
1579
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1752
+ console.error(chalk6.red("\nNot logged in."));
1753
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1580
1754
  process.exit(1);
1581
1755
  }
1582
- const spinner = ora5("Fetching project...").start();
1756
+ const spinner = ora6("Fetching project...").start();
1583
1757
  try {
1584
1758
  const apiUrl = await getApiUrl();
1585
1759
  const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
@@ -1595,22 +1769,22 @@ async function projectsGetCommand(projectId) {
1595
1769
  }
1596
1770
  const project = await response.json();
1597
1771
  spinner.stop();
1598
- console.log(chalk5.cyan("\nProject Details\n"));
1599
- console.log(chalk5.gray("\u2500".repeat(60)));
1600
- console.log(chalk5.white.bold(` Name: ${project.name}`));
1601
- console.log(chalk5.gray(` ID: ${project.id}`));
1602
- console.log(chalk5.gray(` Status: ${project.status}`));
1772
+ console.log(chalk6.cyan("\nProject Details\n"));
1773
+ console.log(chalk6.gray("\u2500".repeat(60)));
1774
+ console.log(chalk6.white.bold(` Name: ${project.name}`));
1775
+ console.log(chalk6.gray(` ID: ${project.id}`));
1776
+ console.log(chalk6.gray(` Status: ${project.status}`));
1603
1777
  if (project.s3Location) {
1604
- console.log(chalk5.gray(` S3 Location: ${project.s3Location}`));
1778
+ console.log(chalk6.gray(` S3 Location: ${project.s3Location}`));
1605
1779
  }
1606
- console.log(chalk5.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
1780
+ console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
1607
1781
  if (project.updatedAt) {
1608
- console.log(chalk5.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
1782
+ console.log(chalk6.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
1609
1783
  }
1610
1784
  console.log();
1611
1785
  } catch (error) {
1612
1786
  spinner.fail("Failed to fetch project");
1613
- console.error(chalk5.red(`
1787
+ console.error(chalk6.red(`
1614
1788
  ${error instanceof Error ? error.message : String(error)}`));
1615
1789
  process.exit(1);
1616
1790
  }
@@ -1619,8 +1793,8 @@ __name(projectsGetCommand, "projectsGetCommand");
1619
1793
  async function projectsDeleteCommand(projectId, options = {}) {
1620
1794
  const apiKey = await getApiKey();
1621
1795
  if (!apiKey) {
1622
- console.error(chalk5.red("\nNot logged in."));
1623
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1796
+ console.error(chalk6.red("\nNot logged in."));
1797
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1624
1798
  process.exit(1);
1625
1799
  }
1626
1800
  if (!options.force) {
@@ -1630,11 +1804,11 @@ async function projectsDeleteCommand(projectId, options = {}) {
1630
1804
  default: false
1631
1805
  });
1632
1806
  if (!shouldDelete) {
1633
- console.log(chalk5.gray("\nDeletion cancelled.\n"));
1807
+ console.log(chalk6.gray("\nDeletion cancelled.\n"));
1634
1808
  return;
1635
1809
  }
1636
1810
  }
1637
- const spinner = ora5("Deleting project...").start();
1811
+ const spinner = ora6("Deleting project...").start();
1638
1812
  try {
1639
1813
  const apiUrl = await getApiUrl();
1640
1814
  const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
@@ -1653,238 +1827,102 @@ async function projectsDeleteCommand(projectId, options = {}) {
1653
1827
  console.log();
1654
1828
  } catch (error) {
1655
1829
  spinner.fail("Failed to delete project");
1656
- console.error(chalk5.red(`
1830
+ console.error(chalk6.red(`
1657
1831
  ${error instanceof Error ? error.message : String(error)}`));
1658
1832
  process.exit(1);
1659
1833
  }
1660
1834
  }
1661
1835
  __name(projectsDeleteCommand, "projectsDeleteCommand");
1662
1836
 
1663
- // src/index.ts
1664
- var require2 = createRequire(import.meta.url);
1665
- var pkg = require2("../package.json");
1666
- function capitalize(str) {
1667
- return str.charAt(0).toUpperCase() + str.slice(1);
1668
- }
1669
- __name(capitalize, "capitalize");
1670
- var program = new Command();
1671
- program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
1672
- Examples:
1673
- $ leanmcp create my-app # Create new project (interactive)
1674
- $ leanmcp create my-app --install # Create and install deps (non-interactive)
1675
- $ leanmcp create my-app --no-install # Create without installing deps
1676
- $ leanmcp dev # Start development server
1677
- $ leanmcp login # Authenticate with LeanMCP cloud
1678
- $ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
1679
- $ leanmcp projects list # List your cloud projects
1680
- $ leanmcp projects delete <id> # Delete a cloud project
1681
- `);
1682
- program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").option("--no-dashboard", "Disable dashboard UI at / and /mcp GET endpoints").option("--install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
1683
- const spinner = ora6(`Creating project ${projectName}...`).start();
1684
- const targetDir = path7.join(process.cwd(), projectName);
1685
- if (fs7.existsSync(targetDir)) {
1686
- spinner.fail(`Folder ${projectName} already exists.`);
1687
- process.exit(1);
1688
- }
1689
- await fs7.mkdirp(targetDir);
1690
- await fs7.mkdirp(path7.join(targetDir, "mcp", "example"));
1691
- const pkg2 = {
1692
- name: projectName,
1693
- version: "1.0.0",
1694
- description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
1695
- main: "dist/main.js",
1696
- type: "module",
1697
- scripts: {
1698
- dev: "tsx watch main.ts",
1699
- build: "tsc",
1700
- start: "node dist/main.js",
1701
- clean: "rm -rf dist"
1702
- },
1703
- keywords: [
1704
- "mcp",
1705
- "model-context-protocol",
1706
- "streamable-http",
1707
- "leanmcp"
1708
- ],
1709
- author: "",
1710
- license: "MIT",
1711
- dependencies: {
1712
- "@leanmcp/core": "^0.3.5",
1713
- "dotenv": "^16.5.0"
1714
- },
1715
- devDependencies: {
1716
- "@types/node": "^20.0.0",
1717
- "tsx": "^4.20.3",
1718
- "typescript": "^5.6.3"
1719
- }
1720
- };
1721
- await fs7.writeJSON(path7.join(targetDir, "package.json"), pkg2, {
1722
- spaces: 2
1723
- });
1724
- const tsconfig = {
1725
- compilerOptions: {
1726
- module: "ESNext",
1727
- target: "ES2022",
1728
- moduleResolution: "Node",
1729
- esModuleInterop: true,
1730
- strict: true,
1731
- skipLibCheck: true,
1732
- outDir: "dist",
1733
- experimentalDecorators: true,
1734
- emitDecoratorMetadata: true
1735
- },
1736
- include: [
1737
- "**/*.ts"
1738
- ],
1739
- exclude: [
1740
- "node_modules",
1741
- "dist"
1742
- ]
1743
- };
1744
- await fs7.writeJSON(path7.join(targetDir, "tsconfig.json"), tsconfig, {
1745
- spaces: 2
1746
- });
1747
- const dashboardLine = options.dashboard === false ? `
1748
- dashboard: false, // Dashboard disabled via --no-dashboard` : "";
1749
- const mainTs = `import dotenv from "dotenv";
1750
- import { createHTTPServer } from "@leanmcp/core";
1837
+ // src/templates/readme_v1.ts
1838
+ var getReadmeTemplate = /* @__PURE__ */ __name((projectName) => `# ${projectName}
1751
1839
 
1752
- // Load environment variables
1753
- dotenv.config();
1840
+ MCP Server with Streamable HTTP Transport built with LeanMCP SDK
1754
1841
 
1755
- // Services are automatically discovered from ./mcp directory
1756
- await createHTTPServer({
1757
- name: "${projectName}",
1758
- version: "1.0.0",
1759
- port: 3001,
1760
- cors: true,
1761
- logging: true${dashboardLine}
1762
- });
1842
+ ## Quick Start
1763
1843
 
1764
- console.log("\\n${projectName} MCP Server");
1765
- `;
1766
- await fs7.writeFile(path7.join(targetDir, "main.ts"), mainTs);
1767
- const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
1844
+ \`\`\`bash
1845
+ # Install dependencies
1846
+ npm install
1768
1847
 
1769
- /**
1770
- * Example service demonstrating LeanMCP SDK decorators
1771
- *
1772
- * This is a simple example to get you started. Add your own tools, resources, and prompts here!
1773
- */
1848
+ # Start development server (hot reload)
1849
+ npm run dev
1774
1850
 
1775
- // Input schema with validation decorators
1776
- class CalculateInput {
1777
- @SchemaConstraint({ description: "First number" })
1778
- a!: number;
1851
+ # Build for production
1852
+ npm run build
1779
1853
 
1780
- @SchemaConstraint({ description: "Second number" })
1781
- b!: number;
1854
+ # Run production server
1855
+ npm start
1856
+ \`\`\`
1782
1857
 
1783
- @Optional()
1784
- @SchemaConstraint({
1785
- description: "Operation to perform",
1786
- enum: ["add", "subtract", "multiply", "divide"],
1787
- default: "add"
1788
- })
1789
- operation?: string;
1790
- }
1791
-
1792
- class EchoInput {
1793
- @SchemaConstraint({
1794
- description: "Message to echo back",
1795
- minLength: 1
1796
- })
1797
- message!: string;
1798
- }
1858
+ ## Project Structure
1799
1859
 
1800
- export class ExampleService {
1801
- @Tool({
1802
- description: "Perform arithmetic operations with automatic schema validation",
1803
- inputClass: CalculateInput
1804
- })
1805
- async calculate(input: CalculateInput) {
1806
- // Ensure numerical operations by explicitly converting to numbers
1807
- const a = Number(input.a);
1808
- const b = Number(input.b);
1809
- let result: number;
1860
+ \`\`\`
1861
+ ${projectName}/
1862
+ \u251C\u2500\u2500 main.ts # Server entry point
1863
+ \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
1864
+ \u2502 \u2514\u2500\u2500 example/
1865
+ \u2502 \u2514\u2500\u2500 index.ts # Example service
1866
+ \u251C\u2500\u2500 .env # Environment variables
1867
+ \u2514\u2500\u2500 package.json
1868
+ \`\`\`
1810
1869
 
1811
- switch (input.operation || "add") {
1812
- case "add":
1813
- result = a + b;
1814
- break;
1815
- case "subtract":
1816
- result = a - b;
1817
- break;
1818
- case "multiply":
1819
- result = a * b;
1820
- break;
1821
- case "divide":
1822
- if (b === 0) throw new Error("Cannot divide by zero");
1823
- result = a / b;
1824
- break;
1825
- default:
1826
- throw new Error("Invalid operation");
1827
- }
1870
+ ## Adding New Services
1828
1871
 
1829
- return {
1830
- content: [{
1831
- type: "text" as const,
1832
- text: JSON.stringify({
1833
- operation: input.operation || "add",
1834
- operands: { a: input.a, b: input.b },
1835
- result
1836
- }, null, 2)
1837
- }]
1838
- };
1839
- }
1872
+ Create a new service directory in \`mcp/\`:
1840
1873
 
1841
- @Tool({
1842
- description: "Echo a message back",
1843
- inputClass: EchoInput
1844
- })
1845
- async echo(input: EchoInput) {
1846
- return {
1847
- content: [{
1848
- type: "text" as const,
1849
- text: JSON.stringify({
1850
- echoed: input.message,
1851
- timestamp: new Date().toISOString()
1852
- }, null, 2)
1853
- }]
1854
- };
1855
- }
1874
+ \`\`\`typescript
1875
+ // mcp/myservice/index.ts
1876
+ import { Tool, SchemaConstraint } from "@leanmcp/core";
1856
1877
 
1857
- @Resource({ description: "Get server information" })
1858
- async serverInfo() {
1859
- return {
1860
- contents: [{
1861
- uri: "server://info",
1862
- mimeType: "application/json",
1863
- text: JSON.stringify({
1864
- name: "${projectName}",
1865
- version: "1.0.0",
1866
- uptime: process.uptime()
1867
- }, null, 2)
1868
- }]
1869
- };
1870
- }
1878
+ // Define input schema
1879
+ class MyToolInput {
1880
+ @SchemaConstraint({
1881
+ description: "Message to process",
1882
+ minLength: 1
1883
+ })
1884
+ message!: string;
1885
+ }
1871
1886
 
1872
- @Prompt({ description: "Generate a greeting prompt" })
1873
- async greeting(args: { name?: string }) {
1874
- return {
1875
- messages: [{
1876
- role: "user" as const,
1877
- content: {
1878
- type: "text" as const,
1879
- text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
1880
- }
1887
+ export class MyService {
1888
+ @Tool({
1889
+ description: "My awesome tool",
1890
+ inputClass: MyToolInput
1891
+ })
1892
+ async myTool(input: MyToolInput) {
1893
+ return {
1894
+ content: [{
1895
+ type: "text",
1896
+ text: \`You said: \${input.message}\`
1881
1897
  }]
1882
1898
  };
1883
1899
  }
1884
1900
  }
1885
- `;
1886
- await fs7.writeFile(path7.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
1887
- const gitignore = `# Logs
1901
+ \`\`\`
1902
+
1903
+ Services are automatically discovered and registered - no need to modify \`main.ts\`!
1904
+
1905
+ ## Features
1906
+
1907
+ - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
1908
+ - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
1909
+ - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
1910
+ - **HTTP transport** - Production-ready HTTP server with session management
1911
+ - **Hot reload** - Development mode with automatic restart on file changes
1912
+
1913
+ ## Testing with MCP Inspector
1914
+
1915
+ \`\`\`bash
1916
+ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
1917
+ \`\`\`
1918
+
1919
+ ## License
1920
+
1921
+ MIT
1922
+ `, "getReadmeTemplate");
1923
+
1924
+ // src/templates/gitignore_v1.ts
1925
+ var gitignoreTemplate = `# Logs
1888
1926
  logs
1889
1927
  *.log
1890
1928
  npm-debug.log*
@@ -2026,111 +2064,333 @@ vite.config.js.timestamp-*
2026
2064
  vite.config.ts.timestamp-*
2027
2065
  .vite/
2028
2066
  `;
2029
- const env = `# Server Configuration
2030
- PORT=3001
2031
- NODE_ENV=development
2032
2067
 
2033
- # Add your environment variables here
2034
- `;
2035
- await fs7.writeFile(path7.join(targetDir, ".gitignore"), gitignore);
2036
- await fs7.writeFile(path7.join(targetDir, ".env"), env);
2037
- const readme = `# ${projectName}
2068
+ // src/templates/example_service_v1.ts
2069
+ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
2038
2070
 
2039
- MCP Server with Streamable HTTP Transport built with LeanMCP SDK
2071
+ /**
2072
+ * Example service demonstrating LeanMCP SDK decorators
2073
+ *
2074
+ * This is a simple example to get you started. Add your own tools, resources, and prompts here!
2075
+ */
2040
2076
 
2041
- ## Quick Start
2077
+ // Input schema with validation decorators
2078
+ class CalculateInput {
2079
+ @SchemaConstraint({ description: "First number" })
2080
+ a!: number;
2042
2081
 
2043
- \`\`\`bash
2044
- # Install dependencies
2045
- npm install
2082
+ @SchemaConstraint({ description: "Second number" })
2083
+ b!: number;
2046
2084
 
2047
- # Start development server (hot reload)
2048
- npm run dev
2085
+ @Optional()
2086
+ @SchemaConstraint({
2087
+ description: "Operation to perform",
2088
+ enum: ["add", "subtract", "multiply", "divide"],
2089
+ default: "add"
2090
+ })
2091
+ operation?: string;
2092
+ }
2049
2093
 
2050
- # Build for production
2051
- npm run build
2094
+ class EchoInput {
2095
+ @SchemaConstraint({
2096
+ description: "Message to echo back",
2097
+ minLength: 1
2098
+ })
2099
+ message!: string;
2100
+ }
2052
2101
 
2053
- # Run production server
2054
- npm start
2055
- \`\`\`
2102
+ export class ExampleService {
2103
+ @Tool({
2104
+ description: "Perform arithmetic operations with automatic schema validation",
2105
+ inputClass: CalculateInput
2106
+ })
2107
+ async calculate(input: CalculateInput) {
2108
+ // Ensure numerical operations by explicitly converting to numbers
2109
+ const a = Number(input.a);
2110
+ const b = Number(input.b);
2111
+ let result: number;
2056
2112
 
2057
- ## Project Structure
2113
+ switch (input.operation || "add") {
2114
+ case "add":
2115
+ result = a + b;
2116
+ break;
2117
+ case "subtract":
2118
+ result = a - b;
2119
+ break;
2120
+ case "multiply":
2121
+ result = a * b;
2122
+ break;
2123
+ case "divide":
2124
+ if (b === 0) throw new Error("Cannot divide by zero");
2125
+ result = a / b;
2126
+ break;
2127
+ default:
2128
+ throw new Error("Invalid operation");
2129
+ }
2058
2130
 
2059
- \`\`\`
2060
- ${projectName}/
2061
- \u251C\u2500\u2500 main.ts # Server entry point
2062
- \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
2063
- \u2502 \u2514\u2500\u2500 example/
2064
- \u2502 \u2514\u2500\u2500 index.ts # Example service
2065
- \u251C\u2500\u2500 .env # Environment variables
2066
- \u2514\u2500\u2500 package.json
2067
- \`\`\`
2131
+ return {
2132
+ content: [{
2133
+ type: "text" as const,
2134
+ text: JSON.stringify({
2135
+ operation: input.operation || "add",
2136
+ operands: { a: input.a, b: input.b },
2137
+ result
2138
+ }, null, 2)
2139
+ }]
2140
+ };
2141
+ }
2068
2142
 
2069
- ## Adding New Services
2143
+ @Tool({
2144
+ description: "Echo a message back",
2145
+ inputClass: EchoInput
2146
+ })
2147
+ async echo(input: EchoInput) {
2148
+ return {
2149
+ content: [{
2150
+ type: "text" as const,
2151
+ text: JSON.stringify({
2152
+ echoed: input.message,
2153
+ timestamp: new Date().toISOString()
2154
+ }, null, 2)
2155
+ }]
2156
+ };
2157
+ }
2070
2158
 
2071
- Create a new service directory in \`mcp/\`:
2159
+ @Resource({ description: "Get server information" })
2160
+ async serverInfo() {
2161
+ return {
2162
+ contents: [{
2163
+ uri: "server://info",
2164
+ mimeType: "application/json",
2165
+ text: JSON.stringify({
2166
+ name: "${projectName}",
2167
+ version: "1.0.0",
2168
+ uptime: process.uptime()
2169
+ }, null, 2)
2170
+ }]
2171
+ };
2172
+ }
2072
2173
 
2073
- \`\`\`typescript
2074
- // mcp/myservice/index.ts
2075
- import { Tool, SchemaConstraint } from "@leanmcp/core";
2174
+ @Prompt({ description: "Generate a greeting prompt" })
2175
+ async greeting(args: { name?: string }) {
2176
+ return {
2177
+ messages: [{
2178
+ role: "user" as const,
2179
+ content: {
2180
+ type: "text" as const,
2181
+ text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
2182
+ }
2183
+ }]
2184
+ };
2185
+ }
2186
+ }
2187
+ `, "getExampleServiceTemplate");
2076
2188
 
2077
- // Define input schema
2078
- class MyToolInput {
2079
- @SchemaConstraint({
2080
- description: "Message to process",
2189
+ // src/templates/main_ts_v1.ts
2190
+ var getMainTsTemplate = /* @__PURE__ */ __name((projectName, dashboardLine) => `import dotenv from "dotenv";
2191
+ import { createHTTPServer } from "@leanmcp/core";
2192
+
2193
+ // Load environment variables
2194
+ dotenv.config();
2195
+
2196
+ // Services are automatically discovered from ./mcp directory
2197
+ await createHTTPServer({
2198
+ name: "${projectName}",
2199
+ version: "1.0.0",
2200
+ port: 3001,
2201
+ cors: true,
2202
+ logging: true${dashboardLine}
2203
+ });
2204
+
2205
+ console.log("\\n${projectName} MCP Server");
2206
+ `, "getMainTsTemplate");
2207
+
2208
+ // src/templates/service_index_v1.ts
2209
+ var getServiceIndexTemplate = /* @__PURE__ */ __name((serviceName, capitalizedName) => `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
2210
+
2211
+ // Input schema for greeting
2212
+ class GreetInput {
2213
+ @SchemaConstraint({
2214
+ description: "Name to greet",
2081
2215
  minLength: 1
2082
2216
  })
2083
- message!: string;
2217
+ name!: string;
2084
2218
  }
2085
2219
 
2086
- export class MyService {
2220
+ /**
2221
+ * ${capitalizedName} Service
2222
+ *
2223
+ * This service demonstrates the three types of MCP primitives:
2224
+ * - Tools: Callable functions (like API endpoints)
2225
+ * - Prompts: Reusable prompt templates
2226
+ * - Resources: Data sources/endpoints
2227
+ */
2228
+ export class ${capitalizedName}Service {
2229
+ // TOOL - Callable function
2230
+ // Tool name: "greet" (from function name)
2087
2231
  @Tool({
2088
- description: "My awesome tool",
2089
- inputClass: MyToolInput
2232
+ description: "Greet a user by name",
2233
+ inputClass: GreetInput
2090
2234
  })
2091
- async myTool(input: MyToolInput) {
2235
+ greet(args: GreetInput) {
2236
+ return { message: \`Hello, \${args.name}! from ${serviceName}\` };
2237
+ }
2238
+
2239
+ // PROMPT - Prompt template
2240
+ // Prompt name: "welcomePrompt" (from function name)
2241
+ @Prompt({ description: "Welcome message prompt template" })
2242
+ welcomePrompt(args: { userName?: string }) {
2092
2243
  return {
2093
- content: [{
2094
- type: "text",
2095
- text: \`You said: \${input.message}\`
2096
- }]
2244
+ messages: [
2245
+ {
2246
+ role: "user",
2247
+ content: {
2248
+ type: "text",
2249
+ text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
2250
+ }
2251
+ }
2252
+ ]
2097
2253
  };
2098
2254
  }
2099
- }
2100
- \`\`\`
2101
-
2102
- Services are automatically discovered and registered - no need to modify \`main.ts\`!
2103
-
2104
- ## Features
2105
2255
 
2106
- - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
2107
- - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
2108
- - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
2109
- - **HTTP transport** - Production-ready HTTP server with session management
2110
- - **Hot reload** - Development mode with automatic restart on file changes
2111
-
2112
- ## Testing with MCP Inspector
2113
-
2114
- \`\`\`bash
2115
- npx @modelcontextprotocol/inspector http://localhost:3001/mcp
2116
- \`\`\`
2256
+ // RESOURCE - Data endpoint
2257
+ // Resource URI auto-generated from class and method name
2258
+ @Resource({ description: "${capitalizedName} service status" })
2259
+ getStatus() {
2260
+ return {
2261
+ service: "${serviceName}",
2262
+ status: "active",
2263
+ timestamp: new Date().toISOString()
2264
+ };
2265
+ }
2266
+ }
2267
+ `, "getServiceIndexTemplate");
2117
2268
 
2118
- ## License
2269
+ // src/index.ts
2270
+ var require2 = createRequire(import.meta.url);
2271
+ var pkg = require2("../package.json");
2272
+ function capitalize(str) {
2273
+ return str.charAt(0).toUpperCase() + str.slice(1);
2274
+ }
2275
+ __name(capitalize, "capitalize");
2276
+ var program = new Command();
2277
+ program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
2278
+ Examples:
2279
+ $ leanmcp create my-app # Create new project (interactive)
2280
+ $ leanmcp create my-app --install # Create and install deps (non-interactive)
2281
+ $ leanmcp create my-app --no-install # Create without installing deps
2282
+ $ leanmcp dev # Start development server
2283
+ $ leanmcp build # Build UI components and compile TypeScript
2284
+ $ leanmcp start # Build and start production server
2285
+ $ leanmcp login # Authenticate with LeanMCP cloud
2286
+ $ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
2287
+ $ leanmcp projects list # List your cloud projects
2288
+ $ leanmcp projects delete <id> # Delete a cloud project
2289
+ `);
2290
+ program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").option("--no-dashboard", "Disable dashboard UI at / and /mcp GET endpoints").option("--install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
2291
+ const spinner = ora7(`Creating project ${projectName}...`).start();
2292
+ const targetDir = path8.join(process.cwd(), projectName);
2293
+ if (fs8.existsSync(targetDir)) {
2294
+ spinner.fail(`Folder ${projectName} already exists.`);
2295
+ process.exit(1);
2296
+ }
2297
+ await fs8.mkdirp(targetDir);
2298
+ await fs8.mkdirp(path8.join(targetDir, "mcp", "example"));
2299
+ const pkg2 = {
2300
+ name: projectName,
2301
+ version: "1.0.0",
2302
+ description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
2303
+ main: "dist/main.js",
2304
+ type: "module",
2305
+ scripts: {
2306
+ dev: "leanmcp dev",
2307
+ build: "leanmcp build",
2308
+ start: "leanmcp start",
2309
+ "start:node": "node dist/main.js",
2310
+ clean: "rm -rf dist"
2311
+ },
2312
+ keywords: [
2313
+ "mcp",
2314
+ "model-context-protocol",
2315
+ "streamable-http",
2316
+ "leanmcp"
2317
+ ],
2318
+ author: "",
2319
+ license: "MIT",
2320
+ dependencies: {
2321
+ "@leanmcp/core": "^0.3.9",
2322
+ "@leanmcp/ui": "^0.2.1",
2323
+ "@leanmcp/auth": "^0.3.2",
2324
+ "dotenv": "^16.5.0"
2325
+ },
2326
+ devDependencies: {
2327
+ "@leanmcp/cli": "^0.3.0",
2328
+ "@types/node": "^20.0.0",
2329
+ "tsx": "^4.20.3",
2330
+ "typescript": "^5.6.3"
2331
+ }
2332
+ };
2333
+ await fs8.writeJSON(path8.join(targetDir, "package.json"), pkg2, {
2334
+ spaces: 2
2335
+ });
2336
+ const tsconfig = {
2337
+ compilerOptions: {
2338
+ module: "ESNext",
2339
+ target: "ES2022",
2340
+ moduleResolution: "Node",
2341
+ esModuleInterop: true,
2342
+ strict: true,
2343
+ skipLibCheck: true,
2344
+ outDir: "dist",
2345
+ experimentalDecorators: true,
2346
+ emitDecoratorMetadata: true
2347
+ },
2348
+ include: [
2349
+ "**/*.ts"
2350
+ ],
2351
+ exclude: [
2352
+ "node_modules",
2353
+ "dist"
2354
+ ]
2355
+ };
2356
+ await fs8.writeJSON(path8.join(targetDir, "tsconfig.json"), tsconfig, {
2357
+ spaces: 2
2358
+ });
2359
+ const dashboardLine = options.dashboard === false ? `
2360
+ dashboard: false, // Dashboard disabled via --no-dashboard` : "";
2361
+ const mainTs = getMainTsTemplate(projectName, dashboardLine);
2362
+ await fs8.writeFile(path8.join(targetDir, "main.ts"), mainTs);
2363
+ const exampleServiceTs = getExampleServiceTemplate(projectName);
2364
+ await fs8.writeFile(path8.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
2365
+ const gitignore = gitignoreTemplate;
2366
+ const env = `# Server Configuration
2367
+ PORT=3001
2368
+ NODE_ENV=development
2119
2369
 
2120
- MIT
2370
+ # Add your environment variables here
2121
2371
  `;
2122
- await fs7.writeFile(path7.join(targetDir, "README.md"), readme);
2372
+ await fs8.writeFile(path8.join(targetDir, ".gitignore"), gitignore);
2373
+ await fs8.writeFile(path8.join(targetDir, ".env"), env);
2374
+ const readme = getReadmeTemplate(projectName);
2375
+ await fs8.writeFile(path8.join(targetDir, "README.md"), readme);
2123
2376
  spinner.succeed(`Project ${projectName} created!`);
2124
- console.log(chalk6.green("\nSuccess! Your MCP server is ready.\n"));
2125
- console.log(chalk6.cyan(`Next, navigate to your project:
2126
- cd ${projectName}
2377
+ console.log(chalk7.green("\nSuccess! Your MCP server is ready.\n"));
2378
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2379
+ console.log(chalk7.gray(` cd ${projectName}`));
2380
+ console.log(chalk7.gray(` leanmcp deploy .
2127
2381
  `));
2382
+ console.log(chalk7.cyan("Need help? Join our Discord:"));
2383
+ console.log(chalk7.blue(" https://discord.com/invite/DsRcA3GwPy\n"));
2128
2384
  const isNonInteractive = options.install !== void 0 || options.allowAll;
2129
2385
  if (options.install === false) {
2130
- console.log(chalk6.cyan("\nTo get started:"));
2131
- console.log(chalk6.gray(` cd ${projectName}`));
2132
- console.log(chalk6.gray(` npm install`));
2133
- console.log(chalk6.gray(` npm run dev`));
2386
+ console.log(chalk7.cyan("To get started:"));
2387
+ console.log(chalk7.gray(` cd ${projectName}`));
2388
+ console.log(chalk7.gray(` npm install`));
2389
+ console.log(chalk7.gray(` npm run dev`));
2390
+ console.log();
2391
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2392
+ console.log(chalk7.gray(` cd ${projectName}`));
2393
+ console.log(chalk7.gray(` leanmcp deploy .`));
2134
2394
  return;
2135
2395
  }
2136
2396
  const shouldInstall = isNonInteractive ? true : await confirm3({
@@ -2138,10 +2398,10 @@ MIT
2138
2398
  default: true
2139
2399
  });
2140
2400
  if (shouldInstall) {
2141
- const installSpinner = ora6("Installing dependencies...").start();
2401
+ const installSpinner = ora7("Installing dependencies...").start();
2142
2402
  try {
2143
2403
  await new Promise((resolve, reject) => {
2144
- const npmInstall = spawn3("npm", [
2404
+ const npmInstall = spawn4("npm", [
2145
2405
  "install"
2146
2406
  ], {
2147
2407
  cwd: targetDir,
@@ -2159,9 +2419,13 @@ MIT
2159
2419
  });
2160
2420
  installSpinner.succeed("Dependencies installed successfully!");
2161
2421
  if (options.install === true) {
2162
- console.log(chalk6.cyan("\nTo start the development server:"));
2163
- console.log(chalk6.gray(` cd ${projectName}`));
2164
- console.log(chalk6.gray(` npm run dev`));
2422
+ console.log(chalk7.cyan("\nTo start the development server:"));
2423
+ console.log(chalk7.gray(` cd ${projectName}`));
2424
+ console.log(chalk7.gray(` npm run dev`));
2425
+ console.log();
2426
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2427
+ console.log(chalk7.gray(` cd ${projectName}`));
2428
+ console.log(chalk7.gray(` leanmcp deploy .`));
2165
2429
  return;
2166
2430
  }
2167
2431
  const shouldStartDev = options.allowAll ? true : await confirm3({
@@ -2169,8 +2433,8 @@ MIT
2169
2433
  default: true
2170
2434
  });
2171
2435
  if (shouldStartDev) {
2172
- console.log(chalk6.cyan("\nStarting development server...\n"));
2173
- const devServer = spawn3("npm", [
2436
+ console.log(chalk7.cyan("\nStarting development server...\n"));
2437
+ const devServer = spawn4("npm", [
2174
2438
  "run",
2175
2439
  "dev"
2176
2440
  ], {
@@ -2183,106 +2447,57 @@ MIT
2183
2447
  process.exit(0);
2184
2448
  });
2185
2449
  } else {
2186
- console.log(chalk6.cyan("\nTo start the development server later:"));
2187
- console.log(chalk6.gray(` cd ${projectName}`));
2188
- console.log(chalk6.gray(` npm run dev`));
2450
+ console.log(chalk7.cyan("\nTo start the development server later:"));
2451
+ console.log(chalk7.gray(` cd ${projectName}`));
2452
+ console.log(chalk7.gray(` npm run dev`));
2453
+ console.log();
2454
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2455
+ console.log(chalk7.gray(` cd ${projectName}`));
2456
+ console.log(chalk7.gray(` leanmcp deploy .`));
2189
2457
  }
2190
2458
  } catch (error) {
2191
2459
  installSpinner.fail("Failed to install dependencies");
2192
- console.error(chalk6.red(error instanceof Error ? error.message : String(error)));
2193
- console.log(chalk6.cyan("\nYou can install dependencies manually:"));
2194
- console.log(chalk6.gray(` cd ${projectName}`));
2195
- console.log(chalk6.gray(` npm install`));
2460
+ console.error(chalk7.red(error instanceof Error ? error.message : String(error)));
2461
+ console.log(chalk7.cyan("\nYou can install dependencies manually:"));
2462
+ console.log(chalk7.gray(` cd ${projectName}`));
2463
+ console.log(chalk7.gray(` npm install`));
2196
2464
  }
2197
2465
  } else {
2198
- console.log(chalk6.cyan("\nTo get started:"));
2199
- console.log(chalk6.gray(` cd ${projectName}`));
2200
- console.log(chalk6.gray(` npm install`));
2201
- console.log(chalk6.gray(` npm run dev`));
2466
+ console.log(chalk7.cyan("\nTo get started:"));
2467
+ console.log(chalk7.gray(` cd ${projectName}`));
2468
+ console.log(chalk7.gray(` npm install`));
2469
+ console.log(chalk7.gray(` npm run dev`));
2470
+ console.log();
2471
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2472
+ console.log(chalk7.gray(` cd ${projectName}`));
2473
+ console.log(chalk7.gray(` leanmcp deploy .`));
2202
2474
  }
2203
2475
  });
2204
2476
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
2205
2477
  const cwd = process.cwd();
2206
- const mcpDir = path7.join(cwd, "mcp");
2207
- if (!fs7.existsSync(path7.join(cwd, "main.ts"))) {
2208
- console.error(chalk6.red("ERROR: Not a LeanMCP project (main.ts missing)."));
2478
+ const mcpDir = path8.join(cwd, "mcp");
2479
+ if (!fs8.existsSync(path8.join(cwd, "main.ts"))) {
2480
+ console.error(chalk7.red("ERROR: Not a LeanMCP project (main.ts missing)."));
2209
2481
  process.exit(1);
2210
2482
  }
2211
- const serviceDir = path7.join(mcpDir, serviceName);
2212
- const serviceFile = path7.join(serviceDir, "index.ts");
2213
- if (fs7.existsSync(serviceDir)) {
2214
- console.error(chalk6.red(`ERROR: Service ${serviceName} already exists.`));
2483
+ const serviceDir = path8.join(mcpDir, serviceName);
2484
+ const serviceFile = path8.join(serviceDir, "index.ts");
2485
+ if (fs8.existsSync(serviceDir)) {
2486
+ console.error(chalk7.red(`ERROR: Service ${serviceName} already exists.`));
2215
2487
  process.exit(1);
2216
2488
  }
2217
- await fs7.mkdirp(serviceDir);
2218
- const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
2219
-
2220
- // Input schema for greeting
2221
- class GreetInput {
2222
- @SchemaConstraint({
2223
- description: "Name to greet",
2224
- minLength: 1
2225
- })
2226
- name!: string;
2227
- }
2228
-
2229
- /**
2230
- * ${capitalize(serviceName)} Service
2231
- *
2232
- * This service demonstrates the three types of MCP primitives:
2233
- * - Tools: Callable functions (like API endpoints)
2234
- * - Prompts: Reusable prompt templates
2235
- * - Resources: Data sources/endpoints
2236
- */
2237
- export class ${capitalize(serviceName)}Service {
2238
- // TOOL - Callable function
2239
- // Tool name: "greet" (from function name)
2240
- @Tool({
2241
- description: "Greet a user by name",
2242
- inputClass: GreetInput
2243
- })
2244
- greet(args: GreetInput) {
2245
- return { message: \`Hello, \${args.name}! from ${serviceName}\` };
2246
- }
2247
-
2248
- // PROMPT - Prompt template
2249
- // Prompt name: "welcomePrompt" (from function name)
2250
- @Prompt({ description: "Welcome message prompt template" })
2251
- welcomePrompt(args: { userName?: string }) {
2252
- return {
2253
- messages: [
2254
- {
2255
- role: "user",
2256
- content: {
2257
- type: "text",
2258
- text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
2259
- }
2260
- }
2261
- ]
2262
- };
2263
- }
2264
-
2265
- // RESOURCE - Data endpoint
2266
- // Resource URI auto-generated from class and method name
2267
- @Resource({ description: "${capitalize(serviceName)} service status" })
2268
- getStatus() {
2269
- return {
2270
- service: "${serviceName}",
2271
- status: "active",
2272
- timestamp: new Date().toISOString()
2273
- };
2274
- }
2275
- }
2276
- `;
2277
- await fs7.writeFile(serviceFile, indexTs);
2278
- console.log(chalk6.green(`\\nCreated new service: ${chalk6.bold(serviceName)}`));
2279
- console.log(chalk6.gray(` File: mcp/${serviceName}/index.ts`));
2280
- console.log(chalk6.gray(` Tool: greet`));
2281
- console.log(chalk6.gray(` Prompt: welcomePrompt`));
2282
- console.log(chalk6.gray(` Resource: getStatus`));
2283
- console.log(chalk6.green(`\\nService will be automatically discovered on next server start!`));
2489
+ await fs8.mkdirp(serviceDir);
2490
+ const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
2491
+ await fs8.writeFile(serviceFile, indexTs);
2492
+ console.log(chalk7.green(`\\nCreated new service: ${chalk7.bold(serviceName)}`));
2493
+ console.log(chalk7.gray(` File: mcp/${serviceName}/index.ts`));
2494
+ console.log(chalk7.gray(` Tool: greet`));
2495
+ console.log(chalk7.gray(` Prompt: welcomePrompt`));
2496
+ console.log(chalk7.gray(` Resource: getStatus`));
2497
+ console.log(chalk7.green(`\\nService will be automatically discovered on next server start!`));
2284
2498
  });
2285
2499
  program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(devCommand);
2500
+ program.command("build").description("Build UI components and compile TypeScript for production").action(buildCommand);
2286
2501
  program.command("start").description("Build UI components and start production server").action(startCommand);
2287
2502
  program.command("login").description("Authenticate with LeanMCP cloud using an API key").option("--debug", "Enable debug logging").action(async (options) => {
2288
2503
  if (options.debug) {