@leanmcp/cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +641 -520
  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";
@@ -573,20 +573,20 @@ async function devCommand() {
573
573
  }
574
574
  __name(devCommand, "devCommand");
575
575
 
576
- // src/commands/start.ts
576
+ // src/commands/build.ts
577
577
  import { spawn as spawn2 } from "child_process";
578
578
  import chalk2 from "chalk";
579
579
  import ora2 from "ora";
580
580
  import path4 from "path";
581
581
  import fs4 from "fs-extra";
582
- async function startCommand() {
582
+ async function buildCommand() {
583
583
  const cwd = process.cwd();
584
584
  if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
585
585
  console.error(chalk2.red("ERROR: Not a LeanMCP project (main.ts not found)."));
586
586
  console.error(chalk2.gray("Run this command from your project root."));
587
587
  process.exit(1);
588
588
  }
589
- console.log(chalk2.cyan("\n\u{1F680} LeanMCP Production Build\n"));
589
+ console.log(chalk2.cyan("\n\u{1F528} LeanMCP Build\n"));
590
590
  const scanSpinner = ora2("Scanning for @UIApp components...").start();
591
591
  const uiApps = await scanUIApp(cwd);
592
592
  if (uiApps.length === 0) {
@@ -642,8 +642,83 @@ async function startCommand() {
642
642
  console.error(chalk2.red(error instanceof Error ? error.message : String(error)));
643
643
  process.exit(1);
644
644
  }
645
- console.log(chalk2.cyan("\nStarting production server...\n"));
646
- const server = spawn2("node", [
645
+ console.log(chalk2.green("\nBuild complete!"));
646
+ console.log(chalk2.gray("\nTo start the server:"));
647
+ console.log(chalk2.cyan(" npm run start:node\n"));
648
+ }
649
+ __name(buildCommand, "buildCommand");
650
+
651
+ // src/commands/start.ts
652
+ import { spawn as spawn3 } from "child_process";
653
+ import chalk3 from "chalk";
654
+ import ora3 from "ora";
655
+ import path5 from "path";
656
+ import fs5 from "fs-extra";
657
+ async function startCommand() {
658
+ const cwd = process.cwd();
659
+ if (!await fs5.pathExists(path5.join(cwd, "main.ts"))) {
660
+ console.error(chalk3.red("ERROR: Not a LeanMCP project (main.ts not found)."));
661
+ console.error(chalk3.gray("Run this command from your project root."));
662
+ process.exit(1);
663
+ }
664
+ console.log(chalk3.cyan("\n\u{1F680} LeanMCP Production Build\n"));
665
+ const scanSpinner = ora3("Scanning for @UIApp components...").start();
666
+ const uiApps = await scanUIApp(cwd);
667
+ if (uiApps.length === 0) {
668
+ scanSpinner.succeed("No @UIApp components found");
669
+ } else {
670
+ scanSpinner.succeed(`Found ${uiApps.length} @UIApp component(s)`);
671
+ }
672
+ const manifest = {};
673
+ if (uiApps.length > 0) {
674
+ const buildSpinner = ora3("Building UI components...").start();
675
+ const errors = [];
676
+ for (const app of uiApps) {
677
+ const result = await buildUIComponent(app, cwd, false);
678
+ if (result.success) {
679
+ manifest[app.resourceUri] = result.htmlPath;
680
+ } else {
681
+ errors.push(`${app.componentName}: ${result.error}`);
682
+ }
683
+ }
684
+ await writeUIManifest(manifest, cwd);
685
+ if (errors.length > 0) {
686
+ buildSpinner.fail("Build failed");
687
+ for (const error of errors) {
688
+ console.error(chalk3.red(` \u2717 ${error}`));
689
+ }
690
+ process.exit(1);
691
+ }
692
+ buildSpinner.succeed("UI components built");
693
+ }
694
+ const tscSpinner = ora3("Compiling TypeScript...").start();
695
+ try {
696
+ await new Promise((resolve, reject) => {
697
+ const tsc = spawn3("npx", [
698
+ "tsc"
699
+ ], {
700
+ cwd,
701
+ stdio: "pipe",
702
+ shell: true
703
+ });
704
+ let stderr = "";
705
+ tsc.stderr?.on("data", (data) => {
706
+ stderr += data;
707
+ });
708
+ tsc.on("close", (code) => {
709
+ if (code === 0) resolve();
710
+ else reject(new Error(stderr || `tsc exited with code ${code}`));
711
+ });
712
+ tsc.on("error", reject);
713
+ });
714
+ tscSpinner.succeed("TypeScript compiled");
715
+ } catch (error) {
716
+ tscSpinner.fail("TypeScript compilation failed");
717
+ console.error(chalk3.red(error instanceof Error ? error.message : String(error)));
718
+ process.exit(1);
719
+ }
720
+ console.log(chalk3.cyan("\nStarting production server...\n"));
721
+ const server = spawn3("node", [
647
722
  "dist/main.js"
648
723
  ], {
649
724
  cwd,
@@ -654,7 +729,7 @@ async function startCommand() {
654
729
  const cleanup = /* @__PURE__ */ __name(() => {
655
730
  if (isCleaningUp) return;
656
731
  isCleaningUp = true;
657
- console.log(chalk2.gray("\nShutting down..."));
732
+ console.log(chalk3.gray("\nShutting down..."));
658
733
  server.kill("SIGTERM");
659
734
  }, "cleanup");
660
735
  process.on("SIGINT", cleanup);
@@ -666,10 +741,10 @@ async function startCommand() {
666
741
  __name(startCommand, "startCommand");
667
742
 
668
743
  // 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";
744
+ import chalk4 from "chalk";
745
+ import ora4 from "ora";
746
+ import path6 from "path";
747
+ import fs6 from "fs-extra";
673
748
  import os from "os";
674
749
  import { input, confirm } from "@inquirer/prompts";
675
750
  var DEBUG_MODE = false;
@@ -679,16 +754,16 @@ function setDebugMode(enabled) {
679
754
  __name(setDebugMode, "setDebugMode");
680
755
  function debug(message, ...args) {
681
756
  if (DEBUG_MODE) {
682
- console.log(chalk3.gray(`[DEBUG] ${message}`), ...args);
757
+ console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
683
758
  }
684
759
  }
685
760
  __name(debug, "debug");
686
- var CONFIG_DIR = path5.join(os.homedir(), ".leanmcp");
687
- var CONFIG_FILE = path5.join(CONFIG_DIR, "config.json");
761
+ var CONFIG_DIR = path6.join(os.homedir(), ".leanmcp");
762
+ var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
688
763
  async function loadConfig() {
689
764
  try {
690
- if (await fs5.pathExists(CONFIG_FILE)) {
691
- return await fs5.readJSON(CONFIG_FILE);
765
+ if (await fs6.pathExists(CONFIG_FILE)) {
766
+ return await fs6.readJSON(CONFIG_FILE);
692
767
  }
693
768
  } catch (error) {
694
769
  }
@@ -696,8 +771,8 @@ async function loadConfig() {
696
771
  }
697
772
  __name(loadConfig, "loadConfig");
698
773
  async function saveConfig(config) {
699
- await fs5.ensureDir(CONFIG_DIR);
700
- await fs5.writeJSON(CONFIG_FILE, config, {
774
+ await fs6.ensureDir(CONFIG_DIR);
775
+ await fs6.writeJSON(CONFIG_FILE, config, {
701
776
  spaces: 2
702
777
  });
703
778
  }
@@ -735,24 +810,24 @@ Allowed URLs:
735
810
  }
736
811
  __name(getApiUrl, "getApiUrl");
737
812
  async function loginCommand() {
738
- console.log(chalk3.cyan("\nLeanMCP Login\n"));
813
+ console.log(chalk4.cyan("\nLeanMCP Login\n"));
739
814
  const existingConfig = await loadConfig();
740
815
  if (existingConfig.apiKey) {
741
- console.log(chalk3.yellow("You are already logged in."));
816
+ console.log(chalk4.yellow("You are already logged in."));
742
817
  const shouldRelogin = await confirm({
743
818
  message: "Do you want to replace the existing API key?",
744
819
  default: false
745
820
  });
746
821
  if (!shouldRelogin) {
747
- console.log(chalk3.gray("\nLogin cancelled. Existing API key preserved."));
822
+ console.log(chalk4.gray("\nLogin cancelled. Existing API key preserved."));
748
823
  return;
749
824
  }
750
825
  }
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"));
826
+ console.log(chalk4.white("To authenticate, you need an API key from LeanMCP.\n"));
827
+ console.log(chalk4.cyan("Steps:"));
828
+ console.log(chalk4.gray(" 1. Go to: ") + chalk4.blue.underline("https://ship.leanmcp.com/api-keys"));
829
+ console.log(chalk4.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
830
+ console.log(chalk4.gray(" 3. Copy the API key and paste it below\n"));
756
831
  const apiKey = await input({
757
832
  message: "Enter your API key:",
758
833
  validate: /* @__PURE__ */ __name((value) => {
@@ -765,7 +840,7 @@ async function loginCommand() {
765
840
  return true;
766
841
  }, "validate")
767
842
  });
768
- const spinner = ora3("Validating API key...").start();
843
+ const spinner = ora4("Validating API key...").start();
769
844
  try {
770
845
  const apiUrl = await getApiUrl();
771
846
  const validateUrl = `${apiUrl}/api-keys/validate`;
@@ -785,10 +860,10 @@ async function loginCommand() {
785
860
  const errorText = await response.text();
786
861
  debug("Error response:", errorText);
787
862
  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."));
863
+ console.error(chalk4.red("\nThe API key is invalid or has expired."));
864
+ console.log(chalk4.gray("Please check your API key and try again."));
790
865
  if (DEBUG_MODE) {
791
- console.log(chalk3.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
866
+ console.log(chalk4.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
792
867
  }
793
868
  process.exit(1);
794
869
  }
@@ -798,24 +873,24 @@ async function loginCommand() {
798
873
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
799
874
  });
800
875
  spinner.succeed("API key validated and saved");
801
- console.log(chalk3.green("\nLogin successful!"));
802
- console.log(chalk3.gray(` Config saved to: ${CONFIG_FILE}
876
+ console.log(chalk4.green("\nLogin successful!"));
877
+ console.log(chalk4.gray(` Config saved to: ${CONFIG_FILE}
803
878
  `));
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"));
879
+ console.log(chalk4.cyan("You can now use:"));
880
+ console.log(chalk4.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
881
+ console.log(chalk4.gray(" leanmcp logout - Remove your API key"));
807
882
  } catch (error) {
808
883
  spinner.fail("Failed to validate API key");
809
884
  debug("Error:", error);
810
885
  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."));
886
+ console.error(chalk4.red("\nCould not connect to LeanMCP servers."));
887
+ console.log(chalk4.gray("Please check your internet connection and try again."));
813
888
  } else {
814
- console.error(chalk3.red(`
889
+ console.error(chalk4.red(`
815
890
  Error: ${error instanceof Error ? error.message : String(error)}`));
816
891
  }
817
892
  if (DEBUG_MODE) {
818
- console.log(chalk3.gray(`
893
+ console.log(chalk4.gray(`
819
894
  Debug: Full error: ${error}`));
820
895
  }
821
896
  process.exit(1);
@@ -823,10 +898,10 @@ Debug: Full error: ${error}`));
823
898
  }
824
899
  __name(loginCommand, "loginCommand");
825
900
  async function logoutCommand() {
826
- console.log(chalk3.cyan("\nLeanMCP Logout\n"));
901
+ console.log(chalk4.cyan("\nLeanMCP Logout\n"));
827
902
  const config = await loadConfig();
828
903
  if (!config.apiKey) {
829
- console.log(chalk3.yellow("You are not currently logged in."));
904
+ console.log(chalk4.yellow("You are not currently logged in."));
830
905
  return;
831
906
  }
832
907
  const shouldLogout = await confirm({
@@ -834,7 +909,7 @@ async function logoutCommand() {
834
909
  default: false
835
910
  });
836
911
  if (!shouldLogout) {
837
- console.log(chalk3.gray("\nLogout cancelled."));
912
+ console.log(chalk4.gray("\nLogout cancelled."));
838
913
  return;
839
914
  }
840
915
  await saveConfig({
@@ -842,33 +917,33 @@ async function logoutCommand() {
842
917
  apiKey: void 0,
843
918
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
844
919
  });
845
- console.log(chalk3.green("\nLogged out successfully!"));
846
- console.log(chalk3.gray(` API key removed from: ${CONFIG_FILE}`));
920
+ console.log(chalk4.green("\nLogged out successfully!"));
921
+ console.log(chalk4.gray(` API key removed from: ${CONFIG_FILE}`));
847
922
  }
848
923
  __name(logoutCommand, "logoutCommand");
849
924
  async function whoamiCommand() {
850
925
  const config = await loadConfig();
851
926
  if (!config.apiKey) {
852
- console.log(chalk3.yellow("\nYou are not logged in."));
853
- console.log(chalk3.gray("Run `leanmcp login` to authenticate.\n"));
927
+ console.log(chalk4.yellow("\nYou are not logged in."));
928
+ console.log(chalk4.gray("Run `leanmcp login` to authenticate.\n"));
854
929
  return;
855
930
  }
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"}`));
931
+ console.log(chalk4.cyan("\nLeanMCP Authentication Status\n"));
932
+ console.log(chalk4.green("Logged in"));
933
+ console.log(chalk4.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
934
+ console.log(chalk4.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
860
935
  if (config.lastUpdated) {
861
- console.log(chalk3.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
936
+ console.log(chalk4.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
862
937
  }
863
938
  console.log();
864
939
  }
865
940
  __name(whoamiCommand, "whoamiCommand");
866
941
 
867
942
  // 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";
943
+ import chalk5 from "chalk";
944
+ import ora5 from "ora";
945
+ import path7 from "path";
946
+ import fs7 from "fs-extra";
872
947
  import os2 from "os";
873
948
  import archiver from "archiver";
874
949
  import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
@@ -1058,7 +1133,7 @@ function setDeployDebugMode(enabled) {
1058
1133
  __name(setDeployDebugMode, "setDeployDebugMode");
1059
1134
  function debug2(message, ...args) {
1060
1135
  if (DEBUG_MODE2) {
1061
- console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
1136
+ console.log(chalk5.gray(`[DEBUG] ${message}`), ...args);
1062
1137
  }
1063
1138
  }
1064
1139
  __name(debug2, "debug");
@@ -1095,7 +1170,7 @@ var API_ENDPOINTS = {
1095
1170
  };
1096
1171
  async function createZipArchive(folderPath, outputPath) {
1097
1172
  return new Promise((resolve, reject) => {
1098
- const output = fs6.createWriteStream(outputPath);
1173
+ const output = fs7.createWriteStream(outputPath);
1099
1174
  const archive = archiver("zip", {
1100
1175
  zlib: {
1101
1176
  level: 9
@@ -1182,26 +1257,26 @@ async function waitForDeployment(apiUrl, apiKey, deploymentId, spinner) {
1182
1257
  __name(waitForDeployment, "waitForDeployment");
1183
1258
  async function deployCommand(folderPath, options = {}) {
1184
1259
  const deployStartTime = Date.now();
1185
- console.log(chalk4.cyan("\nLeanMCP Deploy\n"));
1260
+ console.log(chalk5.cyan("\nLeanMCP Deploy\n"));
1186
1261
  debug2("Starting deployment...");
1187
1262
  const apiKey = await getApiKey();
1188
1263
  if (!apiKey) {
1189
- console.error(chalk4.red("Not logged in."));
1190
- console.log(chalk4.gray("Run `leanmcp login` first to authenticate.\n"));
1264
+ console.error(chalk5.red("Not logged in."));
1265
+ console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1191
1266
  process.exit(1);
1192
1267
  }
1193
1268
  const apiUrl = await getApiUrl();
1194
1269
  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}`));
1270
+ const absolutePath = path7.resolve(process.cwd(), folderPath);
1271
+ if (!await fs7.pathExists(absolutePath)) {
1272
+ console.error(chalk5.red(`Folder not found: ${absolutePath}`));
1198
1273
  process.exit(1);
1199
1274
  }
1200
- const hasMainTs = await fs6.pathExists(path6.join(absolutePath, "main.ts"));
1201
- const hasPackageJson = await fs6.pathExists(path6.join(absolutePath, "package.json"));
1275
+ const hasMainTs = await fs7.pathExists(path7.join(absolutePath, "main.ts"));
1276
+ const hasPackageJson = await fs7.pathExists(path7.join(absolutePath, "package.json"));
1202
1277
  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"));
1278
+ console.error(chalk5.red("Not a valid project folder."));
1279
+ console.log(chalk5.gray("Expected main.ts or package.json in the folder.\n"));
1205
1280
  process.exit(1);
1206
1281
  }
1207
1282
  debug2("Fetching existing projects...");
@@ -1222,17 +1297,17 @@ async function deployCommand(folderPath, options = {}) {
1222
1297
  let projectName;
1223
1298
  let existingProject = null;
1224
1299
  let isUpdate = false;
1225
- let folderName = path6.basename(absolutePath);
1300
+ let folderName = path7.basename(absolutePath);
1226
1301
  if (hasPackageJson) {
1227
1302
  try {
1228
- const pkg2 = await fs6.readJSON(path6.join(absolutePath, "package.json"));
1303
+ const pkg2 = await fs7.readJSON(path7.join(absolutePath, "package.json"));
1229
1304
  folderName = pkg2.name || folderName;
1230
1305
  } catch (e) {
1231
1306
  }
1232
1307
  }
1233
1308
  const matchingProject = existingProjects.find((p) => p.name === folderName);
1234
1309
  if (matchingProject) {
1235
- console.log(chalk4.yellow(`Project '${folderName}' already exists.
1310
+ console.log(chalk5.yellow(`Project '${folderName}' already exists.
1236
1311
  `));
1237
1312
  const choice = await select({
1238
1313
  message: "What would you like to do?",
@@ -1252,26 +1327,26 @@ async function deployCommand(folderPath, options = {}) {
1252
1327
  ]
1253
1328
  });
1254
1329
  if (choice === "cancel") {
1255
- console.log(chalk4.gray("\nDeployment cancelled.\n"));
1330
+ console.log(chalk5.gray("\nDeployment cancelled.\n"));
1256
1331
  return;
1257
1332
  }
1258
1333
  if (choice === "update") {
1259
1334
  existingProject = matchingProject;
1260
1335
  projectName = matchingProject.name;
1261
1336
  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"));
1337
+ console.log(chalk5.yellow("\nWARNING: This will replace the existing deployment."));
1338
+ console.log(chalk5.gray("The previous version will be overwritten.\n"));
1264
1339
  } else {
1265
1340
  projectName = generateProjectName();
1266
- console.log(chalk4.cyan(`
1267
- Generated project name: ${chalk4.bold(projectName)}
1341
+ console.log(chalk5.cyan(`
1342
+ Generated project name: ${chalk5.bold(projectName)}
1268
1343
  `));
1269
1344
  }
1270
1345
  } else {
1271
1346
  projectName = generateProjectName();
1272
- console.log(chalk4.cyan(`Generated project name: ${chalk4.bold(projectName)}`));
1347
+ console.log(chalk5.cyan(`Generated project name: ${chalk5.bold(projectName)}`));
1273
1348
  }
1274
- console.log(chalk4.gray(`Path: ${absolutePath}
1349
+ console.log(chalk5.gray(`Path: ${absolutePath}
1275
1350
  `));
1276
1351
  let subdomain = options.subdomain;
1277
1352
  if (!subdomain) {
@@ -1293,7 +1368,7 @@ Generated project name: ${chalk4.bold(projectName)}
1293
1368
  }, "validate")
1294
1369
  });
1295
1370
  }
1296
- const checkSpinner = ora4("Checking subdomain availability...").start();
1371
+ const checkSpinner = ora5("Checking subdomain availability...").start();
1297
1372
  try {
1298
1373
  debug2("Checking subdomain:", subdomain);
1299
1374
  const checkResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.checkSubdomain}/${subdomain}`, {
@@ -1305,7 +1380,7 @@ Generated project name: ${chalk4.bold(projectName)}
1305
1380
  const result = await checkResponse.json();
1306
1381
  if (!result.available) {
1307
1382
  checkSpinner.fail(`Subdomain '${subdomain}' is already taken`);
1308
- console.log(chalk4.gray("\nPlease choose a different subdomain.\n"));
1383
+ console.log(chalk5.gray("\nPlease choose a different subdomain.\n"));
1309
1384
  process.exit(1);
1310
1385
  }
1311
1386
  }
@@ -1314,17 +1389,17 @@ Generated project name: ${chalk4.bold(projectName)}
1314
1389
  checkSpinner.warn("Could not verify subdomain availability");
1315
1390
  }
1316
1391
  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
1392
+ console.log(chalk5.cyan("\nDeployment Details:"));
1393
+ console.log(chalk5.gray(` Project: ${projectName}`));
1394
+ console.log(chalk5.gray(` Subdomain: ${subdomain}`));
1395
+ console.log(chalk5.gray(` URL: https://${subdomain}.leanmcp.dev
1321
1396
  `));
1322
1397
  const shouldDeploy = await confirm2({
1323
1398
  message: "Proceed with deployment?",
1324
1399
  default: true
1325
1400
  });
1326
1401
  if (!shouldDeploy) {
1327
- console.log(chalk4.gray("\nDeployment cancelled.\n"));
1402
+ console.log(chalk5.gray("\nDeployment cancelled.\n"));
1328
1403
  return;
1329
1404
  }
1330
1405
  }
@@ -1332,9 +1407,9 @@ Generated project name: ${chalk4.bold(projectName)}
1332
1407
  let projectId;
1333
1408
  if (isUpdate && existingProject) {
1334
1409
  projectId = existingProject.id;
1335
- console.log(chalk4.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
1410
+ console.log(chalk5.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
1336
1411
  } else {
1337
- const projectSpinner = ora4("Creating project...").start();
1412
+ const projectSpinner = ora5("Creating project...").start();
1338
1413
  try {
1339
1414
  debug2("Step 1: Creating project:", projectName);
1340
1415
  const createResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
@@ -1356,14 +1431,14 @@ Generated project name: ${chalk4.bold(projectName)}
1356
1431
  projectSpinner.succeed(`Project created: ${projectId.substring(0, 8)}...`);
1357
1432
  } catch (error) {
1358
1433
  projectSpinner.fail("Failed to create project");
1359
- console.error(chalk4.red(`
1434
+ console.error(chalk5.red(`
1360
1435
  ${error instanceof Error ? error.message : String(error)}`));
1361
1436
  process.exit(1);
1362
1437
  }
1363
1438
  }
1364
- const uploadSpinner = ora4("Packaging and uploading...").start();
1439
+ const uploadSpinner = ora5("Packaging and uploading...").start();
1365
1440
  try {
1366
- const tempZip = path6.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
1441
+ const tempZip = path7.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
1367
1442
  const zipSize = await createZipArchive(absolutePath, tempZip);
1368
1443
  uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
1369
1444
  debug2("Step 2a: Getting upload URL for project:", projectId);
@@ -1390,7 +1465,7 @@ ${error instanceof Error ? error.message : String(error)}`));
1390
1465
  throw new Error("Backend did not return upload URL");
1391
1466
  }
1392
1467
  debug2("Step 2b: Uploading to S3...");
1393
- const zipBuffer = await fs6.readFile(tempZip);
1468
+ const zipBuffer = await fs7.readFile(tempZip);
1394
1469
  const s3Response = await fetch(uploadUrl, {
1395
1470
  method: "PUT",
1396
1471
  body: zipBuffer,
@@ -1413,15 +1488,15 @@ ${error instanceof Error ? error.message : String(error)}`));
1413
1488
  s3Location
1414
1489
  })
1415
1490
  });
1416
- await fs6.remove(tempZip);
1491
+ await fs7.remove(tempZip);
1417
1492
  uploadSpinner.succeed("Project uploaded");
1418
1493
  } catch (error) {
1419
1494
  uploadSpinner.fail("Failed to upload");
1420
- console.error(chalk4.red(`
1495
+ console.error(chalk5.red(`
1421
1496
  ${error instanceof Error ? error.message : String(error)}`));
1422
1497
  process.exit(1);
1423
1498
  }
1424
- const buildSpinner = ora4("Building...").start();
1499
+ const buildSpinner = ora5("Building...").start();
1425
1500
  const buildStartTime = Date.now();
1426
1501
  let buildId;
1427
1502
  let imageUri;
@@ -1445,11 +1520,11 @@ ${error instanceof Error ? error.message : String(error)}`));
1445
1520
  buildSpinner.succeed(`Build complete (${buildDuration}s)`);
1446
1521
  } catch (error) {
1447
1522
  buildSpinner.fail("Build failed");
1448
- console.error(chalk4.red(`
1523
+ console.error(chalk5.red(`
1449
1524
  ${error instanceof Error ? error.message : String(error)}`));
1450
1525
  process.exit(1);
1451
1526
  }
1452
- const deploySpinner = ora4("Deploying to LeanMCP...").start();
1527
+ const deploySpinner = ora5("Deploying to LeanMCP...").start();
1453
1528
  let deploymentId;
1454
1529
  let functionUrl;
1455
1530
  try {
@@ -1475,11 +1550,11 @@ ${error instanceof Error ? error.message : String(error)}`));
1475
1550
  deploySpinner.succeed("Deployed");
1476
1551
  } catch (error) {
1477
1552
  deploySpinner.fail("Deployment failed");
1478
- console.error(chalk4.red(`
1553
+ console.error(chalk5.red(`
1479
1554
  ${error instanceof Error ? error.message : String(error)}`));
1480
1555
  process.exit(1);
1481
1556
  }
1482
- const mappingSpinner = ora4("Configuring subdomain...").start();
1557
+ const mappingSpinner = ora5("Configuring subdomain...").start();
1483
1558
  try {
1484
1559
  debug2("Step 5: Creating subdomain mapping:", subdomain);
1485
1560
  const mappingResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createMapping}`, {
@@ -1503,38 +1578,43 @@ ${error instanceof Error ? error.message : String(error)}`));
1503
1578
  } catch (error) {
1504
1579
  mappingSpinner.warn("Subdomain mapping may need manual setup");
1505
1580
  }
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`));
1581
+ console.log(chalk5.green("\n" + "=".repeat(60)));
1582
+ console.log(chalk5.green.bold(" DEPLOYMENT SUCCESSFUL!"));
1583
+ console.log(chalk5.green("=".repeat(60) + "\n"));
1584
+ console.log(chalk5.white(" Your MCP server is now live:\n"));
1585
+ console.log(chalk5.cyan(` URL: `) + chalk5.white.bold(`https://${subdomain}.leanmcp.dev`));
1511
1586
  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`));
1587
+ console.log(chalk5.gray(" Test endpoints:"));
1588
+ console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/health`));
1589
+ console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
1515
1590
  console.log();
1516
1591
  const totalDuration = Math.round((Date.now() - deployStartTime) / 1e3);
1517
- console.log(chalk4.gray(` Total time: ${totalDuration}s`));
1592
+ console.log(chalk5.gray(` Total time: ${totalDuration}s`));
1518
1593
  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}`));
1594
+ const dashboardBaseUrl = "https://ship.leanmcp.com";
1595
+ console.log(chalk5.cyan(" Dashboard links:"));
1596
+ console.log(chalk5.gray(` Project: ${dashboardBaseUrl}/projects/${projectId}`));
1597
+ console.log(chalk5.gray(` Build: ${dashboardBaseUrl}/builds/${buildId}`));
1598
+ console.log(chalk5.gray(` Deployment: ${dashboardBaseUrl}/deployments/${deploymentId}`));
1599
+ console.log();
1600
+ console.log(chalk5.cyan(" Need help? Join our Discord:"));
1601
+ console.log(chalk5.blue(" https://discord.com/invite/DsRcA3GwPy"));
1522
1602
  console.log();
1523
1603
  }
1524
1604
  __name(deployCommand, "deployCommand");
1525
1605
 
1526
1606
  // src/commands/projects.ts
1527
- import chalk5 from "chalk";
1528
- import ora5 from "ora";
1607
+ import chalk6 from "chalk";
1608
+ import ora6 from "ora";
1529
1609
  var API_ENDPOINT = "/api/projects";
1530
1610
  async function projectsListCommand() {
1531
1611
  const apiKey = await getApiKey();
1532
1612
  if (!apiKey) {
1533
- console.error(chalk5.red("\nNot logged in."));
1534
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1613
+ console.error(chalk6.red("\nNot logged in."));
1614
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1535
1615
  process.exit(1);
1536
1616
  }
1537
- const spinner = ora5("Fetching projects...").start();
1617
+ const spinner = ora6("Fetching projects...").start();
1538
1618
  try {
1539
1619
  const apiUrl = await getApiUrl();
1540
1620
  const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
@@ -1548,25 +1628,25 @@ async function projectsListCommand() {
1548
1628
  const projects = await response.json();
1549
1629
  spinner.stop();
1550
1630
  if (projects.length === 0) {
1551
- console.log(chalk5.yellow("\nNo projects found."));
1552
- console.log(chalk5.gray("Create one with: leanmcp deploy <folder>\n"));
1631
+ console.log(chalk6.yellow("\nNo projects found."));
1632
+ console.log(chalk6.gray("Create one with: leanmcp deploy <folder>\n"));
1553
1633
  return;
1554
1634
  }
1555
- console.log(chalk5.cyan(`
1635
+ console.log(chalk6.cyan(`
1556
1636
  Your Projects (${projects.length})
1557
1637
  `));
1558
- console.log(chalk5.gray("\u2500".repeat(60)));
1638
+ console.log(chalk6.gray("\u2500".repeat(60)));
1559
1639
  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()}`));
1640
+ const statusColor = project.status === "ACTIVE" ? chalk6.green : chalk6.yellow;
1641
+ console.log(chalk6.white.bold(` ${project.name}`));
1642
+ console.log(chalk6.gray(` ID: ${project.id}`));
1643
+ console.log(chalk6.gray(` Status: `) + statusColor(project.status));
1644
+ console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
1565
1645
  console.log();
1566
1646
  }
1567
1647
  } catch (error) {
1568
1648
  spinner.fail("Failed to fetch projects");
1569
- console.error(chalk5.red(`
1649
+ console.error(chalk6.red(`
1570
1650
  ${error instanceof Error ? error.message : String(error)}`));
1571
1651
  process.exit(1);
1572
1652
  }
@@ -1575,11 +1655,11 @@ __name(projectsListCommand, "projectsListCommand");
1575
1655
  async function projectsGetCommand(projectId) {
1576
1656
  const apiKey = await getApiKey();
1577
1657
  if (!apiKey) {
1578
- console.error(chalk5.red("\nNot logged in."));
1579
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1658
+ console.error(chalk6.red("\nNot logged in."));
1659
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1580
1660
  process.exit(1);
1581
1661
  }
1582
- const spinner = ora5("Fetching project...").start();
1662
+ const spinner = ora6("Fetching project...").start();
1583
1663
  try {
1584
1664
  const apiUrl = await getApiUrl();
1585
1665
  const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
@@ -1595,22 +1675,22 @@ async function projectsGetCommand(projectId) {
1595
1675
  }
1596
1676
  const project = await response.json();
1597
1677
  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}`));
1678
+ console.log(chalk6.cyan("\nProject Details\n"));
1679
+ console.log(chalk6.gray("\u2500".repeat(60)));
1680
+ console.log(chalk6.white.bold(` Name: ${project.name}`));
1681
+ console.log(chalk6.gray(` ID: ${project.id}`));
1682
+ console.log(chalk6.gray(` Status: ${project.status}`));
1603
1683
  if (project.s3Location) {
1604
- console.log(chalk5.gray(` S3 Location: ${project.s3Location}`));
1684
+ console.log(chalk6.gray(` S3 Location: ${project.s3Location}`));
1605
1685
  }
1606
- console.log(chalk5.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
1686
+ console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
1607
1687
  if (project.updatedAt) {
1608
- console.log(chalk5.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
1688
+ console.log(chalk6.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
1609
1689
  }
1610
1690
  console.log();
1611
1691
  } catch (error) {
1612
1692
  spinner.fail("Failed to fetch project");
1613
- console.error(chalk5.red(`
1693
+ console.error(chalk6.red(`
1614
1694
  ${error instanceof Error ? error.message : String(error)}`));
1615
1695
  process.exit(1);
1616
1696
  }
@@ -1619,8 +1699,8 @@ __name(projectsGetCommand, "projectsGetCommand");
1619
1699
  async function projectsDeleteCommand(projectId, options = {}) {
1620
1700
  const apiKey = await getApiKey();
1621
1701
  if (!apiKey) {
1622
- console.error(chalk5.red("\nNot logged in."));
1623
- console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
1702
+ console.error(chalk6.red("\nNot logged in."));
1703
+ console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
1624
1704
  process.exit(1);
1625
1705
  }
1626
1706
  if (!options.force) {
@@ -1630,11 +1710,11 @@ async function projectsDeleteCommand(projectId, options = {}) {
1630
1710
  default: false
1631
1711
  });
1632
1712
  if (!shouldDelete) {
1633
- console.log(chalk5.gray("\nDeletion cancelled.\n"));
1713
+ console.log(chalk6.gray("\nDeletion cancelled.\n"));
1634
1714
  return;
1635
1715
  }
1636
1716
  }
1637
- const spinner = ora5("Deleting project...").start();
1717
+ const spinner = ora6("Deleting project...").start();
1638
1718
  try {
1639
1719
  const apiUrl = await getApiUrl();
1640
1720
  const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
@@ -1653,238 +1733,102 @@ async function projectsDeleteCommand(projectId, options = {}) {
1653
1733
  console.log();
1654
1734
  } catch (error) {
1655
1735
  spinner.fail("Failed to delete project");
1656
- console.error(chalk5.red(`
1736
+ console.error(chalk6.red(`
1657
1737
  ${error instanceof Error ? error.message : String(error)}`));
1658
1738
  process.exit(1);
1659
1739
  }
1660
1740
  }
1661
1741
  __name(projectsDeleteCommand, "projectsDeleteCommand");
1662
1742
 
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";
1743
+ // src/templates/readme_v1.ts
1744
+ var getReadmeTemplate = /* @__PURE__ */ __name((projectName) => `# ${projectName}
1751
1745
 
1752
- // Load environment variables
1753
- dotenv.config();
1746
+ MCP Server with Streamable HTTP Transport built with LeanMCP SDK
1754
1747
 
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
- });
1748
+ ## Quick Start
1763
1749
 
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";
1750
+ \`\`\`bash
1751
+ # Install dependencies
1752
+ npm install
1768
1753
 
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
- */
1754
+ # Start development server (hot reload)
1755
+ npm run dev
1774
1756
 
1775
- // Input schema with validation decorators
1776
- class CalculateInput {
1777
- @SchemaConstraint({ description: "First number" })
1778
- a!: number;
1757
+ # Build for production
1758
+ npm run build
1779
1759
 
1780
- @SchemaConstraint({ description: "Second number" })
1781
- b!: number;
1760
+ # Run production server
1761
+ npm start
1762
+ \`\`\`
1782
1763
 
1783
- @Optional()
1784
- @SchemaConstraint({
1785
- description: "Operation to perform",
1786
- enum: ["add", "subtract", "multiply", "divide"],
1787
- default: "add"
1788
- })
1789
- operation?: string;
1790
- }
1764
+ ## Project Structure
1791
1765
 
1792
- class EchoInput {
1793
- @SchemaConstraint({
1794
- description: "Message to echo back",
1795
- minLength: 1
1796
- })
1797
- message!: string;
1798
- }
1766
+ \`\`\`
1767
+ ${projectName}/
1768
+ \u251C\u2500\u2500 main.ts # Server entry point
1769
+ \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
1770
+ \u2502 \u2514\u2500\u2500 example/
1771
+ \u2502 \u2514\u2500\u2500 index.ts # Example service
1772
+ \u251C\u2500\u2500 .env # Environment variables
1773
+ \u2514\u2500\u2500 package.json
1774
+ \`\`\`
1799
1775
 
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;
1776
+ ## Adding New Services
1810
1777
 
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
- }
1828
-
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
- }
1778
+ Create a new service directory in \`mcp/\`:
1840
1779
 
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
- }
1780
+ \`\`\`typescript
1781
+ // mcp/myservice/index.ts
1782
+ import { Tool, SchemaConstraint } from "@leanmcp/core";
1856
1783
 
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
- }
1784
+ // Define input schema
1785
+ class MyToolInput {
1786
+ @SchemaConstraint({
1787
+ description: "Message to process",
1788
+ minLength: 1
1789
+ })
1790
+ message!: string;
1791
+ }
1871
1792
 
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
- }
1793
+ export class MyService {
1794
+ @Tool({
1795
+ description: "My awesome tool",
1796
+ inputClass: MyToolInput
1797
+ })
1798
+ async myTool(input: MyToolInput) {
1799
+ return {
1800
+ content: [{
1801
+ type: "text",
1802
+ text: \`You said: \${input.message}\`
1881
1803
  }]
1882
1804
  };
1883
1805
  }
1884
1806
  }
1885
- `;
1886
- await fs7.writeFile(path7.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
1887
- const gitignore = `# Logs
1807
+ \`\`\`
1808
+
1809
+ Services are automatically discovered and registered - no need to modify \`main.ts\`!
1810
+
1811
+ ## Features
1812
+
1813
+ - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
1814
+ - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
1815
+ - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
1816
+ - **HTTP transport** - Production-ready HTTP server with session management
1817
+ - **Hot reload** - Development mode with automatic restart on file changes
1818
+
1819
+ ## Testing with MCP Inspector
1820
+
1821
+ \`\`\`bash
1822
+ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
1823
+ \`\`\`
1824
+
1825
+ ## License
1826
+
1827
+ MIT
1828
+ `, "getReadmeTemplate");
1829
+
1830
+ // src/templates/gitignore_v1.ts
1831
+ var gitignoreTemplate = `# Logs
1888
1832
  logs
1889
1833
  *.log
1890
1834
  npm-debug.log*
@@ -2026,111 +1970,333 @@ vite.config.js.timestamp-*
2026
1970
  vite.config.ts.timestamp-*
2027
1971
  .vite/
2028
1972
  `;
2029
- const env = `# Server Configuration
2030
- PORT=3001
2031
- NODE_ENV=development
2032
1973
 
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}
1974
+ // src/templates/example_service_v1.ts
1975
+ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
2038
1976
 
2039
- MCP Server with Streamable HTTP Transport built with LeanMCP SDK
1977
+ /**
1978
+ * Example service demonstrating LeanMCP SDK decorators
1979
+ *
1980
+ * This is a simple example to get you started. Add your own tools, resources, and prompts here!
1981
+ */
2040
1982
 
2041
- ## Quick Start
1983
+ // Input schema with validation decorators
1984
+ class CalculateInput {
1985
+ @SchemaConstraint({ description: "First number" })
1986
+ a!: number;
2042
1987
 
2043
- \`\`\`bash
2044
- # Install dependencies
2045
- npm install
1988
+ @SchemaConstraint({ description: "Second number" })
1989
+ b!: number;
2046
1990
 
2047
- # Start development server (hot reload)
2048
- npm run dev
1991
+ @Optional()
1992
+ @SchemaConstraint({
1993
+ description: "Operation to perform",
1994
+ enum: ["add", "subtract", "multiply", "divide"],
1995
+ default: "add"
1996
+ })
1997
+ operation?: string;
1998
+ }
2049
1999
 
2050
- # Build for production
2051
- npm run build
2000
+ class EchoInput {
2001
+ @SchemaConstraint({
2002
+ description: "Message to echo back",
2003
+ minLength: 1
2004
+ })
2005
+ message!: string;
2006
+ }
2052
2007
 
2053
- # Run production server
2054
- npm start
2055
- \`\`\`
2008
+ export class ExampleService {
2009
+ @Tool({
2010
+ description: "Perform arithmetic operations with automatic schema validation",
2011
+ inputClass: CalculateInput
2012
+ })
2013
+ async calculate(input: CalculateInput) {
2014
+ // Ensure numerical operations by explicitly converting to numbers
2015
+ const a = Number(input.a);
2016
+ const b = Number(input.b);
2017
+ let result: number;
2056
2018
 
2057
- ## Project Structure
2019
+ switch (input.operation || "add") {
2020
+ case "add":
2021
+ result = a + b;
2022
+ break;
2023
+ case "subtract":
2024
+ result = a - b;
2025
+ break;
2026
+ case "multiply":
2027
+ result = a * b;
2028
+ break;
2029
+ case "divide":
2030
+ if (b === 0) throw new Error("Cannot divide by zero");
2031
+ result = a / b;
2032
+ break;
2033
+ default:
2034
+ throw new Error("Invalid operation");
2035
+ }
2058
2036
 
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
- \`\`\`
2037
+ return {
2038
+ content: [{
2039
+ type: "text" as const,
2040
+ text: JSON.stringify({
2041
+ operation: input.operation || "add",
2042
+ operands: { a: input.a, b: input.b },
2043
+ result
2044
+ }, null, 2)
2045
+ }]
2046
+ };
2047
+ }
2068
2048
 
2069
- ## Adding New Services
2049
+ @Tool({
2050
+ description: "Echo a message back",
2051
+ inputClass: EchoInput
2052
+ })
2053
+ async echo(input: EchoInput) {
2054
+ return {
2055
+ content: [{
2056
+ type: "text" as const,
2057
+ text: JSON.stringify({
2058
+ echoed: input.message,
2059
+ timestamp: new Date().toISOString()
2060
+ }, null, 2)
2061
+ }]
2062
+ };
2063
+ }
2070
2064
 
2071
- Create a new service directory in \`mcp/\`:
2065
+ @Resource({ description: "Get server information" })
2066
+ async serverInfo() {
2067
+ return {
2068
+ contents: [{
2069
+ uri: "server://info",
2070
+ mimeType: "application/json",
2071
+ text: JSON.stringify({
2072
+ name: "${projectName}",
2073
+ version: "1.0.0",
2074
+ uptime: process.uptime()
2075
+ }, null, 2)
2076
+ }]
2077
+ };
2078
+ }
2072
2079
 
2073
- \`\`\`typescript
2074
- // mcp/myservice/index.ts
2075
- import { Tool, SchemaConstraint } from "@leanmcp/core";
2080
+ @Prompt({ description: "Generate a greeting prompt" })
2081
+ async greeting(args: { name?: string }) {
2082
+ return {
2083
+ messages: [{
2084
+ role: "user" as const,
2085
+ content: {
2086
+ type: "text" as const,
2087
+ text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
2088
+ }
2089
+ }]
2090
+ };
2091
+ }
2092
+ }
2093
+ `, "getExampleServiceTemplate");
2076
2094
 
2077
- // Define input schema
2078
- class MyToolInput {
2079
- @SchemaConstraint({
2080
- description: "Message to process",
2095
+ // src/templates/main_ts_v1.ts
2096
+ var getMainTsTemplate = /* @__PURE__ */ __name((projectName, dashboardLine) => `import dotenv from "dotenv";
2097
+ import { createHTTPServer } from "@leanmcp/core";
2098
+
2099
+ // Load environment variables
2100
+ dotenv.config();
2101
+
2102
+ // Services are automatically discovered from ./mcp directory
2103
+ await createHTTPServer({
2104
+ name: "${projectName}",
2105
+ version: "1.0.0",
2106
+ port: 3001,
2107
+ cors: true,
2108
+ logging: true${dashboardLine}
2109
+ });
2110
+
2111
+ console.log("\\n${projectName} MCP Server");
2112
+ `, "getMainTsTemplate");
2113
+
2114
+ // src/templates/service_index_v1.ts
2115
+ var getServiceIndexTemplate = /* @__PURE__ */ __name((serviceName, capitalizedName) => `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
2116
+
2117
+ // Input schema for greeting
2118
+ class GreetInput {
2119
+ @SchemaConstraint({
2120
+ description: "Name to greet",
2081
2121
  minLength: 1
2082
2122
  })
2083
- message!: string;
2123
+ name!: string;
2084
2124
  }
2085
2125
 
2086
- export class MyService {
2126
+ /**
2127
+ * ${capitalizedName} Service
2128
+ *
2129
+ * This service demonstrates the three types of MCP primitives:
2130
+ * - Tools: Callable functions (like API endpoints)
2131
+ * - Prompts: Reusable prompt templates
2132
+ * - Resources: Data sources/endpoints
2133
+ */
2134
+ export class ${capitalizedName}Service {
2135
+ // TOOL - Callable function
2136
+ // Tool name: "greet" (from function name)
2087
2137
  @Tool({
2088
- description: "My awesome tool",
2089
- inputClass: MyToolInput
2138
+ description: "Greet a user by name",
2139
+ inputClass: GreetInput
2090
2140
  })
2091
- async myTool(input: MyToolInput) {
2141
+ greet(args: GreetInput) {
2142
+ return { message: \`Hello, \${args.name}! from ${serviceName}\` };
2143
+ }
2144
+
2145
+ // PROMPT - Prompt template
2146
+ // Prompt name: "welcomePrompt" (from function name)
2147
+ @Prompt({ description: "Welcome message prompt template" })
2148
+ welcomePrompt(args: { userName?: string }) {
2092
2149
  return {
2093
- content: [{
2094
- type: "text",
2095
- text: \`You said: \${input.message}\`
2096
- }]
2150
+ messages: [
2151
+ {
2152
+ role: "user",
2153
+ content: {
2154
+ type: "text",
2155
+ text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
2156
+ }
2157
+ }
2158
+ ]
2097
2159
  };
2098
2160
  }
2099
- }
2100
- \`\`\`
2101
-
2102
- Services are automatically discovered and registered - no need to modify \`main.ts\`!
2103
-
2104
- ## Features
2105
2161
 
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
- \`\`\`
2162
+ // RESOURCE - Data endpoint
2163
+ // Resource URI auto-generated from class and method name
2164
+ @Resource({ description: "${capitalizedName} service status" })
2165
+ getStatus() {
2166
+ return {
2167
+ service: "${serviceName}",
2168
+ status: "active",
2169
+ timestamp: new Date().toISOString()
2170
+ };
2171
+ }
2172
+ }
2173
+ `, "getServiceIndexTemplate");
2117
2174
 
2118
- ## License
2175
+ // src/index.ts
2176
+ var require2 = createRequire(import.meta.url);
2177
+ var pkg = require2("../package.json");
2178
+ function capitalize(str) {
2179
+ return str.charAt(0).toUpperCase() + str.slice(1);
2180
+ }
2181
+ __name(capitalize, "capitalize");
2182
+ var program = new Command();
2183
+ program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
2184
+ Examples:
2185
+ $ leanmcp create my-app # Create new project (interactive)
2186
+ $ leanmcp create my-app --install # Create and install deps (non-interactive)
2187
+ $ leanmcp create my-app --no-install # Create without installing deps
2188
+ $ leanmcp dev # Start development server
2189
+ $ leanmcp build # Build UI components and compile TypeScript
2190
+ $ leanmcp start # Build and start production server
2191
+ $ leanmcp login # Authenticate with LeanMCP cloud
2192
+ $ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
2193
+ $ leanmcp projects list # List your cloud projects
2194
+ $ leanmcp projects delete <id> # Delete a cloud project
2195
+ `);
2196
+ 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) => {
2197
+ const spinner = ora7(`Creating project ${projectName}...`).start();
2198
+ const targetDir = path8.join(process.cwd(), projectName);
2199
+ if (fs8.existsSync(targetDir)) {
2200
+ spinner.fail(`Folder ${projectName} already exists.`);
2201
+ process.exit(1);
2202
+ }
2203
+ await fs8.mkdirp(targetDir);
2204
+ await fs8.mkdirp(path8.join(targetDir, "mcp", "example"));
2205
+ const pkg2 = {
2206
+ name: projectName,
2207
+ version: "1.0.0",
2208
+ description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
2209
+ main: "dist/main.js",
2210
+ type: "module",
2211
+ scripts: {
2212
+ dev: "leanmcp dev",
2213
+ build: "leanmcp build",
2214
+ start: "leanmcp start",
2215
+ "start:node": "node dist/main.js",
2216
+ clean: "rm -rf dist"
2217
+ },
2218
+ keywords: [
2219
+ "mcp",
2220
+ "model-context-protocol",
2221
+ "streamable-http",
2222
+ "leanmcp"
2223
+ ],
2224
+ author: "",
2225
+ license: "MIT",
2226
+ dependencies: {
2227
+ "@leanmcp/core": "^0.3.9",
2228
+ "@leanmcp/ui": "^0.2.1",
2229
+ "@leanmcp/auth": "^0.3.2",
2230
+ "dotenv": "^16.5.0"
2231
+ },
2232
+ devDependencies: {
2233
+ "@leanmcp/cli": "^0.3.0",
2234
+ "@types/node": "^20.0.0",
2235
+ "tsx": "^4.20.3",
2236
+ "typescript": "^5.6.3"
2237
+ }
2238
+ };
2239
+ await fs8.writeJSON(path8.join(targetDir, "package.json"), pkg2, {
2240
+ spaces: 2
2241
+ });
2242
+ const tsconfig = {
2243
+ compilerOptions: {
2244
+ module: "ESNext",
2245
+ target: "ES2022",
2246
+ moduleResolution: "Node",
2247
+ esModuleInterop: true,
2248
+ strict: true,
2249
+ skipLibCheck: true,
2250
+ outDir: "dist",
2251
+ experimentalDecorators: true,
2252
+ emitDecoratorMetadata: true
2253
+ },
2254
+ include: [
2255
+ "**/*.ts"
2256
+ ],
2257
+ exclude: [
2258
+ "node_modules",
2259
+ "dist"
2260
+ ]
2261
+ };
2262
+ await fs8.writeJSON(path8.join(targetDir, "tsconfig.json"), tsconfig, {
2263
+ spaces: 2
2264
+ });
2265
+ const dashboardLine = options.dashboard === false ? `
2266
+ dashboard: false, // Dashboard disabled via --no-dashboard` : "";
2267
+ const mainTs = getMainTsTemplate(projectName, dashboardLine);
2268
+ await fs8.writeFile(path8.join(targetDir, "main.ts"), mainTs);
2269
+ const exampleServiceTs = getExampleServiceTemplate(projectName);
2270
+ await fs8.writeFile(path8.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
2271
+ const gitignore = gitignoreTemplate;
2272
+ const env = `# Server Configuration
2273
+ PORT=3001
2274
+ NODE_ENV=development
2119
2275
 
2120
- MIT
2276
+ # Add your environment variables here
2121
2277
  `;
2122
- await fs7.writeFile(path7.join(targetDir, "README.md"), readme);
2278
+ await fs8.writeFile(path8.join(targetDir, ".gitignore"), gitignore);
2279
+ await fs8.writeFile(path8.join(targetDir, ".env"), env);
2280
+ const readme = getReadmeTemplate(projectName);
2281
+ await fs8.writeFile(path8.join(targetDir, "README.md"), readme);
2123
2282
  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}
2283
+ console.log(chalk7.green("\nSuccess! Your MCP server is ready.\n"));
2284
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2285
+ console.log(chalk7.gray(` cd ${projectName}`));
2286
+ console.log(chalk7.gray(` leanmcp deploy .
2127
2287
  `));
2288
+ console.log(chalk7.cyan("Need help? Join our Discord:"));
2289
+ console.log(chalk7.blue(" https://discord.com/invite/DsRcA3GwPy\n"));
2128
2290
  const isNonInteractive = options.install !== void 0 || options.allowAll;
2129
2291
  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`));
2292
+ console.log(chalk7.cyan("To get started:"));
2293
+ console.log(chalk7.gray(` cd ${projectName}`));
2294
+ console.log(chalk7.gray(` npm install`));
2295
+ console.log(chalk7.gray(` npm run dev`));
2296
+ console.log();
2297
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2298
+ console.log(chalk7.gray(` cd ${projectName}`));
2299
+ console.log(chalk7.gray(` leanmcp deploy .`));
2134
2300
  return;
2135
2301
  }
2136
2302
  const shouldInstall = isNonInteractive ? true : await confirm3({
@@ -2138,10 +2304,10 @@ MIT
2138
2304
  default: true
2139
2305
  });
2140
2306
  if (shouldInstall) {
2141
- const installSpinner = ora6("Installing dependencies...").start();
2307
+ const installSpinner = ora7("Installing dependencies...").start();
2142
2308
  try {
2143
2309
  await new Promise((resolve, reject) => {
2144
- const npmInstall = spawn3("npm", [
2310
+ const npmInstall = spawn4("npm", [
2145
2311
  "install"
2146
2312
  ], {
2147
2313
  cwd: targetDir,
@@ -2159,9 +2325,13 @@ MIT
2159
2325
  });
2160
2326
  installSpinner.succeed("Dependencies installed successfully!");
2161
2327
  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`));
2328
+ console.log(chalk7.cyan("\nTo start the development server:"));
2329
+ console.log(chalk7.gray(` cd ${projectName}`));
2330
+ console.log(chalk7.gray(` npm run dev`));
2331
+ console.log();
2332
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2333
+ console.log(chalk7.gray(` cd ${projectName}`));
2334
+ console.log(chalk7.gray(` leanmcp deploy .`));
2165
2335
  return;
2166
2336
  }
2167
2337
  const shouldStartDev = options.allowAll ? true : await confirm3({
@@ -2169,8 +2339,8 @@ MIT
2169
2339
  default: true
2170
2340
  });
2171
2341
  if (shouldStartDev) {
2172
- console.log(chalk6.cyan("\nStarting development server...\n"));
2173
- const devServer = spawn3("npm", [
2342
+ console.log(chalk7.cyan("\nStarting development server...\n"));
2343
+ const devServer = spawn4("npm", [
2174
2344
  "run",
2175
2345
  "dev"
2176
2346
  ], {
@@ -2183,106 +2353,57 @@ MIT
2183
2353
  process.exit(0);
2184
2354
  });
2185
2355
  } 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`));
2356
+ console.log(chalk7.cyan("\nTo start the development server later:"));
2357
+ console.log(chalk7.gray(` cd ${projectName}`));
2358
+ console.log(chalk7.gray(` npm run dev`));
2359
+ console.log();
2360
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2361
+ console.log(chalk7.gray(` cd ${projectName}`));
2362
+ console.log(chalk7.gray(` leanmcp deploy .`));
2189
2363
  }
2190
2364
  } catch (error) {
2191
2365
  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`));
2366
+ console.error(chalk7.red(error instanceof Error ? error.message : String(error)));
2367
+ console.log(chalk7.cyan("\nYou can install dependencies manually:"));
2368
+ console.log(chalk7.gray(` cd ${projectName}`));
2369
+ console.log(chalk7.gray(` npm install`));
2196
2370
  }
2197
2371
  } 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`));
2372
+ console.log(chalk7.cyan("\nTo get started:"));
2373
+ console.log(chalk7.gray(` cd ${projectName}`));
2374
+ console.log(chalk7.gray(` npm install`));
2375
+ console.log(chalk7.gray(` npm run dev`));
2376
+ console.log();
2377
+ console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
2378
+ console.log(chalk7.gray(` cd ${projectName}`));
2379
+ console.log(chalk7.gray(` leanmcp deploy .`));
2202
2380
  }
2203
2381
  });
2204
2382
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
2205
2383
  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)."));
2384
+ const mcpDir = path8.join(cwd, "mcp");
2385
+ if (!fs8.existsSync(path8.join(cwd, "main.ts"))) {
2386
+ console.error(chalk7.red("ERROR: Not a LeanMCP project (main.ts missing)."));
2209
2387
  process.exit(1);
2210
2388
  }
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.`));
2389
+ const serviceDir = path8.join(mcpDir, serviceName);
2390
+ const serviceFile = path8.join(serviceDir, "index.ts");
2391
+ if (fs8.existsSync(serviceDir)) {
2392
+ console.error(chalk7.red(`ERROR: Service ${serviceName} already exists.`));
2215
2393
  process.exit(1);
2216
2394
  }
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!`));
2395
+ await fs8.mkdirp(serviceDir);
2396
+ const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
2397
+ await fs8.writeFile(serviceFile, indexTs);
2398
+ console.log(chalk7.green(`\\nCreated new service: ${chalk7.bold(serviceName)}`));
2399
+ console.log(chalk7.gray(` File: mcp/${serviceName}/index.ts`));
2400
+ console.log(chalk7.gray(` Tool: greet`));
2401
+ console.log(chalk7.gray(` Prompt: welcomePrompt`));
2402
+ console.log(chalk7.gray(` Resource: getStatus`));
2403
+ console.log(chalk7.green(`\\nService will be automatically discovered on next server start!`));
2284
2404
  });
2285
2405
  program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(devCommand);
2406
+ program.command("build").description("Build UI components and compile TypeScript for production").action(buildCommand);
2286
2407
  program.command("start").description("Build UI components and start production server").action(startCommand);
2287
2408
  program.command("login").description("Authenticate with LeanMCP cloud using an API key").option("--debug", "Enable debug logging").action(async (options) => {
2288
2409
  if (options.debug) {