@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 +21 -21
- package/bin/leanmcp.js +0 -0
- package/dist/index.js +337 -92
- package/package.json +4 -3
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
|
|
7
|
-
import
|
|
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
|
|
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
|
|
923
|
-
import
|
|
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
|
|
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
|
|
1029
|
-
import
|
|
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 =
|
|
1044
|
-
var CONFIG_FILE =
|
|
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
|
|
1048
|
-
return await
|
|
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
|
|
1057
|
-
await
|
|
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
|
|
1265
|
-
import
|
|
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
|
|
1449
|
-
import
|
|
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 =
|
|
1513
|
-
if (!await
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
1806
|
+
const configPath = path9.join(projectPath, LEANMCP_CONFIG_DIR, LEANMCP_CONFIG_FILE);
|
|
1616
1807
|
try {
|
|
1617
|
-
if (await
|
|
1618
|
-
const config = await
|
|
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 =
|
|
1630
|
-
const configPath =
|
|
1631
|
-
await
|
|
1632
|
-
await
|
|
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 =
|
|
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 =
|
|
1751
|
-
if (!await
|
|
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
|
|
1756
|
-
const hasPackageJson = await
|
|
1757
|
-
const hasMainPy = await
|
|
1758
|
-
const hasRequirementsTxt = await
|
|
1759
|
-
const hasPyprojectToml = await
|
|
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 =
|
|
2022
|
+
let folderName = path9.basename(absolutePath);
|
|
1832
2023
|
if (hasPackageJson) {
|
|
1833
2024
|
try {
|
|
1834
|
-
const pkg2 = await
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
2311
|
-
import
|
|
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 =
|
|
2535
|
+
const configPath = path10.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
|
|
2345
2536
|
try {
|
|
2346
|
-
if (await
|
|
2347
|
-
const config = await
|
|
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 =
|
|
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
|
-
*
|
|
3181
|
+
* ${projectName} - Production-ready MCP server example
|
|
2991
3182
|
*
|
|
2992
|
-
* This
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3093
|
-
|
|
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: \`
|
|
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 =
|
|
3449
|
-
if (
|
|
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
|
|
3698
|
+
await fs11.mkdirp(targetDir);
|
|
3454
3699
|
const isPython = options.python === true;
|
|
3455
3700
|
if (isPython) {
|
|
3456
3701
|
const requirements = getPythonRequirementsTemplate();
|
|
3457
|
-
await
|
|
3702
|
+
await fs11.writeFile(path11.join(targetDir, "requirements.txt"), requirements);
|
|
3458
3703
|
const mainPy = getPythonMainTemplate(projectName);
|
|
3459
|
-
await
|
|
3460
|
-
await
|
|
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
|
|
3711
|
+
await fs11.writeFile(path11.join(targetDir, ".env"), env);
|
|
3467
3712
|
const readme = getPythonReadmeTemplate(projectName);
|
|
3468
|
-
await
|
|
3713
|
+
await fs11.writeFile(path11.join(targetDir, "README.md"), readme);
|
|
3469
3714
|
} else {
|
|
3470
|
-
await
|
|
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
|
|
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
|
|
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
|
|
3780
|
+
await fs11.writeFile(path11.join(targetDir, "main.ts"), mainTs);
|
|
3536
3781
|
const exampleServiceTs = getExampleServiceTemplate(projectName);
|
|
3537
|
-
await
|
|
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
|
|
3546
|
-
await
|
|
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
|
|
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 =
|
|
3673
|
-
if (!
|
|
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 =
|
|
3678
|
-
const serviceFile =
|
|
3679
|
-
if (
|
|
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
|
|
3928
|
+
await fs11.mkdirp(serviceDir);
|
|
3684
3929
|
const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
|
|
3685
|
-
await
|
|
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.
|
|
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
|
+
}
|