@leanmcp/cli 0.4.5 → 0.5.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 +921 -118
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,11 +3,11 @@ 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
|
|
6
|
+
import fs10 from "fs-extra";
|
|
7
|
+
import path10 from "path";
|
|
8
|
+
import ora8 from "ora";
|
|
9
9
|
import { createRequire } from "module";
|
|
10
|
-
import { confirm as
|
|
10
|
+
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
11
11
|
import { spawn as spawn4 } from "child_process";
|
|
12
12
|
|
|
13
13
|
// src/commands/dev.ts
|
|
@@ -1261,8 +1261,8 @@ __name(whoamiCommand, "whoamiCommand");
|
|
|
1261
1261
|
|
|
1262
1262
|
// src/commands/deploy.ts
|
|
1263
1263
|
import ora5 from "ora";
|
|
1264
|
-
import
|
|
1265
|
-
import
|
|
1264
|
+
import path8 from "path";
|
|
1265
|
+
import fs8 from "fs-extra";
|
|
1266
1266
|
import os3 from "os";
|
|
1267
1267
|
import archiver from "archiver";
|
|
1268
1268
|
import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
|
|
@@ -1444,6 +1444,128 @@ function generateProjectName() {
|
|
|
1444
1444
|
}
|
|
1445
1445
|
__name(generateProjectName, "generateProjectName");
|
|
1446
1446
|
|
|
1447
|
+
// src/utils/env-parser.ts
|
|
1448
|
+
import fs7 from "fs-extra";
|
|
1449
|
+
import path7 from "path";
|
|
1450
|
+
var RESERVED_ENV_KEYS = [
|
|
1451
|
+
"AWS_REGION",
|
|
1452
|
+
"AWS_ACCESS_KEY_ID",
|
|
1453
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
1454
|
+
"AWS_SESSION_TOKEN",
|
|
1455
|
+
"AWS_LAMBDA_FUNCTION_NAME",
|
|
1456
|
+
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE",
|
|
1457
|
+
"AWS_LAMBDA_FUNCTION_VERSION",
|
|
1458
|
+
"AWS_LAMBDA_LOG_GROUP_NAME",
|
|
1459
|
+
"AWS_LAMBDA_LOG_STREAM_NAME",
|
|
1460
|
+
"_HANDLER",
|
|
1461
|
+
"_X_AMZN_TRACE_ID"
|
|
1462
|
+
];
|
|
1463
|
+
var SYSTEM_ENV_KEYS = [
|
|
1464
|
+
"PORT",
|
|
1465
|
+
"AWS_LWA_PORT",
|
|
1466
|
+
"AWS_LWA_INVOKE_MODE",
|
|
1467
|
+
"AWS_LWA_READINESS_CHECK_MIN_UNHEALTHY_STATUS"
|
|
1468
|
+
];
|
|
1469
|
+
function parseEnvVar(input3) {
|
|
1470
|
+
if (!input3 || typeof input3 !== "string") {
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
const trimmed = input3.trim();
|
|
1474
|
+
const equalIndex = trimmed.indexOf("=");
|
|
1475
|
+
if (equalIndex === -1) {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
const key = trimmed.substring(0, equalIndex).trim();
|
|
1479
|
+
let value = trimmed.substring(equalIndex + 1);
|
|
1480
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1481
|
+
value = value.slice(1, -1);
|
|
1482
|
+
}
|
|
1483
|
+
if (!isValidEnvKey(key)) {
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
return {
|
|
1487
|
+
key,
|
|
1488
|
+
value
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
__name(parseEnvVar, "parseEnvVar");
|
|
1492
|
+
function parseEnvFile(content) {
|
|
1493
|
+
const result = {};
|
|
1494
|
+
if (!content || typeof content !== "string") {
|
|
1495
|
+
return result;
|
|
1496
|
+
}
|
|
1497
|
+
const lines = content.split(/\r?\n/);
|
|
1498
|
+
for (const line of lines) {
|
|
1499
|
+
const trimmed = line.trim();
|
|
1500
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
const parsed = parseEnvVar(trimmed);
|
|
1504
|
+
if (parsed) {
|
|
1505
|
+
result[parsed.key] = parsed.value;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
return result;
|
|
1509
|
+
}
|
|
1510
|
+
__name(parseEnvFile, "parseEnvFile");
|
|
1511
|
+
async function loadEnvFile(filePath) {
|
|
1512
|
+
const absolutePath = path7.resolve(filePath);
|
|
1513
|
+
if (!await fs7.pathExists(absolutePath)) {
|
|
1514
|
+
throw new Error(`Env file not found: ${absolutePath}`);
|
|
1515
|
+
}
|
|
1516
|
+
const content = await fs7.readFile(absolutePath, "utf-8");
|
|
1517
|
+
return parseEnvFile(content);
|
|
1518
|
+
}
|
|
1519
|
+
__name(loadEnvFile, "loadEnvFile");
|
|
1520
|
+
async function writeEnvFile(filePath, vars) {
|
|
1521
|
+
const absolutePath = path7.resolve(filePath);
|
|
1522
|
+
const lines = [
|
|
1523
|
+
"# Environment variables",
|
|
1524
|
+
`# Generated by leanmcp CLI at ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1525
|
+
""
|
|
1526
|
+
];
|
|
1527
|
+
const sortedKeys = Object.keys(vars).sort();
|
|
1528
|
+
for (const key of sortedKeys) {
|
|
1529
|
+
const value = vars[key];
|
|
1530
|
+
if (value.includes(" ") || value.includes("#") || value.includes('"')) {
|
|
1531
|
+
lines.push(`${key}="${value.replace(/"/g, '\\"')}"`);
|
|
1532
|
+
} else {
|
|
1533
|
+
lines.push(`${key}=${value}`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
lines.push("");
|
|
1537
|
+
await fs7.writeFile(absolutePath, lines.join("\n"));
|
|
1538
|
+
}
|
|
1539
|
+
__name(writeEnvFile, "writeEnvFile");
|
|
1540
|
+
function isValidEnvKey(key) {
|
|
1541
|
+
if (!key || typeof key !== "string") {
|
|
1542
|
+
return false;
|
|
1543
|
+
}
|
|
1544
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
|
|
1545
|
+
}
|
|
1546
|
+
__name(isValidEnvKey, "isValidEnvKey");
|
|
1547
|
+
function isReservedKey(key) {
|
|
1548
|
+
return RESERVED_ENV_KEYS.includes(key);
|
|
1549
|
+
}
|
|
1550
|
+
__name(isReservedKey, "isReservedKey");
|
|
1551
|
+
function isSystemKey(key) {
|
|
1552
|
+
return SYSTEM_ENV_KEYS.includes(key);
|
|
1553
|
+
}
|
|
1554
|
+
__name(isSystemKey, "isSystemKey");
|
|
1555
|
+
function formatEnvVarsForDisplay(vars, reveal = false) {
|
|
1556
|
+
const keys = Object.keys(vars).sort();
|
|
1557
|
+
if (keys.length === 0) {
|
|
1558
|
+
return " (no environment variables)";
|
|
1559
|
+
}
|
|
1560
|
+
const lines = keys.map((key) => {
|
|
1561
|
+
const value = reveal ? vars[key] : "***";
|
|
1562
|
+
const keyType = isSystemKey(key) ? " (system)" : "";
|
|
1563
|
+
return ` ${key}=${value}${keyType}`;
|
|
1564
|
+
});
|
|
1565
|
+
return lines.join("\n");
|
|
1566
|
+
}
|
|
1567
|
+
__name(formatEnvVarsForDisplay, "formatEnvVarsForDisplay");
|
|
1568
|
+
|
|
1447
1569
|
// src/commands/deploy.ts
|
|
1448
1570
|
var DEBUG_MODE3 = false;
|
|
1449
1571
|
function setDeployDebugMode(enabled) {
|
|
@@ -1490,10 +1612,10 @@ var API_ENDPOINTS = {
|
|
|
1490
1612
|
var LEANMCP_CONFIG_DIR = ".leanmcp";
|
|
1491
1613
|
var LEANMCP_CONFIG_FILE = "config.json";
|
|
1492
1614
|
async function readLeanMCPConfig(projectPath) {
|
|
1493
|
-
const configPath =
|
|
1615
|
+
const configPath = path8.join(projectPath, LEANMCP_CONFIG_DIR, LEANMCP_CONFIG_FILE);
|
|
1494
1616
|
try {
|
|
1495
|
-
if (await
|
|
1496
|
-
const config = await
|
|
1617
|
+
if (await fs8.pathExists(configPath)) {
|
|
1618
|
+
const config = await fs8.readJSON(configPath);
|
|
1497
1619
|
debug3("Found existing .leanmcp config:", config);
|
|
1498
1620
|
return config;
|
|
1499
1621
|
}
|
|
@@ -1504,10 +1626,10 @@ async function readLeanMCPConfig(projectPath) {
|
|
|
1504
1626
|
}
|
|
1505
1627
|
__name(readLeanMCPConfig, "readLeanMCPConfig");
|
|
1506
1628
|
async function writeLeanMCPConfig(projectPath, config) {
|
|
1507
|
-
const configDir =
|
|
1508
|
-
const configPath =
|
|
1509
|
-
await
|
|
1510
|
-
await
|
|
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, {
|
|
1511
1633
|
spaces: 2
|
|
1512
1634
|
});
|
|
1513
1635
|
debug3("Saved .leanmcp config:", config);
|
|
@@ -1515,7 +1637,7 @@ async function writeLeanMCPConfig(projectPath, config) {
|
|
|
1515
1637
|
__name(writeLeanMCPConfig, "writeLeanMCPConfig");
|
|
1516
1638
|
async function createZipArchive(folderPath, outputPath) {
|
|
1517
1639
|
return new Promise((resolve, reject) => {
|
|
1518
|
-
const output =
|
|
1640
|
+
const output = fs8.createWriteStream(outputPath);
|
|
1519
1641
|
const archive = archiver("zip", {
|
|
1520
1642
|
zlib: {
|
|
1521
1643
|
level: 9
|
|
@@ -1613,16 +1735,21 @@ async function deployCommand(folderPath, options = {}) {
|
|
|
1613
1735
|
}
|
|
1614
1736
|
const apiUrl = await getApiUrl();
|
|
1615
1737
|
debug3("API URL:", apiUrl);
|
|
1616
|
-
const absolutePath =
|
|
1617
|
-
if (!await
|
|
1738
|
+
const absolutePath = path8.resolve(process.cwd(), folderPath);
|
|
1739
|
+
if (!await fs8.pathExists(absolutePath)) {
|
|
1618
1740
|
logger.error(`Folder not found: ${absolutePath}`);
|
|
1619
1741
|
process.exit(1);
|
|
1620
1742
|
}
|
|
1621
|
-
const hasMainTs = await
|
|
1622
|
-
const hasPackageJson = await
|
|
1623
|
-
|
|
1743
|
+
const hasMainTs = await fs8.pathExists(path8.join(absolutePath, "main.ts"));
|
|
1744
|
+
const hasPackageJson = await fs8.pathExists(path8.join(absolutePath, "package.json"));
|
|
1745
|
+
const hasMainPy = await fs8.pathExists(path8.join(absolutePath, "main.py"));
|
|
1746
|
+
const hasRequirementsTxt = await fs8.pathExists(path8.join(absolutePath, "requirements.txt"));
|
|
1747
|
+
const hasPyprojectToml = await fs8.pathExists(path8.join(absolutePath, "pyproject.toml"));
|
|
1748
|
+
const isNodeProject = hasMainTs || hasPackageJson;
|
|
1749
|
+
const isPythonProject = hasMainPy || hasRequirementsTxt || hasPyprojectToml;
|
|
1750
|
+
if (!isNodeProject && !isPythonProject) {
|
|
1624
1751
|
logger.error("Not a valid project folder.");
|
|
1625
|
-
logger.gray("Expected main.ts
|
|
1752
|
+
logger.gray("Expected one of: main.ts, package.json, main.py, requirements.txt, or pyproject.toml\n");
|
|
1626
1753
|
process.exit(1);
|
|
1627
1754
|
}
|
|
1628
1755
|
const existingConfig = await readLeanMCPConfig(absolutePath);
|
|
@@ -1689,10 +1816,10 @@ Generated project name: ${chalk.bold(projectName)}
|
|
|
1689
1816
|
} catch (e) {
|
|
1690
1817
|
debug3("Could not fetch existing projects");
|
|
1691
1818
|
}
|
|
1692
|
-
let folderName =
|
|
1819
|
+
let folderName = path8.basename(absolutePath);
|
|
1693
1820
|
if (hasPackageJson) {
|
|
1694
1821
|
try {
|
|
1695
|
-
const pkg2 = await
|
|
1822
|
+
const pkg2 = await fs8.readJSON(path8.join(absolutePath, "package.json"));
|
|
1696
1823
|
folderName = pkg2.name || folderName;
|
|
1697
1824
|
} catch (e) {
|
|
1698
1825
|
}
|
|
@@ -1843,7 +1970,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
1843
1970
|
}
|
|
1844
1971
|
const uploadSpinner = ora5("Packaging and uploading...").start();
|
|
1845
1972
|
try {
|
|
1846
|
-
const tempZip =
|
|
1973
|
+
const tempZip = path8.join(os3.tmpdir(), `leanmcp-${Date.now()}.zip`);
|
|
1847
1974
|
const zipSize = await createZipArchive(absolutePath, tempZip);
|
|
1848
1975
|
uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
|
|
1849
1976
|
debug3("Step 2a: Getting upload URL for project:", projectId);
|
|
@@ -1870,7 +1997,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
1870
1997
|
throw new Error("Backend did not return upload URL");
|
|
1871
1998
|
}
|
|
1872
1999
|
debug3("Step 2b: Uploading to S3...");
|
|
1873
|
-
const zipBuffer = await
|
|
2000
|
+
const zipBuffer = await fs8.readFile(tempZip);
|
|
1874
2001
|
const s3Response = await fetch(uploadUrl, {
|
|
1875
2002
|
method: "PUT",
|
|
1876
2003
|
body: zipBuffer,
|
|
@@ -1893,7 +2020,7 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
1893
2020
|
s3Location
|
|
1894
2021
|
})
|
|
1895
2022
|
});
|
|
1896
|
-
await
|
|
2023
|
+
await fs8.remove(tempZip);
|
|
1897
2024
|
uploadSpinner.succeed("Project uploaded");
|
|
1898
2025
|
} catch (error) {
|
|
1899
2026
|
uploadSpinner.fail("Failed to upload");
|
|
@@ -2123,8 +2250,8 @@ async function projectsDeleteCommand(projectId, options = {}) {
|
|
|
2123
2250
|
process.exit(1);
|
|
2124
2251
|
}
|
|
2125
2252
|
if (!options.force) {
|
|
2126
|
-
const { confirm:
|
|
2127
|
-
const shouldDelete = await
|
|
2253
|
+
const { confirm: confirm5 } = await import("@inquirer/prompts");
|
|
2254
|
+
const shouldDelete = await confirm5({
|
|
2128
2255
|
message: `Are you sure you want to delete project ${projectId}?`,
|
|
2129
2256
|
default: false
|
|
2130
2257
|
});
|
|
@@ -2159,6 +2286,383 @@ ${error instanceof Error ? error.message : String(error)}`);
|
|
|
2159
2286
|
}
|
|
2160
2287
|
__name(projectsDeleteCommand, "projectsDeleteCommand");
|
|
2161
2288
|
|
|
2289
|
+
// src/commands/env.ts
|
|
2290
|
+
import ora7 from "ora";
|
|
2291
|
+
import path9 from "path";
|
|
2292
|
+
import fs9 from "fs-extra";
|
|
2293
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
2294
|
+
var DEBUG_MODE4 = false;
|
|
2295
|
+
function setEnvDebugMode(enabled) {
|
|
2296
|
+
DEBUG_MODE4 = enabled;
|
|
2297
|
+
}
|
|
2298
|
+
__name(setEnvDebugMode, "setEnvDebugMode");
|
|
2299
|
+
function debug4(message, ...args) {
|
|
2300
|
+
if (DEBUG_MODE4) {
|
|
2301
|
+
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
__name(debug4, "debug");
|
|
2305
|
+
async function debugFetch2(url, options = {}) {
|
|
2306
|
+
debug4(`HTTP ${options.method || "GET"} ${url}`);
|
|
2307
|
+
if (options.body && typeof options.body === "string") {
|
|
2308
|
+
try {
|
|
2309
|
+
const body = JSON.parse(options.body);
|
|
2310
|
+
debug4("Request body:", JSON.stringify(body, null, 2));
|
|
2311
|
+
} catch {
|
|
2312
|
+
debug4("Request body:", options.body);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
const startTime = Date.now();
|
|
2316
|
+
const response = await fetch(url, options);
|
|
2317
|
+
const duration = Date.now() - startTime;
|
|
2318
|
+
debug4(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
|
|
2319
|
+
return response;
|
|
2320
|
+
}
|
|
2321
|
+
__name(debugFetch2, "debugFetch");
|
|
2322
|
+
var LEANMCP_CONFIG_DIR2 = ".leanmcp";
|
|
2323
|
+
var LEANMCP_CONFIG_FILE2 = "config.json";
|
|
2324
|
+
async function readLeanMCPConfig2(projectPath) {
|
|
2325
|
+
const configPath = path9.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
|
|
2326
|
+
try {
|
|
2327
|
+
if (await fs9.pathExists(configPath)) {
|
|
2328
|
+
const config = await fs9.readJSON(configPath);
|
|
2329
|
+
debug4("Found existing .leanmcp config:", config);
|
|
2330
|
+
return config;
|
|
2331
|
+
}
|
|
2332
|
+
} catch (e) {
|
|
2333
|
+
debug4("Could not read .leanmcp config:", e);
|
|
2334
|
+
}
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
__name(readLeanMCPConfig2, "readLeanMCPConfig");
|
|
2338
|
+
async function getDeploymentContext(folderPath) {
|
|
2339
|
+
const apiKey = await getApiKey();
|
|
2340
|
+
if (!apiKey) {
|
|
2341
|
+
logger.error("Not logged in.");
|
|
2342
|
+
logger.gray("Run `leanmcp login` first to authenticate.\n");
|
|
2343
|
+
return null;
|
|
2344
|
+
}
|
|
2345
|
+
const apiUrl = await getApiUrl();
|
|
2346
|
+
const absolutePath = path9.resolve(process.cwd(), folderPath);
|
|
2347
|
+
const config = await readLeanMCPConfig2(absolutePath);
|
|
2348
|
+
if (!config) {
|
|
2349
|
+
logger.error("No deployment found.");
|
|
2350
|
+
logger.gray(`No .leanmcp/config.json found in ${absolutePath}`);
|
|
2351
|
+
logger.gray("Deploy first with: leanmcp deploy .\n");
|
|
2352
|
+
return null;
|
|
2353
|
+
}
|
|
2354
|
+
if (!config.deploymentId) {
|
|
2355
|
+
logger.error("Deployment ID not found in config.");
|
|
2356
|
+
logger.gray("Please redeploy with: leanmcp deploy .\n");
|
|
2357
|
+
return null;
|
|
2358
|
+
}
|
|
2359
|
+
return {
|
|
2360
|
+
apiKey,
|
|
2361
|
+
apiUrl,
|
|
2362
|
+
config
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
__name(getDeploymentContext, "getDeploymentContext");
|
|
2366
|
+
async function envListCommand(folderPath, options = {}) {
|
|
2367
|
+
logger.info("\nLeanMCP Environment Variables\n");
|
|
2368
|
+
const context = await getDeploymentContext(folderPath);
|
|
2369
|
+
if (!context) {
|
|
2370
|
+
process.exit(1);
|
|
2371
|
+
}
|
|
2372
|
+
const { apiKey, apiUrl, config } = context;
|
|
2373
|
+
const spinner = ora7("Fetching environment variables...").start();
|
|
2374
|
+
try {
|
|
2375
|
+
const reveal = options.reveal ? "?reveal=true" : "";
|
|
2376
|
+
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env${reveal}`, {
|
|
2377
|
+
headers: {
|
|
2378
|
+
"Authorization": `Bearer ${apiKey}`
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
if (!response.ok) {
|
|
2382
|
+
const error = await response.text();
|
|
2383
|
+
throw new Error(`Failed to fetch env vars: ${error}`);
|
|
2384
|
+
}
|
|
2385
|
+
const envVars = await response.json();
|
|
2386
|
+
spinner.succeed("Environment variables retrieved");
|
|
2387
|
+
logger.info(`
|
|
2388
|
+
Project: ${config.projectName}`);
|
|
2389
|
+
logger.gray(`Deployment: ${config.deploymentId.substring(0, 8)}...`);
|
|
2390
|
+
logger.gray(`URL: ${config.url}
|
|
2391
|
+
`);
|
|
2392
|
+
const formatted = formatEnvVarsForDisplay(envVars, options.reveal);
|
|
2393
|
+
logger.log(formatted);
|
|
2394
|
+
logger.log("");
|
|
2395
|
+
if (!options.reveal) {
|
|
2396
|
+
logger.gray("Use --reveal to show actual values\n");
|
|
2397
|
+
}
|
|
2398
|
+
} catch (error) {
|
|
2399
|
+
spinner.fail("Failed to fetch environment variables");
|
|
2400
|
+
logger.error(`
|
|
2401
|
+
${error instanceof Error ? error.message : String(error)}`);
|
|
2402
|
+
process.exit(1);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
__name(envListCommand, "envListCommand");
|
|
2406
|
+
async function envSetCommand(keyValue, folderPath, options = {}) {
|
|
2407
|
+
const context = await getDeploymentContext(folderPath);
|
|
2408
|
+
if (!context) {
|
|
2409
|
+
process.exit(1);
|
|
2410
|
+
}
|
|
2411
|
+
const { apiKey, apiUrl, config } = context;
|
|
2412
|
+
let variables = {};
|
|
2413
|
+
if (options.file) {
|
|
2414
|
+
const spinner2 = ora7(`Loading from ${options.file}...`).start();
|
|
2415
|
+
try {
|
|
2416
|
+
variables = await loadEnvFile(options.file);
|
|
2417
|
+
spinner2.succeed(`Loaded ${Object.keys(variables).length} variable(s) from ${options.file}`);
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
spinner2.fail(`Failed to load ${options.file}`);
|
|
2420
|
+
logger.error(`
|
|
2421
|
+
${error instanceof Error ? error.message : String(error)}`);
|
|
2422
|
+
process.exit(1);
|
|
2423
|
+
}
|
|
2424
|
+
} else {
|
|
2425
|
+
const parsed = parseEnvVar(keyValue);
|
|
2426
|
+
if (!parsed) {
|
|
2427
|
+
logger.error("Invalid format. Expected: KEY=VALUE");
|
|
2428
|
+
logger.gray("Example: leanmcp env set API_KEY=secret123\n");
|
|
2429
|
+
process.exit(1);
|
|
2430
|
+
}
|
|
2431
|
+
if (isReservedKey(parsed.key)) {
|
|
2432
|
+
logger.error(`Cannot set reserved key: ${parsed.key}`);
|
|
2433
|
+
logger.gray("This key is managed by AWS Lambda.\n");
|
|
2434
|
+
process.exit(1);
|
|
2435
|
+
}
|
|
2436
|
+
if (isSystemKey(parsed.key) && !options.force) {
|
|
2437
|
+
logger.warn(`Warning: ${parsed.key} is a system key.`);
|
|
2438
|
+
const shouldContinue = await confirm3({
|
|
2439
|
+
message: "Are you sure you want to modify it?",
|
|
2440
|
+
default: false
|
|
2441
|
+
});
|
|
2442
|
+
if (!shouldContinue) {
|
|
2443
|
+
logger.gray("\nCancelled.\n");
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
variables = {
|
|
2448
|
+
[parsed.key]: parsed.value
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
if (Object.keys(variables).length === 0) {
|
|
2452
|
+
logger.warn("No variables to set.\n");
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
const spinner = ora7("Updating environment variables...").start();
|
|
2456
|
+
try {
|
|
2457
|
+
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env`, {
|
|
2458
|
+
method: "PUT",
|
|
2459
|
+
headers: {
|
|
2460
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
2461
|
+
"Content-Type": "application/json"
|
|
2462
|
+
},
|
|
2463
|
+
body: JSON.stringify({
|
|
2464
|
+
variables
|
|
2465
|
+
})
|
|
2466
|
+
});
|
|
2467
|
+
if (!response.ok) {
|
|
2468
|
+
const error = await response.text();
|
|
2469
|
+
throw new Error(`Failed to update env vars: ${error}`);
|
|
2470
|
+
}
|
|
2471
|
+
const result = await response.json();
|
|
2472
|
+
spinner.succeed("Environment variables updated");
|
|
2473
|
+
logger.info(`
|
|
2474
|
+
${result.message || "Variables updated successfully"}`);
|
|
2475
|
+
logger.gray("Note: Lambda will cold-start on next invocation.\n");
|
|
2476
|
+
} catch (error) {
|
|
2477
|
+
spinner.fail("Failed to update environment variables");
|
|
2478
|
+
logger.error(`
|
|
2479
|
+
${error instanceof Error ? error.message : String(error)}`);
|
|
2480
|
+
process.exit(1);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
__name(envSetCommand, "envSetCommand");
|
|
2484
|
+
async function envGetCommand(key, folderPath, options = {}) {
|
|
2485
|
+
const context = await getDeploymentContext(folderPath);
|
|
2486
|
+
if (!context) {
|
|
2487
|
+
process.exit(1);
|
|
2488
|
+
}
|
|
2489
|
+
const { apiKey, apiUrl, config } = context;
|
|
2490
|
+
try {
|
|
2491
|
+
const reveal = options.reveal ? "?reveal=true" : "";
|
|
2492
|
+
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env${reveal}`, {
|
|
2493
|
+
headers: {
|
|
2494
|
+
"Authorization": `Bearer ${apiKey}`
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
if (!response.ok) {
|
|
2498
|
+
const error = await response.text();
|
|
2499
|
+
throw new Error(`Failed to fetch env vars: ${error}`);
|
|
2500
|
+
}
|
|
2501
|
+
const envVars = await response.json();
|
|
2502
|
+
if (key in envVars) {
|
|
2503
|
+
const value = envVars[key];
|
|
2504
|
+
logger.log(`${key}=${value}`);
|
|
2505
|
+
} else {
|
|
2506
|
+
logger.warn(`Variable '${key}' not found.`);
|
|
2507
|
+
process.exit(1);
|
|
2508
|
+
}
|
|
2509
|
+
} catch (error) {
|
|
2510
|
+
logger.error(`
|
|
2511
|
+
${error instanceof Error ? error.message : String(error)}`);
|
|
2512
|
+
process.exit(1);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
__name(envGetCommand, "envGetCommand");
|
|
2516
|
+
async function envRemoveCommand(key, folderPath, options = {}) {
|
|
2517
|
+
const context = await getDeploymentContext(folderPath);
|
|
2518
|
+
if (!context) {
|
|
2519
|
+
process.exit(1);
|
|
2520
|
+
}
|
|
2521
|
+
const { apiKey, apiUrl, config } = context;
|
|
2522
|
+
if (isReservedKey(key)) {
|
|
2523
|
+
logger.error(`Cannot remove reserved key: ${key}`);
|
|
2524
|
+
process.exit(1);
|
|
2525
|
+
}
|
|
2526
|
+
if (isSystemKey(key)) {
|
|
2527
|
+
logger.error(`Cannot remove system key: ${key}`);
|
|
2528
|
+
process.exit(1);
|
|
2529
|
+
}
|
|
2530
|
+
if (!options.force) {
|
|
2531
|
+
const shouldDelete = await confirm3({
|
|
2532
|
+
message: `Remove environment variable '${key}'?`,
|
|
2533
|
+
default: false
|
|
2534
|
+
});
|
|
2535
|
+
if (!shouldDelete) {
|
|
2536
|
+
logger.gray("\nCancelled.\n");
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
const spinner = ora7(`Removing ${key}...`).start();
|
|
2541
|
+
try {
|
|
2542
|
+
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env/${key}`, {
|
|
2543
|
+
method: "DELETE",
|
|
2544
|
+
headers: {
|
|
2545
|
+
"Authorization": `Bearer ${apiKey}`
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
if (!response.ok) {
|
|
2549
|
+
const error = await response.text();
|
|
2550
|
+
throw new Error(`Failed to remove env var: ${error}`);
|
|
2551
|
+
}
|
|
2552
|
+
spinner.succeed(`Removed ${key}`);
|
|
2553
|
+
logger.gray("Note: Lambda will cold-start on next invocation.\n");
|
|
2554
|
+
} catch (error) {
|
|
2555
|
+
spinner.fail(`Failed to remove ${key}`);
|
|
2556
|
+
logger.error(`
|
|
2557
|
+
${error instanceof Error ? error.message : String(error)}`);
|
|
2558
|
+
process.exit(1);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
__name(envRemoveCommand, "envRemoveCommand");
|
|
2562
|
+
async function envPullCommand(folderPath, options = {}) {
|
|
2563
|
+
const context = await getDeploymentContext(folderPath);
|
|
2564
|
+
if (!context) {
|
|
2565
|
+
process.exit(1);
|
|
2566
|
+
}
|
|
2567
|
+
const { apiKey, apiUrl, config } = context;
|
|
2568
|
+
const outputFile = options.file || ".env.remote";
|
|
2569
|
+
const spinner = ora7("Fetching environment variables...").start();
|
|
2570
|
+
try {
|
|
2571
|
+
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env?reveal=true`, {
|
|
2572
|
+
headers: {
|
|
2573
|
+
"Authorization": `Bearer ${apiKey}`
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
if (!response.ok) {
|
|
2577
|
+
const error = await response.text();
|
|
2578
|
+
throw new Error(`Failed to fetch env vars: ${error}`);
|
|
2579
|
+
}
|
|
2580
|
+
const envVars = await response.json();
|
|
2581
|
+
const userVars = {};
|
|
2582
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
2583
|
+
if (!isSystemKey(key)) {
|
|
2584
|
+
userVars[key] = value;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
spinner.text = `Writing to ${outputFile}...`;
|
|
2588
|
+
await writeEnvFile(outputFile, userVars);
|
|
2589
|
+
spinner.succeed(`Saved ${Object.keys(userVars).length} variable(s) to ${outputFile}`);
|
|
2590
|
+
logger.gray(`
|
|
2591
|
+
System variables (PORT, AWS_LWA_*) are not included.
|
|
2592
|
+
`);
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
spinner.fail("Failed to pull environment variables");
|
|
2595
|
+
logger.error(`
|
|
2596
|
+
${error instanceof Error ? error.message : String(error)}`);
|
|
2597
|
+
process.exit(1);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
__name(envPullCommand, "envPullCommand");
|
|
2601
|
+
async function envPushCommand(folderPath, options = {}) {
|
|
2602
|
+
const context = await getDeploymentContext(folderPath);
|
|
2603
|
+
if (!context) {
|
|
2604
|
+
process.exit(1);
|
|
2605
|
+
}
|
|
2606
|
+
const { apiKey, apiUrl, config } = context;
|
|
2607
|
+
const inputFile = options.file || ".env";
|
|
2608
|
+
const loadSpinner = ora7(`Loading from ${inputFile}...`).start();
|
|
2609
|
+
let variables;
|
|
2610
|
+
try {
|
|
2611
|
+
variables = await loadEnvFile(inputFile);
|
|
2612
|
+
loadSpinner.succeed(`Loaded ${Object.keys(variables).length} variable(s) from ${inputFile}`);
|
|
2613
|
+
} catch (error) {
|
|
2614
|
+
loadSpinner.fail(`Failed to load ${inputFile}`);
|
|
2615
|
+
logger.error(`
|
|
2616
|
+
${error instanceof Error ? error.message : String(error)}`);
|
|
2617
|
+
process.exit(1);
|
|
2618
|
+
}
|
|
2619
|
+
if (Object.keys(variables).length === 0) {
|
|
2620
|
+
logger.warn("No variables found in file.\n");
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
logger.warn("\nThis will REPLACE ALL current environment variables.");
|
|
2624
|
+
logger.gray("System variables (PORT, AWS_LWA_*) will be preserved.\n");
|
|
2625
|
+
if (!options.force) {
|
|
2626
|
+
const shouldPush = await confirm3({
|
|
2627
|
+
message: `Replace all env vars with ${Object.keys(variables).length} variables from ${inputFile}?`,
|
|
2628
|
+
default: false
|
|
2629
|
+
});
|
|
2630
|
+
if (!shouldPush) {
|
|
2631
|
+
logger.gray("\nCancelled.\n");
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
const pushSpinner = ora7("Pushing environment variables...").start();
|
|
2636
|
+
try {
|
|
2637
|
+
const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env`, {
|
|
2638
|
+
method: "PUT",
|
|
2639
|
+
headers: {
|
|
2640
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
2641
|
+
"Content-Type": "application/json"
|
|
2642
|
+
},
|
|
2643
|
+
body: JSON.stringify({
|
|
2644
|
+
variables,
|
|
2645
|
+
replaceAll: true
|
|
2646
|
+
})
|
|
2647
|
+
});
|
|
2648
|
+
if (!response.ok) {
|
|
2649
|
+
const error = await response.text();
|
|
2650
|
+
throw new Error(`Failed to push env vars: ${error}`);
|
|
2651
|
+
}
|
|
2652
|
+
const result = await response.json();
|
|
2653
|
+
pushSpinner.succeed("Environment variables pushed");
|
|
2654
|
+
logger.info(`
|
|
2655
|
+
${result.message || "Variables updated successfully"}`);
|
|
2656
|
+
logger.gray("Note: Lambda will cold-start on next invocation.\n");
|
|
2657
|
+
} catch (error) {
|
|
2658
|
+
pushSpinner.fail("Failed to push environment variables");
|
|
2659
|
+
logger.error(`
|
|
2660
|
+
${error instanceof Error ? error.message : String(error)}`);
|
|
2661
|
+
process.exit(1);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
__name(envPushCommand, "envPushCommand");
|
|
2665
|
+
|
|
2162
2666
|
// src/templates/readme_v1.ts
|
|
2163
2667
|
var getReadmeTemplate = /* @__PURE__ */ __name((projectName) => `<p align="center">
|
|
2164
2668
|
<img
|
|
@@ -2661,6 +3165,219 @@ export class ${capitalizedName}Service {
|
|
|
2661
3165
|
}
|
|
2662
3166
|
`, "getServiceIndexTemplate");
|
|
2663
3167
|
|
|
3168
|
+
// src/templates/python/main_py_v1.ts
|
|
3169
|
+
var getPythonMainTemplate = /* @__PURE__ */ __name((projectName) => `#!/usr/bin/env python3
|
|
3170
|
+
"""
|
|
3171
|
+
${projectName} - MCP Server with Streamable HTTP Transport
|
|
3172
|
+
"""
|
|
3173
|
+
import os
|
|
3174
|
+
import uvicorn
|
|
3175
|
+
from dotenv import load_dotenv
|
|
3176
|
+
from mcp.server.fastmcp import FastMCP
|
|
3177
|
+
|
|
3178
|
+
# Load environment variables
|
|
3179
|
+
load_dotenv()
|
|
3180
|
+
|
|
3181
|
+
# Create the MCP server
|
|
3182
|
+
mcp = FastMCP("${projectName}")
|
|
3183
|
+
|
|
3184
|
+
|
|
3185
|
+
# === Define your tools, resources, and prompts below ===
|
|
3186
|
+
|
|
3187
|
+
@mcp.tool()
|
|
3188
|
+
def calculate(a: float, b: float, operation: str = "add") -> dict:
|
|
3189
|
+
"""
|
|
3190
|
+
Perform arithmetic operations.
|
|
3191
|
+
|
|
3192
|
+
Args:
|
|
3193
|
+
a: First number
|
|
3194
|
+
b: Second number
|
|
3195
|
+
operation: Operation to perform (add, subtract, multiply, divide)
|
|
3196
|
+
"""
|
|
3197
|
+
if operation == "add":
|
|
3198
|
+
result = a + b
|
|
3199
|
+
elif operation == "subtract":
|
|
3200
|
+
result = a - b
|
|
3201
|
+
elif operation == "multiply":
|
|
3202
|
+
result = a * b
|
|
3203
|
+
elif operation == "divide":
|
|
3204
|
+
if b == 0:
|
|
3205
|
+
raise ValueError("Cannot divide by zero")
|
|
3206
|
+
result = a / b
|
|
3207
|
+
else:
|
|
3208
|
+
raise ValueError(f"Invalid operation: {operation}")
|
|
3209
|
+
|
|
3210
|
+
return {"operation": operation, "a": a, "b": b, "result": result}
|
|
3211
|
+
|
|
3212
|
+
|
|
3213
|
+
@mcp.tool()
|
|
3214
|
+
def echo(message: str) -> dict:
|
|
3215
|
+
"""Echo a message back with a timestamp."""
|
|
3216
|
+
from datetime import datetime
|
|
3217
|
+
return {"echoed": message, "timestamp": datetime.now().isoformat()}
|
|
3218
|
+
|
|
3219
|
+
|
|
3220
|
+
@mcp.resource("server://info")
|
|
3221
|
+
def server_info() -> str:
|
|
3222
|
+
"""Get server information."""
|
|
3223
|
+
import json
|
|
3224
|
+
import time
|
|
3225
|
+
return json.dumps({
|
|
3226
|
+
"name": "${projectName}",
|
|
3227
|
+
"version": "1.0.0",
|
|
3228
|
+
"uptime": time.process_time()
|
|
3229
|
+
}, indent=2)
|
|
3230
|
+
|
|
3231
|
+
|
|
3232
|
+
@mcp.prompt()
|
|
3233
|
+
def greeting(name: str = "there") -> str:
|
|
3234
|
+
"""Generate a greeting prompt."""
|
|
3235
|
+
return f"Hello {name}! Welcome to ${projectName}."
|
|
3236
|
+
|
|
3237
|
+
|
|
3238
|
+
if __name__ == "__main__":
|
|
3239
|
+
port = int(os.getenv("PORT", "3001"))
|
|
3240
|
+
print(f"\\n${projectName} MCP Server starting on port {port}...")
|
|
3241
|
+
|
|
3242
|
+
# Run with streamable HTTP transport
|
|
3243
|
+
uvicorn.run(
|
|
3244
|
+
mcp.streamable_http_app(),
|
|
3245
|
+
host="127.0.0.1",
|
|
3246
|
+
port=port,
|
|
3247
|
+
log_level="info"
|
|
3248
|
+
)
|
|
3249
|
+
`, "getPythonMainTemplate");
|
|
3250
|
+
|
|
3251
|
+
// src/templates/python/requirements_v1.ts
|
|
3252
|
+
var getPythonRequirementsTemplate = /* @__PURE__ */ __name(() => `# MCP Server Dependencies
|
|
3253
|
+
mcp>=1.0.0
|
|
3254
|
+
fastmcp>=0.1.0
|
|
3255
|
+
uvicorn>=0.30.0
|
|
3256
|
+
python-dotenv>=1.0.0
|
|
3257
|
+
pydantic>=2.0.0
|
|
3258
|
+
|
|
3259
|
+
# Optional: Add your dependencies below
|
|
3260
|
+
# requests>=2.31.0
|
|
3261
|
+
# httpx>=0.27.0
|
|
3262
|
+
`, "getPythonRequirementsTemplate");
|
|
3263
|
+
|
|
3264
|
+
// src/templates/python/gitignore_py_v1.ts
|
|
3265
|
+
var pythonGitignoreTemplate = `# Byte-compiled / optimized / DLL files
|
|
3266
|
+
__pycache__/
|
|
3267
|
+
*.py[cod]
|
|
3268
|
+
*$py.class
|
|
3269
|
+
|
|
3270
|
+
# Virtual environments
|
|
3271
|
+
venv/
|
|
3272
|
+
.venv/
|
|
3273
|
+
ENV/
|
|
3274
|
+
env/
|
|
3275
|
+
|
|
3276
|
+
# Distribution / packaging
|
|
3277
|
+
build/
|
|
3278
|
+
dist/
|
|
3279
|
+
*.egg-info/
|
|
3280
|
+
*.egg
|
|
3281
|
+
|
|
3282
|
+
# IDE
|
|
3283
|
+
.idea/
|
|
3284
|
+
.vscode/
|
|
3285
|
+
*.swp
|
|
3286
|
+
*.swo
|
|
3287
|
+
|
|
3288
|
+
# Environment variables
|
|
3289
|
+
.env
|
|
3290
|
+
.env.local
|
|
3291
|
+
.env.*.local
|
|
3292
|
+
|
|
3293
|
+
# Testing
|
|
3294
|
+
.pytest_cache/
|
|
3295
|
+
.coverage
|
|
3296
|
+
htmlcov/
|
|
3297
|
+
|
|
3298
|
+
# Logs
|
|
3299
|
+
*.log
|
|
3300
|
+
|
|
3301
|
+
# OS
|
|
3302
|
+
.DS_Store
|
|
3303
|
+
Thumbs.db
|
|
3304
|
+
`;
|
|
3305
|
+
|
|
3306
|
+
// src/templates/python/readme_py_v1.ts
|
|
3307
|
+
var getPythonReadmeTemplate = /* @__PURE__ */ __name((projectName) => `# ${projectName}
|
|
3308
|
+
|
|
3309
|
+
A Python MCP (Model Context Protocol) server with Streamable HTTP transport.
|
|
3310
|
+
|
|
3311
|
+
## Quick Start
|
|
3312
|
+
|
|
3313
|
+
### Prerequisites
|
|
3314
|
+
|
|
3315
|
+
- Python 3.10+
|
|
3316
|
+
- pip or uv package manager
|
|
3317
|
+
|
|
3318
|
+
### Installation
|
|
3319
|
+
|
|
3320
|
+
\`\`\`bash
|
|
3321
|
+
# Create virtual environment
|
|
3322
|
+
python -m venv venv
|
|
3323
|
+
source venv/bin/activate # On Windows: venv\\Scripts\\activate
|
|
3324
|
+
|
|
3325
|
+
# Install dependencies
|
|
3326
|
+
pip install -r requirements.txt
|
|
3327
|
+
\`\`\`
|
|
3328
|
+
|
|
3329
|
+
### Development
|
|
3330
|
+
|
|
3331
|
+
\`\`\`bash
|
|
3332
|
+
# Start development server
|
|
3333
|
+
python main.py
|
|
3334
|
+
\`\`\`
|
|
3335
|
+
|
|
3336
|
+
Server runs at http://localhost:3001
|
|
3337
|
+
|
|
3338
|
+
### Test with MCP Inspector
|
|
3339
|
+
|
|
3340
|
+
\`\`\`bash
|
|
3341
|
+
npx @modelcontextprotocol/inspector http://localhost:3001/mcp
|
|
3342
|
+
\`\`\`
|
|
3343
|
+
|
|
3344
|
+
## Project Structure
|
|
3345
|
+
|
|
3346
|
+
\`\`\`
|
|
3347
|
+
${projectName}/
|
|
3348
|
+
main.py # Server entry point with tools/resources/prompts
|
|
3349
|
+
requirements.txt # Python dependencies
|
|
3350
|
+
.env # Environment variables
|
|
3351
|
+
\`\`\`
|
|
3352
|
+
|
|
3353
|
+
## Adding New Tools
|
|
3354
|
+
|
|
3355
|
+
Add tools directly in \`main.py\` using the \`@mcp.tool()\` decorator:
|
|
3356
|
+
|
|
3357
|
+
\`\`\`python
|
|
3358
|
+
@mcp.tool()
|
|
3359
|
+
def my_tool(param: str) -> dict:
|
|
3360
|
+
"""Tool description shown to AI.
|
|
3361
|
+
|
|
3362
|
+
Args:
|
|
3363
|
+
param: Parameter description
|
|
3364
|
+
"""
|
|
3365
|
+
return {"result": param}
|
|
3366
|
+
\`\`\`
|
|
3367
|
+
|
|
3368
|
+
## Deploy to LeanMCP Cloud
|
|
3369
|
+
|
|
3370
|
+
\`\`\`bash
|
|
3371
|
+
leanmcp deploy .
|
|
3372
|
+
\`\`\`
|
|
3373
|
+
|
|
3374
|
+
## Resources
|
|
3375
|
+
|
|
3376
|
+
- [MCP Documentation](https://modelcontextprotocol.io)
|
|
3377
|
+
- [FastMCP Documentation](https://github.com/jlowin/fastmcp)
|
|
3378
|
+
- [LeanMCP Documentation](https://docs.leanmcp.com)
|
|
3379
|
+
`, "getPythonReadmeTemplate");
|
|
3380
|
+
|
|
2664
3381
|
// src/index.ts
|
|
2665
3382
|
var require2 = createRequire(import.meta.url);
|
|
2666
3383
|
var pkg = require2("../package.json");
|
|
@@ -2675,6 +3392,7 @@ function enableDebugIfNeeded() {
|
|
|
2675
3392
|
setDebugMode(true);
|
|
2676
3393
|
setDebugMode2(true);
|
|
2677
3394
|
setDeployDebugMode(true);
|
|
3395
|
+
setEnvDebugMode(true);
|
|
2678
3396
|
debug("Debug mode enabled globally");
|
|
2679
3397
|
}
|
|
2680
3398
|
}
|
|
@@ -2682,7 +3400,8 @@ __name(enableDebugIfNeeded, "enableDebugIfNeeded");
|
|
|
2682
3400
|
enableDebugIfNeeded();
|
|
2683
3401
|
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version, "-v, --version", "Output the current version").helpOption("-h, --help", "Display help for command").option("-d, --debug", "Enable debug logging for all commands").addHelpText("after", `
|
|
2684
3402
|
Examples:
|
|
2685
|
-
$ leanmcp create my-app # Create new project (interactive)
|
|
3403
|
+
$ leanmcp create my-app # Create new TypeScript project (interactive)
|
|
3404
|
+
$ leanmcp create my-app --python # Create new Python project
|
|
2686
3405
|
$ leanmcp create my-app -i # Create and install deps (non-interactive)
|
|
2687
3406
|
$ leanmcp create my-app --no-install # Create without installing deps
|
|
2688
3407
|
$ leanmcp dev # Start development server
|
|
@@ -2692,103 +3411,122 @@ Examples:
|
|
|
2692
3411
|
$ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
|
|
2693
3412
|
$ leanmcp projects list # List your cloud projects
|
|
2694
3413
|
$ leanmcp projects delete <id> # Delete a cloud project
|
|
3414
|
+
$ leanmcp env list # List environment variables
|
|
3415
|
+
$ leanmcp env set KEY=VALUE # Set an environment variable
|
|
2695
3416
|
|
|
2696
3417
|
Global Options:
|
|
2697
3418
|
-v, --version Output the current version
|
|
2698
3419
|
-h, --help Display help for command
|
|
2699
3420
|
-d, --debug Enable debug logging for all commands
|
|
2700
3421
|
`);
|
|
2701
|
-
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("-i, --install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
|
|
3422
|
+
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("-i, --install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").option("--python", "Create a Python MCP project instead of TypeScript").action(async (projectName, options) => {
|
|
2702
3423
|
trackCommand("create", {
|
|
2703
3424
|
projectName,
|
|
2704
3425
|
...options
|
|
2705
3426
|
});
|
|
2706
|
-
const spinner =
|
|
2707
|
-
const targetDir =
|
|
2708
|
-
if (
|
|
3427
|
+
const spinner = ora8(`Creating project ${projectName}...`).start();
|
|
3428
|
+
const targetDir = path10.join(process.cwd(), projectName);
|
|
3429
|
+
if (fs10.existsSync(targetDir)) {
|
|
2709
3430
|
spinner.fail(`Folder ${projectName} already exists.`);
|
|
2710
3431
|
process.exit(1);
|
|
2711
3432
|
}
|
|
2712
|
-
await
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
"
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
3433
|
+
await fs10.mkdirp(targetDir);
|
|
3434
|
+
const isPython = options.python === true;
|
|
3435
|
+
if (isPython) {
|
|
3436
|
+
const requirements = getPythonRequirementsTemplate();
|
|
3437
|
+
await fs10.writeFile(path10.join(targetDir, "requirements.txt"), requirements);
|
|
3438
|
+
const mainPy = getPythonMainTemplate(projectName);
|
|
3439
|
+
await fs10.writeFile(path10.join(targetDir, "main.py"), mainPy);
|
|
3440
|
+
await fs10.writeFile(path10.join(targetDir, ".gitignore"), pythonGitignoreTemplate);
|
|
3441
|
+
const env = `# Server Configuration
|
|
3442
|
+
PORT=3001
|
|
3443
|
+
|
|
3444
|
+
# Add your environment variables here
|
|
3445
|
+
`;
|
|
3446
|
+
await fs10.writeFile(path10.join(targetDir, ".env"), env);
|
|
3447
|
+
const readme = getPythonReadmeTemplate(projectName);
|
|
3448
|
+
await fs10.writeFile(path10.join(targetDir, "README.md"), readme);
|
|
3449
|
+
} else {
|
|
3450
|
+
await fs10.mkdirp(path10.join(targetDir, "mcp", "example"));
|
|
3451
|
+
const pkg2 = {
|
|
3452
|
+
name: projectName,
|
|
3453
|
+
version: "1.0.0",
|
|
3454
|
+
description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
|
|
3455
|
+
main: "dist/main.js",
|
|
3456
|
+
type: "module",
|
|
3457
|
+
scripts: {
|
|
3458
|
+
dev: "leanmcp dev",
|
|
3459
|
+
build: "leanmcp build",
|
|
3460
|
+
start: "leanmcp start",
|
|
3461
|
+
inspect: "npx @modelcontextprotocol/inspector node dist/main.js",
|
|
3462
|
+
"start:node": "node dist/main.js",
|
|
3463
|
+
clean: "rm -rf dist"
|
|
3464
|
+
},
|
|
3465
|
+
keywords: [
|
|
3466
|
+
"mcp",
|
|
3467
|
+
"model-context-protocol",
|
|
3468
|
+
"streamable-http",
|
|
3469
|
+
"leanmcp"
|
|
3470
|
+
],
|
|
3471
|
+
author: "",
|
|
3472
|
+
license: "MIT",
|
|
3473
|
+
dependencies: {
|
|
3474
|
+
"@leanmcp/core": "^0.3.14",
|
|
3475
|
+
"@leanmcp/ui": "^0.2.1",
|
|
3476
|
+
"@leanmcp/auth": "^0.4.0",
|
|
3477
|
+
"dotenv": "^16.5.0"
|
|
3478
|
+
},
|
|
3479
|
+
devDependencies: {
|
|
3480
|
+
"@leanmcp/cli": "^0.4.0",
|
|
3481
|
+
"@types/node": "^20.0.0",
|
|
3482
|
+
"tsx": "^4.20.3",
|
|
3483
|
+
"typescript": "^5.6.3"
|
|
3484
|
+
}
|
|
3485
|
+
};
|
|
3486
|
+
await fs10.writeJSON(path10.join(targetDir, "package.json"), pkg2, {
|
|
3487
|
+
spaces: 2
|
|
3488
|
+
});
|
|
3489
|
+
const tsconfig = {
|
|
3490
|
+
compilerOptions: {
|
|
3491
|
+
module: "ESNext",
|
|
3492
|
+
target: "ES2022",
|
|
3493
|
+
moduleResolution: "Node",
|
|
3494
|
+
esModuleInterop: true,
|
|
3495
|
+
strict: true,
|
|
3496
|
+
skipLibCheck: true,
|
|
3497
|
+
outDir: "dist",
|
|
3498
|
+
experimentalDecorators: true,
|
|
3499
|
+
emitDecoratorMetadata: true
|
|
3500
|
+
},
|
|
3501
|
+
include: [
|
|
3502
|
+
"**/*.ts"
|
|
3503
|
+
],
|
|
3504
|
+
exclude: [
|
|
3505
|
+
"node_modules",
|
|
3506
|
+
"dist"
|
|
3507
|
+
]
|
|
3508
|
+
};
|
|
3509
|
+
await fs10.writeJSON(path10.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
3510
|
+
spaces: 2
|
|
3511
|
+
});
|
|
3512
|
+
const dashboardLine = options.dashboard === false ? `
|
|
2776
3513
|
dashboard: false, // Dashboard disabled via --no-dashboard` : "";
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
3514
|
+
const mainTs = getMainTsTemplate(projectName, dashboardLine);
|
|
3515
|
+
await fs10.writeFile(path10.join(targetDir, "main.ts"), mainTs);
|
|
3516
|
+
const exampleServiceTs = getExampleServiceTemplate(projectName);
|
|
3517
|
+
await fs10.writeFile(path10.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
|
|
3518
|
+
const gitignore = gitignoreTemplate;
|
|
3519
|
+
const env = `# Server Configuration
|
|
2783
3520
|
PORT=3001
|
|
2784
3521
|
NODE_ENV=development
|
|
2785
3522
|
|
|
2786
3523
|
# Add your environment variables here
|
|
2787
3524
|
`;
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
3525
|
+
await fs10.writeFile(path10.join(targetDir, ".gitignore"), gitignore);
|
|
3526
|
+
await fs10.writeFile(path10.join(targetDir, ".env"), env);
|
|
3527
|
+
const readme = getReadmeTemplate(projectName);
|
|
3528
|
+
await fs10.writeFile(path10.join(targetDir, "README.md"), readme);
|
|
3529
|
+
}
|
|
2792
3530
|
spinner.succeed(`Project ${projectName} created!`);
|
|
2793
3531
|
logger.log("\nSuccess! Your MCP server is ready.\n", chalk.green);
|
|
2794
3532
|
logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
|
|
@@ -2801,20 +3539,40 @@ NODE_ENV=development
|
|
|
2801
3539
|
if (options.install === false) {
|
|
2802
3540
|
logger.log("To get started:", chalk.cyan);
|
|
2803
3541
|
logger.log(` cd ${projectName}`, chalk.gray);
|
|
2804
|
-
|
|
2805
|
-
|
|
3542
|
+
if (isPython) {
|
|
3543
|
+
logger.log(` python -m venv venv`, chalk.gray);
|
|
3544
|
+
logger.log(` source venv/bin/activate # On Windows: venv\\Scripts\\activate`, chalk.gray);
|
|
3545
|
+
logger.log(` pip install -r requirements.txt`, chalk.gray);
|
|
3546
|
+
logger.log(` python main.py`, chalk.gray);
|
|
3547
|
+
} else {
|
|
3548
|
+
logger.log(` npm install`, chalk.gray);
|
|
3549
|
+
logger.log(` npm run dev`, chalk.gray);
|
|
3550
|
+
}
|
|
2806
3551
|
logger.log("");
|
|
2807
3552
|
logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
|
|
2808
3553
|
logger.log(` cd ${projectName}`, chalk.gray);
|
|
2809
3554
|
logger.log(` leanmcp deploy .`, chalk.gray);
|
|
2810
3555
|
return;
|
|
2811
3556
|
}
|
|
2812
|
-
|
|
3557
|
+
if (isPython) {
|
|
3558
|
+
logger.log("\nTo get started:", chalk.cyan);
|
|
3559
|
+
logger.log(` cd ${projectName}`, chalk.gray);
|
|
3560
|
+
logger.log(` python -m venv venv`, chalk.gray);
|
|
3561
|
+
logger.log(` source venv/bin/activate # On Windows: venv\\Scripts\\activate`, chalk.gray);
|
|
3562
|
+
logger.log(` pip install -r requirements.txt`, chalk.gray);
|
|
3563
|
+
logger.log(` python main.py`, chalk.gray);
|
|
3564
|
+
logger.log("");
|
|
3565
|
+
logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
|
|
3566
|
+
logger.log(` cd ${projectName}`, chalk.gray);
|
|
3567
|
+
logger.log(` leanmcp deploy .`, chalk.gray);
|
|
3568
|
+
return;
|
|
3569
|
+
}
|
|
3570
|
+
const shouldInstall = isNonInteractive ? true : await confirm4({
|
|
2813
3571
|
message: "Would you like to install dependencies now?",
|
|
2814
3572
|
default: true
|
|
2815
3573
|
});
|
|
2816
3574
|
if (shouldInstall) {
|
|
2817
|
-
const installSpinner =
|
|
3575
|
+
const installSpinner = ora8("Installing dependencies...").start();
|
|
2818
3576
|
try {
|
|
2819
3577
|
await new Promise((resolve, reject) => {
|
|
2820
3578
|
const npmInstall = spawn4("npm", [
|
|
@@ -2844,7 +3602,7 @@ NODE_ENV=development
|
|
|
2844
3602
|
logger.log(` leanmcp deploy .`, chalk.gray);
|
|
2845
3603
|
return;
|
|
2846
3604
|
}
|
|
2847
|
-
const shouldStartDev = options.allowAll ? true : await
|
|
3605
|
+
const shouldStartDev = options.allowAll ? true : await confirm4({
|
|
2848
3606
|
message: "Would you like to start the development server?",
|
|
2849
3607
|
default: true
|
|
2850
3608
|
});
|
|
@@ -2891,20 +3649,20 @@ NODE_ENV=development
|
|
|
2891
3649
|
});
|
|
2892
3650
|
program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
|
|
2893
3651
|
const cwd = process.cwd();
|
|
2894
|
-
const mcpDir =
|
|
2895
|
-
if (!
|
|
3652
|
+
const mcpDir = path10.join(cwd, "mcp");
|
|
3653
|
+
if (!fs10.existsSync(path10.join(cwd, "main.ts"))) {
|
|
2896
3654
|
logger.log("ERROR: Not a LeanMCP project (main.ts missing).", chalk.red);
|
|
2897
3655
|
process.exit(1);
|
|
2898
3656
|
}
|
|
2899
|
-
const serviceDir =
|
|
2900
|
-
const serviceFile =
|
|
2901
|
-
if (
|
|
3657
|
+
const serviceDir = path10.join(mcpDir, serviceName);
|
|
3658
|
+
const serviceFile = path10.join(serviceDir, "index.ts");
|
|
3659
|
+
if (fs10.existsSync(serviceDir)) {
|
|
2902
3660
|
logger.log(`ERROR: Service ${serviceName} already exists.`, chalk.red);
|
|
2903
3661
|
process.exit(1);
|
|
2904
3662
|
}
|
|
2905
|
-
await
|
|
3663
|
+
await fs10.mkdirp(serviceDir);
|
|
2906
3664
|
const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
|
|
2907
|
-
await
|
|
3665
|
+
await fs10.writeFile(serviceFile, indexTs);
|
|
2908
3666
|
logger.log(`\\nCreated new service: ${chalk.bold(serviceName)}`, chalk.green);
|
|
2909
3667
|
logger.log(` File: mcp/${serviceName}/index.ts`, chalk.gray);
|
|
2910
3668
|
logger.log(` Tool: greet`, chalk.gray);
|
|
@@ -2966,4 +3724,49 @@ projectsCmd.command("delete <projectId>").alias("rm").description("Delete a proj
|
|
|
2966
3724
|
});
|
|
2967
3725
|
projectsDeleteCommand(projectId, options);
|
|
2968
3726
|
});
|
|
3727
|
+
var envCmd = program.command("env").description("Manage environment variables for deployed projects");
|
|
3728
|
+
envCmd.command("list [folder]").alias("ls").description("List all environment variables").option("--reveal", "Show actual values instead of masked").option("--project-id <id>", "Specify project ID").action((folder, options) => {
|
|
3729
|
+
trackCommand("env_list", {
|
|
3730
|
+
folder,
|
|
3731
|
+
...options
|
|
3732
|
+
});
|
|
3733
|
+
envListCommand(folder || ".", options);
|
|
3734
|
+
});
|
|
3735
|
+
envCmd.command("set <keyValue> [folder]").description("Set an environment variable (KEY=VALUE)").option("-f, --file <file>", "Load from env file").option("--force", "Skip confirmation for reserved keys").action((keyValue, folder, options) => {
|
|
3736
|
+
trackCommand("env_set", {
|
|
3737
|
+
folder,
|
|
3738
|
+
...options
|
|
3739
|
+
});
|
|
3740
|
+
envSetCommand(keyValue, folder || ".", options);
|
|
3741
|
+
});
|
|
3742
|
+
envCmd.command("get <key> [folder]").description("Get an environment variable value").option("--reveal", "Show actual value").action((key, folder, options) => {
|
|
3743
|
+
trackCommand("env_get", {
|
|
3744
|
+
key,
|
|
3745
|
+
folder,
|
|
3746
|
+
...options
|
|
3747
|
+
});
|
|
3748
|
+
envGetCommand(key, folder || ".", options);
|
|
3749
|
+
});
|
|
3750
|
+
envCmd.command("remove <key> [folder]").alias("rm").description("Remove an environment variable").option("--force", "Skip confirmation").action((key, folder, options) => {
|
|
3751
|
+
trackCommand("env_remove", {
|
|
3752
|
+
key,
|
|
3753
|
+
folder,
|
|
3754
|
+
...options
|
|
3755
|
+
});
|
|
3756
|
+
envRemoveCommand(key, folder || ".", options);
|
|
3757
|
+
});
|
|
3758
|
+
envCmd.command("pull [folder]").description("Download environment variables to local .env file").option("-f, --file <file>", "Output file", ".env.remote").action((folder, options) => {
|
|
3759
|
+
trackCommand("env_pull", {
|
|
3760
|
+
folder,
|
|
3761
|
+
...options
|
|
3762
|
+
});
|
|
3763
|
+
envPullCommand(folder || ".", options);
|
|
3764
|
+
});
|
|
3765
|
+
envCmd.command("push [folder]").description("Upload environment variables from local .env file (replaces all)").option("-f, --file <file>", "Input file", ".env").option("--force", "Skip confirmation").action((folder, options) => {
|
|
3766
|
+
trackCommand("env_push", {
|
|
3767
|
+
folder,
|
|
3768
|
+
...options
|
|
3769
|
+
});
|
|
3770
|
+
envPushCommand(folder || ".", options);
|
|
3771
|
+
});
|
|
2969
3772
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leanmcp/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Command-line interface for scaffolding LeanMCP projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"leanmcp": "bin/leanmcp.js"
|
|
@@ -70,4 +70,4 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
}
|
|
73
|
-
}
|
|
73
|
+
}
|