@leanmcp/cli 0.5.4 → 0.5.5-alpha.28.7306df7

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 LeanMCP Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LeanMCP Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/leanmcp.js CHANGED
File without changes
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3
3
 
4
4
  // src/index.ts
5
5
  import { Command } from "commander";
6
- import fs10 from "fs-extra";
7
- import path10 from "path";
6
+ import fs11 from "fs-extra";
7
+ import path11 from "path";
8
8
  import ora8 from "ora";
9
9
  import { createRequire } from "module";
10
10
  import { confirm as confirm4 } from "@inquirer/prompts";
@@ -841,11 +841,194 @@ __name(devCommand, "devCommand");
841
841
  // src/commands/build.ts
842
842
  import { spawn as spawn2 } from "child_process";
843
843
  import ora2 from "ora";
844
+ import path5 from "path";
845
+ import fs5 from "fs-extra";
846
+
847
+ // src/schema-extractor.ts
844
848
  import path4 from "path";
845
849
  import fs4 from "fs-extra";
850
+ import { Project, Node } from "ts-morph";
851
+ async function generateSchemaMetadata(projectDir) {
852
+ const metadata = {};
853
+ const classesByName = /* @__PURE__ */ new Map();
854
+ const classesToProcess = /* @__PURE__ */ new Set();
855
+ const processedClasses = /* @__PURE__ */ new Set();
856
+ const project = new Project({
857
+ skipAddingFilesFromTsConfig: true,
858
+ skipFileDependencyResolution: true
859
+ });
860
+ const tsFiles = await findTsFiles(projectDir);
861
+ for (const filePath of tsFiles) {
862
+ try {
863
+ const sourceFile = project.addSourceFileAtPath(filePath);
864
+ for (const classDecl of sourceFile.getClasses()) {
865
+ const className = classDecl.getName();
866
+ if (className) {
867
+ classesByName.set(className, classDecl);
868
+ }
869
+ }
870
+ for (const classDecl of sourceFile.getClasses()) {
871
+ for (const method of classDecl.getInstanceMethods()) {
872
+ const decorators = method.getDecorators();
873
+ const isTool = decorators.some((d) => {
874
+ const name = d.getName();
875
+ return [
876
+ "Tool",
877
+ "Prompt",
878
+ "Resource"
879
+ ].includes(name);
880
+ });
881
+ if (isTool) {
882
+ const params = method.getParameters();
883
+ if (params.length > 0) {
884
+ const paramToken = params[0];
885
+ const typeNode = paramToken.getTypeNode();
886
+ if (typeNode) {
887
+ const typeName = typeNode.getText().trim();
888
+ if (!isPrimitive(typeName)) {
889
+ classesToProcess.add(extractBaseClassName(typeName));
890
+ }
891
+ }
892
+ }
893
+ }
894
+ }
895
+ }
896
+ } catch (error) {
897
+ }
898
+ }
899
+ for (const className of classesToProcess) {
900
+ if (processedClasses.has(className)) continue;
901
+ const classDecl = classesByName.get(className);
902
+ if (!classDecl) continue;
903
+ const propertyTypes = {};
904
+ for (const prop of classDecl.getInstanceProperties()) {
905
+ if (Node.isPropertyDeclaration(prop)) {
906
+ const propName = prop.getName();
907
+ const typeNode = prop.getTypeNode();
908
+ if (typeNode) {
909
+ const typeText = typeNode.getText();
910
+ const schemaType = typeTextToSchemaType(typeText);
911
+ propertyTypes[propName] = schemaType;
912
+ const baseType = extractBaseClassName(typeText);
913
+ if (!isPrimitive(baseType) && !processedClasses.has(baseType)) {
914
+ classesToProcess.add(baseType);
915
+ }
916
+ }
917
+ }
918
+ }
919
+ if (Object.keys(propertyTypes).length > 0) {
920
+ metadata[className] = propertyTypes;
921
+ }
922
+ processedClasses.add(className);
923
+ }
924
+ const outputPath = path4.join(projectDir, "dist", "schema-metadata.json");
925
+ await fs4.ensureDir(path4.dirname(outputPath));
926
+ await fs4.writeJSON(outputPath, metadata, {
927
+ spaces: 2
928
+ });
929
+ }
930
+ __name(generateSchemaMetadata, "generateSchemaMetadata");
931
+ function isPrimitive(typeName) {
932
+ const t = typeName.toLowerCase();
933
+ return [
934
+ "string",
935
+ "number",
936
+ "boolean",
937
+ "any",
938
+ "void",
939
+ "null",
940
+ "undefined",
941
+ "object",
942
+ "date"
943
+ ].includes(t);
944
+ }
945
+ __name(isPrimitive, "isPrimitive");
946
+ function extractBaseClassName(typeText) {
947
+ let name = typeText.trim();
948
+ if (name.endsWith("[]")) {
949
+ name = name.slice(0, -2);
950
+ }
951
+ const arrayMatch = name.match(/^Array<(.+)>$/i);
952
+ if (arrayMatch) {
953
+ name = arrayMatch[1];
954
+ }
955
+ return name.trim();
956
+ }
957
+ __name(extractBaseClassName, "extractBaseClassName");
958
+ async function findTsFiles(dir) {
959
+ const files = [];
960
+ async function scan(currentDir) {
961
+ const entries = await fs4.readdir(currentDir, {
962
+ withFileTypes: true
963
+ });
964
+ for (const entry of entries) {
965
+ const fullPath = path4.join(currentDir, entry.name);
966
+ if (entry.isDirectory()) {
967
+ if ([
968
+ "node_modules",
969
+ "dist",
970
+ ".git",
971
+ ".leanmcp"
972
+ ].includes(entry.name)) {
973
+ continue;
974
+ }
975
+ await scan(fullPath);
976
+ } else if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
977
+ files.push(fullPath);
978
+ }
979
+ }
980
+ }
981
+ __name(scan, "scan");
982
+ await scan(dir);
983
+ return files;
984
+ }
985
+ __name(findTsFiles, "findTsFiles");
986
+ function typeTextToSchemaType(typeText) {
987
+ const type = typeText.trim().replace(/\?$/, "");
988
+ if (type.endsWith("[]")) {
989
+ const elementType = type.slice(0, -2).toLowerCase();
990
+ return {
991
+ type: "array",
992
+ items: {
993
+ type: mapPrimitiveType(elementType)
994
+ }
995
+ };
996
+ }
997
+ const arrayMatch = type.match(/^Array<(.+)>$/i);
998
+ if (arrayMatch) {
999
+ const elementType = arrayMatch[1].trim().toLowerCase();
1000
+ return {
1001
+ type: "array",
1002
+ items: {
1003
+ type: mapPrimitiveType(elementType)
1004
+ }
1005
+ };
1006
+ }
1007
+ return {
1008
+ type: mapPrimitiveType(type.toLowerCase())
1009
+ };
1010
+ }
1011
+ __name(typeTextToSchemaType, "typeTextToSchemaType");
1012
+ function mapPrimitiveType(tsType) {
1013
+ switch (tsType) {
1014
+ case "string":
1015
+ return "string";
1016
+ case "number":
1017
+ return "number";
1018
+ case "boolean":
1019
+ return "boolean";
1020
+ case "integer":
1021
+ return "integer";
1022
+ default:
1023
+ return "string";
1024
+ }
1025
+ }
1026
+ __name(mapPrimitiveType, "mapPrimitiveType");
1027
+
1028
+ // src/commands/build.ts
846
1029
  async function buildCommand() {
847
1030
  const cwd = process.cwd();
848
- if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
1031
+ if (!await fs5.pathExists(path5.join(cwd, "main.ts"))) {
849
1032
  logger.error("ERROR: Not a LeanMCP project (main.ts not found).");
850
1033
  logger.gray("Run this command from your project root.");
851
1034
  process.exit(1);
@@ -910,6 +1093,14 @@ async function buildCommand() {
910
1093
  logger.error(error instanceof Error ? error.message : String(error));
911
1094
  process.exit(1);
912
1095
  }
1096
+ const schemaSpinner = ora2("Generating schema metadata...").start();
1097
+ try {
1098
+ await generateSchemaMetadata(cwd);
1099
+ schemaSpinner.succeed("Schema metadata generated");
1100
+ } catch (error) {
1101
+ schemaSpinner.warn("Schema metadata generation failed (runtime fallback will be used)");
1102
+ logger.gray(` ${error instanceof Error ? error.message : String(error)}`);
1103
+ }
913
1104
  logger.success("\nBuild complete!");
914
1105
  logger.gray("\nTo start the server:");
915
1106
  logger.info(" npm run start:node\n");
@@ -919,11 +1110,11 @@ __name(buildCommand, "buildCommand");
919
1110
  // src/commands/start.ts
920
1111
  import { spawn as spawn3 } from "child_process";
921
1112
  import ora3 from "ora";
922
- import path5 from "path";
923
- import fs5 from "fs-extra";
1113
+ import path6 from "path";
1114
+ import fs6 from "fs-extra";
924
1115
  async function startCommand() {
925
1116
  const cwd = process.cwd();
926
- if (!await fs5.pathExists(path5.join(cwd, "main.ts"))) {
1117
+ if (!await fs6.pathExists(path6.join(cwd, "main.ts"))) {
927
1118
  logger.error("ERROR: Not a LeanMCP project (main.ts not found).");
928
1119
  logger.gray("Run this command from your project root.");
929
1120
  process.exit(1);
@@ -1025,8 +1216,8 @@ __name(startCommand, "startCommand");
1025
1216
 
1026
1217
  // src/commands/login.ts
1027
1218
  import ora4 from "ora";
1028
- import path6 from "path";
1029
- import fs6 from "fs-extra";
1219
+ import path7 from "path";
1220
+ import fs7 from "fs-extra";
1030
1221
  import os2 from "os";
1031
1222
  import { input, confirm } from "@inquirer/prompts";
1032
1223
  var DEBUG_MODE2 = false;
@@ -1040,12 +1231,12 @@ function debug2(message, ...args) {
1040
1231
  }
1041
1232
  }
1042
1233
  __name(debug2, "debug");
1043
- var CONFIG_DIR = path6.join(os2.homedir(), ".leanmcp");
1044
- var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
1234
+ var CONFIG_DIR = path7.join(os2.homedir(), ".leanmcp");
1235
+ var CONFIG_FILE = path7.join(CONFIG_DIR, "config.json");
1045
1236
  async function loadConfig() {
1046
1237
  try {
1047
- if (await fs6.pathExists(CONFIG_FILE)) {
1048
- return await fs6.readJSON(CONFIG_FILE);
1238
+ if (await fs7.pathExists(CONFIG_FILE)) {
1239
+ return await fs7.readJSON(CONFIG_FILE);
1049
1240
  }
1050
1241
  } catch (error) {
1051
1242
  }
@@ -1053,8 +1244,8 @@ async function loadConfig() {
1053
1244
  }
1054
1245
  __name(loadConfig, "loadConfig");
1055
1246
  async function saveConfig(config) {
1056
- await fs6.ensureDir(CONFIG_DIR);
1057
- await fs6.writeJSON(CONFIG_FILE, config, {
1247
+ await fs7.ensureDir(CONFIG_DIR);
1248
+ await fs7.writeJSON(CONFIG_FILE, config, {
1058
1249
  spaces: 2
1059
1250
  });
1060
1251
  }
@@ -1261,8 +1452,8 @@ __name(whoamiCommand, "whoamiCommand");
1261
1452
 
1262
1453
  // src/commands/deploy.ts
1263
1454
  import ora5 from "ora";
1264
- import path8 from "path";
1265
- import fs8 from "fs-extra";
1455
+ import path9 from "path";
1456
+ import fs9 from "fs-extra";
1266
1457
  import os3 from "os";
1267
1458
  import archiver from "archiver";
1268
1459
  import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
@@ -1445,8 +1636,8 @@ function generateProjectName() {
1445
1636
  __name(generateProjectName, "generateProjectName");
1446
1637
 
1447
1638
  // src/utils/env-parser.ts
1448
- import fs7 from "fs-extra";
1449
- import path7 from "path";
1639
+ import fs8 from "fs-extra";
1640
+ import path8 from "path";
1450
1641
  var RESERVED_ENV_KEYS = [
1451
1642
  "AWS_REGION",
1452
1643
  "AWS_ACCESS_KEY_ID",
@@ -1509,16 +1700,16 @@ function parseEnvFile(content) {
1509
1700
  }
1510
1701
  __name(parseEnvFile, "parseEnvFile");
1511
1702
  async function loadEnvFile(filePath) {
1512
- const absolutePath = path7.resolve(filePath);
1513
- if (!await fs7.pathExists(absolutePath)) {
1703
+ const absolutePath = path8.resolve(filePath);
1704
+ if (!await fs8.pathExists(absolutePath)) {
1514
1705
  throw new Error(`Env file not found: ${absolutePath}`);
1515
1706
  }
1516
- const content = await fs7.readFile(absolutePath, "utf-8");
1707
+ const content = await fs8.readFile(absolutePath, "utf-8");
1517
1708
  return parseEnvFile(content);
1518
1709
  }
1519
1710
  __name(loadEnvFile, "loadEnvFile");
1520
1711
  async function writeEnvFile(filePath, vars) {
1521
- const absolutePath = path7.resolve(filePath);
1712
+ const absolutePath = path8.resolve(filePath);
1522
1713
  const lines = [
1523
1714
  "# Environment variables",
1524
1715
  `# Generated by leanmcp CLI at ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -1534,7 +1725,7 @@ async function writeEnvFile(filePath, vars) {
1534
1725
  }
1535
1726
  }
1536
1727
  lines.push("");
1537
- await fs7.writeFile(absolutePath, lines.join("\n"));
1728
+ await fs8.writeFile(absolutePath, lines.join("\n"));
1538
1729
  }
1539
1730
  __name(writeEnvFile, "writeEnvFile");
1540
1731
  function isValidEnvKey(key) {
@@ -1612,10 +1803,10 @@ var API_ENDPOINTS = {
1612
1803
  var LEANMCP_CONFIG_DIR = ".leanmcp";
1613
1804
  var LEANMCP_CONFIG_FILE = "config.json";
1614
1805
  async function readLeanMCPConfig(projectPath) {
1615
- const configPath = path8.join(projectPath, LEANMCP_CONFIG_DIR, LEANMCP_CONFIG_FILE);
1806
+ const configPath = path9.join(projectPath, LEANMCP_CONFIG_DIR, LEANMCP_CONFIG_FILE);
1616
1807
  try {
1617
- if (await fs8.pathExists(configPath)) {
1618
- const config = await fs8.readJSON(configPath);
1808
+ if (await fs9.pathExists(configPath)) {
1809
+ const config = await fs9.readJSON(configPath);
1619
1810
  debug3("Found existing .leanmcp config:", config);
1620
1811
  return config;
1621
1812
  }
@@ -1626,10 +1817,10 @@ async function readLeanMCPConfig(projectPath) {
1626
1817
  }
1627
1818
  __name(readLeanMCPConfig, "readLeanMCPConfig");
1628
1819
  async function writeLeanMCPConfig(projectPath, config) {
1629
- const configDir = path8.join(projectPath, LEANMCP_CONFIG_DIR);
1630
- const configPath = path8.join(configDir, LEANMCP_CONFIG_FILE);
1631
- await fs8.ensureDir(configDir);
1632
- await fs8.writeJSON(configPath, config, {
1820
+ const configDir = path9.join(projectPath, LEANMCP_CONFIG_DIR);
1821
+ const configPath = path9.join(configDir, LEANMCP_CONFIG_FILE);
1822
+ await fs9.ensureDir(configDir);
1823
+ await fs9.writeJSON(configPath, config, {
1633
1824
  spaces: 2
1634
1825
  });
1635
1826
  debug3("Saved .leanmcp config:", config);
@@ -1637,7 +1828,7 @@ async function writeLeanMCPConfig(projectPath, config) {
1637
1828
  __name(writeLeanMCPConfig, "writeLeanMCPConfig");
1638
1829
  async function createZipArchive(folderPath, outputPath) {
1639
1830
  return new Promise((resolve, reject) => {
1640
- const output = fs8.createWriteStream(outputPath);
1831
+ const output = fs9.createWriteStream(outputPath);
1641
1832
  const archive = archiver("zip", {
1642
1833
  zlib: {
1643
1834
  level: 9
@@ -1747,16 +1938,16 @@ async function deployCommand(folderPath, options = {}) {
1747
1938
  }
1748
1939
  const apiUrl = await getApiUrl();
1749
1940
  debug3("API URL:", apiUrl);
1750
- const absolutePath = path8.resolve(process.cwd(), folderPath);
1751
- if (!await fs8.pathExists(absolutePath)) {
1941
+ const absolutePath = path9.resolve(process.cwd(), folderPath);
1942
+ if (!await fs9.pathExists(absolutePath)) {
1752
1943
  logger.error(`Folder not found: ${absolutePath}`);
1753
1944
  process.exit(1);
1754
1945
  }
1755
- const hasMainTs = await fs8.pathExists(path8.join(absolutePath, "main.ts"));
1756
- const hasPackageJson = await fs8.pathExists(path8.join(absolutePath, "package.json"));
1757
- const hasMainPy = await fs8.pathExists(path8.join(absolutePath, "main.py"));
1758
- const hasRequirementsTxt = await fs8.pathExists(path8.join(absolutePath, "requirements.txt"));
1759
- const hasPyprojectToml = await fs8.pathExists(path8.join(absolutePath, "pyproject.toml"));
1946
+ const hasMainTs = await fs9.pathExists(path9.join(absolutePath, "main.ts"));
1947
+ const hasPackageJson = await fs9.pathExists(path9.join(absolutePath, "package.json"));
1948
+ const hasMainPy = await fs9.pathExists(path9.join(absolutePath, "main.py"));
1949
+ const hasRequirementsTxt = await fs9.pathExists(path9.join(absolutePath, "requirements.txt"));
1950
+ const hasPyprojectToml = await fs9.pathExists(path9.join(absolutePath, "pyproject.toml"));
1760
1951
  const isNodeProject = hasMainTs || hasPackageJson;
1761
1952
  const isPythonProject = hasMainPy || hasRequirementsTxt || hasPyprojectToml;
1762
1953
  if (!isNodeProject && !isPythonProject) {
@@ -1828,10 +2019,10 @@ Generated project name: ${chalk.bold(projectName)}
1828
2019
  } catch (e) {
1829
2020
  debug3("Could not fetch existing projects");
1830
2021
  }
1831
- let folderName = path8.basename(absolutePath);
2022
+ let folderName = path9.basename(absolutePath);
1832
2023
  if (hasPackageJson) {
1833
2024
  try {
1834
- const pkg2 = await fs8.readJSON(path8.join(absolutePath, "package.json"));
2025
+ const pkg2 = await fs9.readJSON(path9.join(absolutePath, "package.json"));
1835
2026
  folderName = pkg2.name || folderName;
1836
2027
  } catch (e) {
1837
2028
  }
@@ -1982,7 +2173,7 @@ ${error instanceof Error ? error.message : String(error)}`);
1982
2173
  }
1983
2174
  const uploadSpinner = ora5("Packaging and uploading...").start();
1984
2175
  try {
1985
- const tempZip = path8.join(os3.tmpdir(), `leanmcp-${Date.now()}.zip`);
2176
+ const tempZip = path9.join(os3.tmpdir(), `leanmcp-${Date.now()}.zip`);
1986
2177
  const zipSize = await createZipArchive(absolutePath, tempZip);
1987
2178
  uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
1988
2179
  debug3("Step 2a: Getting upload URL for project:", projectId);
@@ -2009,7 +2200,7 @@ ${error instanceof Error ? error.message : String(error)}`);
2009
2200
  throw new Error("Backend did not return upload URL");
2010
2201
  }
2011
2202
  debug3("Step 2b: Uploading to S3...");
2012
- const zipBuffer = await fs8.readFile(tempZip);
2203
+ const zipBuffer = await fs9.readFile(tempZip);
2013
2204
  const s3Response = await fetch(uploadUrl, {
2014
2205
  method: "PUT",
2015
2206
  body: zipBuffer,
@@ -2032,7 +2223,7 @@ ${error instanceof Error ? error.message : String(error)}`);
2032
2223
  s3Location
2033
2224
  })
2034
2225
  });
2035
- await fs8.remove(tempZip);
2226
+ await fs9.remove(tempZip);
2036
2227
  uploadSpinner.succeed("Project uploaded");
2037
2228
  } catch (error) {
2038
2229
  uploadSpinner.fail("Failed to upload");
@@ -2307,8 +2498,8 @@ __name(projectsDeleteCommand, "projectsDeleteCommand");
2307
2498
 
2308
2499
  // src/commands/env.ts
2309
2500
  import ora7 from "ora";
2310
- import path9 from "path";
2311
- import fs9 from "fs-extra";
2501
+ import path10 from "path";
2502
+ import fs10 from "fs-extra";
2312
2503
  import { confirm as confirm3 } from "@inquirer/prompts";
2313
2504
  var DEBUG_MODE4 = false;
2314
2505
  function setEnvDebugMode(enabled) {
@@ -2341,10 +2532,10 @@ __name(debugFetch2, "debugFetch");
2341
2532
  var LEANMCP_CONFIG_DIR2 = ".leanmcp";
2342
2533
  var LEANMCP_CONFIG_FILE2 = "config.json";
2343
2534
  async function readLeanMCPConfig2(projectPath) {
2344
- const configPath = path9.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
2535
+ const configPath = path10.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
2345
2536
  try {
2346
- if (await fs9.pathExists(configPath)) {
2347
- const config = await fs9.readJSON(configPath);
2537
+ if (await fs10.pathExists(configPath)) {
2538
+ const config = await fs10.readJSON(configPath);
2348
2539
  debug4("Found existing .leanmcp config:", config);
2349
2540
  return config;
2350
2541
  }
@@ -2362,7 +2553,7 @@ async function getDeploymentContext(folderPath) {
2362
2553
  return null;
2363
2554
  }
2364
2555
  const apiUrl = await getApiUrl();
2365
- const absolutePath = path9.resolve(process.cwd(), folderPath);
2556
+ const absolutePath = path10.resolve(process.cwd(), folderPath);
2366
2557
  const config = await readLeanMCPConfig2(absolutePath);
2367
2558
  if (!config) {
2368
2559
  logger.error("No deployment found.");
@@ -2987,12 +3178,16 @@ vite.config.ts.timestamp-*
2987
3178
  var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
2988
3179
 
2989
3180
  /**
2990
- * Example service demonstrating LeanMCP SDK decorators
3181
+ * ${projectName} - Production-ready MCP server example
2991
3182
  *
2992
- * This is a simple example to get you started. Add your own tools, resources, and prompts here!
3183
+ * This example demonstrates LeanMCP's core features:
3184
+ * - Schema validation with decorators
3185
+ * - Type-safe tool definitions
3186
+ * - Resource and prompt capabilities
3187
+ * - Production-ready structure
2993
3188
  */
2994
3189
 
2995
- // Input schema with validation decorators
3190
+ // Input schemas with validation decorators
2996
3191
  class CalculateInput {
2997
3192
  @SchemaConstraint({ description: "First number" })
2998
3193
  a!: number;
@@ -3017,13 +3212,13 @@ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import
3017
3212
  message!: string;
3018
3213
  }
3019
3214
 
3020
- export class ExampleService {
3215
+ export class ${projectName}Service {
3216
+ // CALCULATION TOOL - Shows schema validation
3021
3217
  @Tool({
3022
3218
  description: "Perform arithmetic operations with automatic schema validation",
3023
3219
  inputClass: CalculateInput
3024
3220
  })
3025
3221
  async calculate(input: CalculateInput) {
3026
- // Ensure numerical operations by explicitly converting to numbers
3027
3222
  const a = Number(input.a);
3028
3223
  const b = Number(input.b);
3029
3224
  let result: number;
@@ -3052,14 +3247,17 @@ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import
3052
3247
  text: JSON.stringify({
3053
3248
  operation: input.operation || "add",
3054
3249
  operands: { a: input.a, b: input.b },
3055
- result
3250
+ result,
3251
+ timestamp: new Date().toISOString(),
3252
+ server: "${projectName}"
3056
3253
  }, null, 2)
3057
3254
  }]
3058
3255
  };
3059
3256
  }
3060
3257
 
3258
+ // ECHO TOOL - Shows basic functionality
3061
3259
  @Tool({
3062
- description: "Echo a message back",
3260
+ description: "Echo a message back with timestamp",
3063
3261
  inputClass: EchoInput
3064
3262
  })
3065
3263
  async echo(input: EchoInput) {
@@ -3068,13 +3266,15 @@ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import
3068
3266
  type: "text" as const,
3069
3267
  text: JSON.stringify({
3070
3268
  echoed: input.message,
3071
- timestamp: new Date().toISOString()
3269
+ timestamp: new Date().toISOString(),
3270
+ server: "${projectName}"
3072
3271
  }, null, 2)
3073
3272
  }]
3074
3273
  };
3075
3274
  }
3076
3275
 
3077
- @Resource({ description: "Get server information" })
3276
+ // SERVER INFO RESOURCE - Shows resource capabilities
3277
+ @Resource({ description: "Get server information and health status" })
3078
3278
  async serverInfo() {
3079
3279
  return {
3080
3280
  contents: [{
@@ -3083,25 +3283,45 @@ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import
3083
3283
  text: JSON.stringify({
3084
3284
  name: "${projectName}",
3085
3285
  version: "1.0.0",
3086
- uptime: process.uptime()
3286
+ status: "healthy",
3287
+ uptime: Math.floor(process.uptime()),
3288
+ memory: {
3289
+ used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
3290
+ total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024)
3291
+ },
3292
+ features: [
3293
+ "Schema validation with decorators",
3294
+ "Type-safe tool definitions",
3295
+ "Resource endpoints",
3296
+ "Prompt templates"
3297
+ ],
3298
+ timestamp: new Date().toISOString()
3087
3299
  }, null, 2)
3088
3300
  }]
3089
3301
  };
3090
3302
  }
3091
3303
 
3092
- @Prompt({ description: "Generate a greeting prompt" })
3093
- async greeting(args: { name?: string }) {
3304
+ // WELCOME PROMPT - Shows prompt capabilities
3305
+ @Prompt({ description: "Generate a welcome prompt for the server" })
3306
+ async welcome(args: { name?: string }) {
3094
3307
  return {
3095
3308
  messages: [{
3096
3309
  role: "user" as const,
3097
3310
  content: {
3098
3311
  type: "text" as const,
3099
- text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
3100
- }
3101
- }]
3102
- };
3103
- }
3104
- }
3312
+ text: \`Welcome \${args.name || 'there'} to ${projectName}!
3313
+
3314
+ Your MCP server is running with these tools:
3315
+ - calculate: Perform arithmetic operations
3316
+ - echo: Echo messages back
3317
+ - serverInfo: Get server status and information
3318
+
3319
+ Try calling these tools to see LeanMCP in action!\`
3320
+ }
3321
+ }]
3322
+ };
3323
+ }
3324
+ }
3105
3325
  `, "getExampleServiceTemplate");
3106
3326
 
3107
3327
  // src/templates/main_ts_v1.ts
@@ -3111,6 +3331,14 @@ import { createHTTPServer } from "@leanmcp/core";
3111
3331
  // Load environment variables
3112
3332
  dotenv.config();
3113
3333
 
3334
+ console.log("Starting ${projectName} MCP Server...");
3335
+ console.log("Features included:");
3336
+ console.log(" Schema validation with decorators");
3337
+ console.log(" Resource endpoints");
3338
+ console.log(" Prompt templates");
3339
+ console.log(" Type-safe tool definitions");
3340
+ console.log("");
3341
+
3114
3342
  // Services are automatically discovered from ./mcp directory
3115
3343
  await createHTTPServer({
3116
3344
  name: "${projectName}",
@@ -3118,10 +3346,27 @@ await createHTTPServer({
3118
3346
  port: 3001,
3119
3347
  cors: true,
3120
3348
  logging: true${dashboardLine}
3121
- // stateless: false, // Enable stateful mode (uses DynamoDB on Lambda for session persistence)
3122
3349
  });
3123
3350
 
3124
- console.log("\\n${projectName} MCP Server");
3351
+ console.log("\\n${projectName} MCP Server is running!");
3352
+ console.log("\\nTry these commands to test your server:");
3353
+ console.log("");
3354
+ console.log("# Test calculation tool (schema validation)");
3355
+ console.log('curl -X POST http://localhost:3001/mcp \\\\');
3356
+ console.log(' -H "Content-Type: application/json" \\\\');
3357
+ console.log(' -d '{"method": "tools/call", "params": {"name": "calculate", "arguments": {"a": 10, "b": 5, "operation": "add"}}}'');
3358
+ console.log("");
3359
+ console.log("# Test echo tool");
3360
+ console.log('curl -X POST http://localhost:3001/mcp \\\\');
3361
+ console.log(' -H "Content-Type: application/json" \\\\');
3362
+ console.log(' -d '{"method": "tools/call", "params": {"name": "echo", "arguments": {"message": "Hello LeanMCP!"}}}'');
3363
+ console.log("");
3364
+ console.log("# Get server information (resource)");
3365
+ console.log('curl -X POST http://localhost:3001/mcp \\\\');
3366
+ console.log(' -H "Content-Type: application/json" \\\\');
3367
+ console.log(' -d '{"method": "resources/read", "params": {"uri": "server://info"}}'');
3368
+ console.log("");
3369
+ console.log("Ready to customize - add your own tools, resources, and prompts!");
3125
3370
  `, "getMainTsTemplate");
3126
3371
 
3127
3372
  // src/templates/service_index_v1.ts
@@ -3445,29 +3690,29 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
3445
3690
  ...options
3446
3691
  });
3447
3692
  const spinner = ora8(`Creating project ${projectName}...`).start();
3448
- const targetDir = path10.join(process.cwd(), projectName);
3449
- if (fs10.existsSync(targetDir)) {
3693
+ const targetDir = path11.join(process.cwd(), projectName);
3694
+ if (fs11.existsSync(targetDir)) {
3450
3695
  spinner.fail(`Folder ${projectName} already exists.`);
3451
3696
  process.exit(1);
3452
3697
  }
3453
- await fs10.mkdirp(targetDir);
3698
+ await fs11.mkdirp(targetDir);
3454
3699
  const isPython = options.python === true;
3455
3700
  if (isPython) {
3456
3701
  const requirements = getPythonRequirementsTemplate();
3457
- await fs10.writeFile(path10.join(targetDir, "requirements.txt"), requirements);
3702
+ await fs11.writeFile(path11.join(targetDir, "requirements.txt"), requirements);
3458
3703
  const mainPy = getPythonMainTemplate(projectName);
3459
- await fs10.writeFile(path10.join(targetDir, "main.py"), mainPy);
3460
- await fs10.writeFile(path10.join(targetDir, ".gitignore"), pythonGitignoreTemplate);
3704
+ await fs11.writeFile(path11.join(targetDir, "main.py"), mainPy);
3705
+ await fs11.writeFile(path11.join(targetDir, ".gitignore"), pythonGitignoreTemplate);
3461
3706
  const env = `# Server Configuration
3462
3707
  PORT=3001
3463
3708
 
3464
3709
  # Add your environment variables here
3465
3710
  `;
3466
- await fs10.writeFile(path10.join(targetDir, ".env"), env);
3711
+ await fs11.writeFile(path11.join(targetDir, ".env"), env);
3467
3712
  const readme = getPythonReadmeTemplate(projectName);
3468
- await fs10.writeFile(path10.join(targetDir, "README.md"), readme);
3713
+ await fs11.writeFile(path11.join(targetDir, "README.md"), readme);
3469
3714
  } else {
3470
- await fs10.mkdirp(path10.join(targetDir, "mcp", "example"));
3715
+ await fs11.mkdirp(path11.join(targetDir, "mcp", "example"));
3471
3716
  const pkg2 = {
3472
3717
  name: projectName,
3473
3718
  version: "1.0.0",
@@ -3503,7 +3748,7 @@ PORT=3001
3503
3748
  typescript: "^5.6.3"
3504
3749
  }
3505
3750
  };
3506
- await fs10.writeJSON(path10.join(targetDir, "package.json"), pkg2, {
3751
+ await fs11.writeJSON(path11.join(targetDir, "package.json"), pkg2, {
3507
3752
  spaces: 2
3508
3753
  });
3509
3754
  const tsconfig = {
@@ -3526,15 +3771,15 @@ PORT=3001
3526
3771
  "dist"
3527
3772
  ]
3528
3773
  };
3529
- await fs10.writeJSON(path10.join(targetDir, "tsconfig.json"), tsconfig, {
3774
+ await fs11.writeJSON(path11.join(targetDir, "tsconfig.json"), tsconfig, {
3530
3775
  spaces: 2
3531
3776
  });
3532
3777
  const dashboardLine = options.dashboard === false ? `
3533
3778
  dashboard: false, // Dashboard disabled via --no-dashboard` : "";
3534
3779
  const mainTs = getMainTsTemplate(projectName, dashboardLine);
3535
- await fs10.writeFile(path10.join(targetDir, "main.ts"), mainTs);
3780
+ await fs11.writeFile(path11.join(targetDir, "main.ts"), mainTs);
3536
3781
  const exampleServiceTs = getExampleServiceTemplate(projectName);
3537
- await fs10.writeFile(path10.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
3782
+ await fs11.writeFile(path11.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
3538
3783
  const gitignore = gitignoreTemplate;
3539
3784
  const env = `# Server Configuration
3540
3785
  PORT=3001
@@ -3542,10 +3787,10 @@ NODE_ENV=development
3542
3787
 
3543
3788
  # Add your environment variables here
3544
3789
  `;
3545
- await fs10.writeFile(path10.join(targetDir, ".gitignore"), gitignore);
3546
- await fs10.writeFile(path10.join(targetDir, ".env"), env);
3790
+ await fs11.writeFile(path11.join(targetDir, ".gitignore"), gitignore);
3791
+ await fs11.writeFile(path11.join(targetDir, ".env"), env);
3547
3792
  const readme = getReadmeTemplate(projectName);
3548
- await fs10.writeFile(path10.join(targetDir, "README.md"), readme);
3793
+ await fs11.writeFile(path11.join(targetDir, "README.md"), readme);
3549
3794
  }
3550
3795
  spinner.succeed(`Project ${projectName} created!`);
3551
3796
  logger.log("\nSuccess! Your MCP server is ready.\n", chalk.green);
@@ -3669,20 +3914,20 @@ NODE_ENV=development
3669
3914
  });
3670
3915
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
3671
3916
  const cwd = process.cwd();
3672
- const mcpDir = path10.join(cwd, "mcp");
3673
- if (!fs10.existsSync(path10.join(cwd, "main.ts"))) {
3917
+ const mcpDir = path11.join(cwd, "mcp");
3918
+ if (!fs11.existsSync(path11.join(cwd, "main.ts"))) {
3674
3919
  logger.log("ERROR: Not a LeanMCP project (main.ts missing).", chalk.red);
3675
3920
  process.exit(1);
3676
3921
  }
3677
- const serviceDir = path10.join(mcpDir, serviceName);
3678
- const serviceFile = path10.join(serviceDir, "index.ts");
3679
- if (fs10.existsSync(serviceDir)) {
3922
+ const serviceDir = path11.join(mcpDir, serviceName);
3923
+ const serviceFile = path11.join(serviceDir, "index.ts");
3924
+ if (fs11.existsSync(serviceDir)) {
3680
3925
  logger.log(`ERROR: Service ${serviceName} already exists.`, chalk.red);
3681
3926
  process.exit(1);
3682
3927
  }
3683
- await fs10.mkdirp(serviceDir);
3928
+ await fs11.mkdirp(serviceDir);
3684
3929
  const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
3685
- await fs10.writeFile(serviceFile, indexTs);
3930
+ await fs11.writeFile(serviceFile, indexTs);
3686
3931
  logger.log(`\\nCreated new service: ${chalk.bold(serviceName)}`, chalk.green);
3687
3932
  logger.log(` File: mcp/${serviceName}/index.ts`, chalk.gray);
3688
3933
  logger.log(` Tool: greet`, chalk.gray);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/cli",
3
- "version": "0.5.4",
3
+ "version": "0.5.5-alpha.28.7306df7",
4
4
  "description": "Command-line interface for scaffolding LeanMCP projects",
5
5
  "bin": {
6
6
  "leanmcp": "bin/leanmcp.js"
@@ -40,7 +40,8 @@
40
40
  "postcss": "^8.4.32",
41
41
  "tailwindcss": "^3.4.0",
42
42
  "vite": "^5.4.0",
43
- "vite-plugin-singlefile": "^2.3.0"
43
+ "vite-plugin-singlefile": "^2.3.0",
44
+ "ts-morph": "^24.0.0"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@types/archiver": "^6.0.3",
@@ -70,4 +71,4 @@
70
71
  "publishConfig": {
71
72
  "access": "public"
72
73
  }
73
- }
74
+ }