@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.
- package/dist/index.js +641 -520
- 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
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
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
|
|
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/
|
|
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
|
|
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{
|
|
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.
|
|
646
|
-
|
|
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(
|
|
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
|
|
670
|
-
import
|
|
671
|
-
import
|
|
672
|
-
import
|
|
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(
|
|
757
|
+
console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
|
|
683
758
|
}
|
|
684
759
|
}
|
|
685
760
|
__name(debug, "debug");
|
|
686
|
-
var CONFIG_DIR =
|
|
687
|
-
var CONFIG_FILE =
|
|
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
|
|
691
|
-
return await
|
|
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
|
|
700
|
-
await
|
|
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(
|
|
813
|
+
console.log(chalk4.cyan("\nLeanMCP Login\n"));
|
|
739
814
|
const existingConfig = await loadConfig();
|
|
740
815
|
if (existingConfig.apiKey) {
|
|
741
|
-
console.log(
|
|
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(
|
|
822
|
+
console.log(chalk4.gray("\nLogin cancelled. Existing API key preserved."));
|
|
748
823
|
return;
|
|
749
824
|
}
|
|
750
825
|
}
|
|
751
|
-
console.log(
|
|
752
|
-
console.log(
|
|
753
|
-
console.log(
|
|
754
|
-
console.log(
|
|
755
|
-
console.log(
|
|
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 =
|
|
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(
|
|
789
|
-
console.log(
|
|
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(
|
|
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(
|
|
802
|
-
console.log(
|
|
876
|
+
console.log(chalk4.green("\nLogin successful!"));
|
|
877
|
+
console.log(chalk4.gray(` Config saved to: ${CONFIG_FILE}
|
|
803
878
|
`));
|
|
804
|
-
console.log(
|
|
805
|
-
console.log(
|
|
806
|
-
console.log(
|
|
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(
|
|
812
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
901
|
+
console.log(chalk4.cyan("\nLeanMCP Logout\n"));
|
|
827
902
|
const config = await loadConfig();
|
|
828
903
|
if (!config.apiKey) {
|
|
829
|
-
console.log(
|
|
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(
|
|
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(
|
|
846
|
-
console.log(
|
|
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(
|
|
853
|
-
console.log(
|
|
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(
|
|
857
|
-
console.log(
|
|
858
|
-
console.log(
|
|
859
|
-
console.log(
|
|
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(
|
|
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
|
|
869
|
-
import
|
|
870
|
-
import
|
|
871
|
-
import
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
1190
|
-
console.log(
|
|
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 =
|
|
1196
|
-
if (!await
|
|
1197
|
-
console.error(
|
|
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
|
|
1201
|
-
const hasPackageJson = await
|
|
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(
|
|
1204
|
-
console.log(
|
|
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 =
|
|
1300
|
+
let folderName = path7.basename(absolutePath);
|
|
1226
1301
|
if (hasPackageJson) {
|
|
1227
1302
|
try {
|
|
1228
|
-
const pkg2 = await
|
|
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(
|
|
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(
|
|
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(
|
|
1263
|
-
console.log(
|
|
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(
|
|
1267
|
-
Generated project name: ${
|
|
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(
|
|
1347
|
+
console.log(chalk5.cyan(`Generated project name: ${chalk5.bold(projectName)}`));
|
|
1273
1348
|
}
|
|
1274
|
-
console.log(
|
|
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 =
|
|
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(
|
|
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(
|
|
1318
|
-
console.log(
|
|
1319
|
-
console.log(
|
|
1320
|
-
console.log(
|
|
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(
|
|
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(
|
|
1410
|
+
console.log(chalk5.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
|
|
1336
1411
|
} else {
|
|
1337
|
-
const projectSpinner =
|
|
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(
|
|
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 =
|
|
1439
|
+
const uploadSpinner = ora5("Packaging and uploading...").start();
|
|
1365
1440
|
try {
|
|
1366
|
-
const tempZip =
|
|
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
|
|
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
|
|
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(
|
|
1495
|
+
console.error(chalk5.red(`
|
|
1421
1496
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1422
1497
|
process.exit(1);
|
|
1423
1498
|
}
|
|
1424
|
-
const buildSpinner =
|
|
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(
|
|
1523
|
+
console.error(chalk5.red(`
|
|
1449
1524
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1450
1525
|
process.exit(1);
|
|
1451
1526
|
}
|
|
1452
|
-
const deploySpinner =
|
|
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(
|
|
1553
|
+
console.error(chalk5.red(`
|
|
1479
1554
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1480
1555
|
process.exit(1);
|
|
1481
1556
|
}
|
|
1482
|
-
const mappingSpinner =
|
|
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(
|
|
1507
|
-
console.log(
|
|
1508
|
-
console.log(
|
|
1509
|
-
console.log(
|
|
1510
|
-
console.log(
|
|
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(
|
|
1513
|
-
console.log(
|
|
1514
|
-
console.log(
|
|
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(
|
|
1592
|
+
console.log(chalk5.gray(` Total time: ${totalDuration}s`));
|
|
1518
1593
|
console.log();
|
|
1519
|
-
|
|
1520
|
-
console.log(
|
|
1521
|
-
console.log(
|
|
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
|
|
1528
|
-
import
|
|
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(
|
|
1534
|
-
console.log(
|
|
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 =
|
|
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(
|
|
1552
|
-
console.log(
|
|
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(
|
|
1635
|
+
console.log(chalk6.cyan(`
|
|
1556
1636
|
Your Projects (${projects.length})
|
|
1557
1637
|
`));
|
|
1558
|
-
console.log(
|
|
1638
|
+
console.log(chalk6.gray("\u2500".repeat(60)));
|
|
1559
1639
|
for (const project of projects) {
|
|
1560
|
-
const statusColor = project.status === "ACTIVE" ?
|
|
1561
|
-
console.log(
|
|
1562
|
-
console.log(
|
|
1563
|
-
console.log(
|
|
1564
|
-
console.log(
|
|
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(
|
|
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(
|
|
1579
|
-
console.log(
|
|
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 =
|
|
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(
|
|
1599
|
-
console.log(
|
|
1600
|
-
console.log(
|
|
1601
|
-
console.log(
|
|
1602
|
-
console.log(
|
|
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(
|
|
1684
|
+
console.log(chalk6.gray(` S3 Location: ${project.s3Location}`));
|
|
1605
1685
|
}
|
|
1606
|
-
console.log(
|
|
1686
|
+
console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
|
|
1607
1687
|
if (project.updatedAt) {
|
|
1608
|
-
console.log(
|
|
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(
|
|
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(
|
|
1623
|
-
console.log(
|
|
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(
|
|
1713
|
+
console.log(chalk6.gray("\nDeletion cancelled.\n"));
|
|
1634
1714
|
return;
|
|
1635
1715
|
}
|
|
1636
1716
|
}
|
|
1637
|
-
const spinner =
|
|
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(
|
|
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/
|
|
1664
|
-
var
|
|
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
|
-
|
|
1753
|
-
dotenv.config();
|
|
1746
|
+
MCP Server with Streamable HTTP Transport built with LeanMCP SDK
|
|
1754
1747
|
|
|
1755
|
-
|
|
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
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
1750
|
+
\`\`\`bash
|
|
1751
|
+
# Install dependencies
|
|
1752
|
+
npm install
|
|
1768
1753
|
|
|
1769
|
-
|
|
1770
|
-
|
|
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
|
-
|
|
1776
|
-
|
|
1777
|
-
@SchemaConstraint({ description: "First number" })
|
|
1778
|
-
a!: number;
|
|
1757
|
+
# Build for production
|
|
1758
|
+
npm run build
|
|
1779
1759
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1760
|
+
# Run production server
|
|
1761
|
+
npm start
|
|
1762
|
+
\`\`\`
|
|
1782
1763
|
|
|
1783
|
-
|
|
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
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
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
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
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
|
-
|
|
1887
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1983
|
+
// Input schema with validation decorators
|
|
1984
|
+
class CalculateInput {
|
|
1985
|
+
@SchemaConstraint({ description: "First number" })
|
|
1986
|
+
a!: number;
|
|
2042
1987
|
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
npm install
|
|
1988
|
+
@SchemaConstraint({ description: "Second number" })
|
|
1989
|
+
b!: number;
|
|
2046
1990
|
|
|
2047
|
-
|
|
2048
|
-
|
|
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
|
-
|
|
2051
|
-
|
|
2000
|
+
class EchoInput {
|
|
2001
|
+
@SchemaConstraint({
|
|
2002
|
+
description: "Message to echo back",
|
|
2003
|
+
minLength: 1
|
|
2004
|
+
})
|
|
2005
|
+
message!: string;
|
|
2006
|
+
}
|
|
2052
2007
|
|
|
2053
|
-
|
|
2054
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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
|
-
//
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
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
|
-
|
|
2123
|
+
name!: string;
|
|
2084
2124
|
}
|
|
2085
2125
|
|
|
2086
|
-
|
|
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: "
|
|
2089
|
-
inputClass:
|
|
2138
|
+
description: "Greet a user by name",
|
|
2139
|
+
inputClass: GreetInput
|
|
2090
2140
|
})
|
|
2091
|
-
|
|
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
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
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
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2276
|
+
# Add your environment variables here
|
|
2121
2277
|
`;
|
|
2122
|
-
await
|
|
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(
|
|
2125
|
-
console.log(
|
|
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(
|
|
2131
|
-
console.log(
|
|
2132
|
-
console.log(
|
|
2133
|
-
console.log(
|
|
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 =
|
|
2307
|
+
const installSpinner = ora7("Installing dependencies...").start();
|
|
2142
2308
|
try {
|
|
2143
2309
|
await new Promise((resolve, reject) => {
|
|
2144
|
-
const npmInstall =
|
|
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(
|
|
2163
|
-
console.log(
|
|
2164
|
-
console.log(
|
|
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(
|
|
2173
|
-
const devServer =
|
|
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(
|
|
2187
|
-
console.log(
|
|
2188
|
-
console.log(
|
|
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(
|
|
2193
|
-
console.log(
|
|
2194
|
-
console.log(
|
|
2195
|
-
console.log(
|
|
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(
|
|
2199
|
-
console.log(
|
|
2200
|
-
console.log(
|
|
2201
|
-
console.log(
|
|
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 =
|
|
2207
|
-
if (!
|
|
2208
|
-
console.error(
|
|
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 =
|
|
2212
|
-
const serviceFile =
|
|
2213
|
-
if (
|
|
2214
|
-
console.error(
|
|
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
|
|
2218
|
-
const indexTs =
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
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) {
|