@leanmcp/cli 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +124 -15
  2. package/dist/index.js +435 -79
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ # @leanmcp/cli
2
+
1
3
  <p align="center">
2
4
  <img
3
5
  src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/assets/logo.png"
@@ -6,11 +8,6 @@
6
8
  />
7
9
  </p>
8
10
 
9
- <p align="center">
10
- <strong>@leanmcp/cli</strong><br/>
11
- Command-line tool for creating, developing, and deploying LeanMCP projects.
12
- </p>
13
-
14
11
  <p align="center">
15
12
  <a href="https://www.npmjs.com/package/@leanmcp/cli">
16
13
  <img src="https://img.shields.io/npm/v/@leanmcp/cli" alt="npm version" />
@@ -22,7 +19,7 @@
22
19
  <img src="https://img.shields.io/badge/Docs-leanmcp-0A66C2?" />
23
20
  </a>
24
21
  <a href="https://discord.com/invite/DsRcA3GwPy">
25
- <img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" />
22
+ <img src="https://dcbadge.limes.pink/api/server/DsRcA3GwPy?style=flat" alt="Discord" />
26
23
  </a>
27
24
  <a href="https://x.com/LeanMcp">
28
25
  <img src="https://img.shields.io/badge/@LeanMCP-f5f5f5?logo=x&logoColor=000000" />
@@ -33,6 +30,58 @@
33
30
  <a href="https://deepwiki.com/LeanMCP/leanmcp-sdk"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
34
31
  </p>
35
32
 
33
+ ```json
34
+ {
35
+ "package": "@leanmcp/cli",
36
+ "purpose": "Command-line interface for scaffolding LeanMCP projects",
37
+ "useCases": [
38
+ "Project scaffolding",
39
+ "Local development with hot-reload",
40
+ "Cloud deployment",
41
+ "Project management"
42
+ ],
43
+ "dependencies": ["@inquirer/prompts", "commander", "chalk", "vite", "archiver", "fs-extra"],
44
+ "bin": {
45
+ "leanmcp": "bin/leanmcp.js"
46
+ },
47
+ "main": "dist/index.js",
48
+ "exports": {
49
+ ".": {
50
+ "types": "./dist/index.d.ts",
51
+ "import": "./dist/index.js"
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Overview
58
+
59
+ - **What it is**: Command-line interface for scaffolding LeanMCP projects with hot-reload development and cloud deployment
60
+ - **Purpose**: Streamlines the entire MCP server development workflow from project creation to production deployment
61
+ - **Key benefits**:
62
+ - Quick project scaffolding with production-ready templates
63
+ - Hot-reload development server with UI component building
64
+ - One-command cloud deployment to LeanMCP platform
65
+ - Interactive setup with guided prompts
66
+ - Project management and monitoring tools
67
+
68
+ ## When to Use It
69
+
70
+ **Use `@leanmcp/cli` when:**
71
+
72
+ - Starting any new MCP server project (highly recommended)
73
+ - Need local development with hot-reload and UI building
74
+ - Want to deploy to LeanMCP Cloud with custom subdomains
75
+ - Managing multiple MCP projects
76
+ - Need guided setup for dependencies and configuration
77
+
78
+ **You probably do NOT need this if:**
79
+
80
+ - Using custom build systems or deployment pipelines
81
+ - Only working with existing projects without scaffolding needs
82
+ - Building MCP clients (not servers)
83
+ - Working in environments where global CLI tools aren't allowed
84
+
36
85
  ## Features
37
86
 
38
87
  - **Quick Scaffolding** — Create production-ready MCP servers in seconds
@@ -53,7 +102,14 @@ Or run without installing:
53
102
  npx @leanmcp/cli create my-mcp-server
54
103
  ```
55
104
 
56
- ## Commands Overview
105
+ **Requirements:**
106
+
107
+ - Node.js >= 18.0.0
108
+ - npm >= 9.0.0
109
+
110
+ ## Usage / Examples
111
+
112
+ ### Commands Overview
57
113
 
58
114
  ```bash
59
115
  # Local development
@@ -270,7 +326,60 @@ leanmcp projects delete <project-id> --force # Skip confirmation
270
326
 
271
327
  ---
272
328
 
273
- ## NPM Scripts
329
+ ## API Reference
330
+
331
+ ### Command Reference
332
+
333
+ | Command | Description | Usage |
334
+ | ---------------------- | ---------------------------------------- | ------------------------------ |
335
+ | `create <name>` | Create new MCP server project | `leanmcp create my-server` |
336
+ | `add <service>` | Add service to existing project | `leanmcp add weather` |
337
+ | `dev` | Start development server with hot-reload | `leanmcp dev` |
338
+ | `build` | Build for production | `leanmcp build` |
339
+ | `start` | Start production server | `leanmcp start` |
340
+ | `login` | Authenticate with LeanMCP Cloud | `leanmcp login` |
341
+ | `logout` | Remove API key | `leanmcp logout` |
342
+ | `whoami` | Show login status | `leanmcp whoami` |
343
+ | `deploy [folder]` | Deploy to LeanMCP Cloud | `leanmcp deploy .` |
344
+ | `projects list` | List cloud projects | `leanmcp projects list` |
345
+ | `projects get <id>` | Get project details | `leanmcp projects get <id>` |
346
+ | `projects delete <id>` | Delete project | `leanmcp projects delete <id>` |
347
+ | `env list [folder]` | List environment variables | `leanmcp env list` |
348
+ | `env set <keyValue>` | Set environment variable | `leanmcp env set KEY=VALUE` |
349
+ | `env get <key>` | Get environment variable value | `leanmcp env get KEY` |
350
+ | `env remove <key>` | Remove environment variable | `leanmcp env remove KEY` |
351
+ | `env pull [folder]` | Download env vars to local file | `leanmcp env pull` |
352
+ | `env push [folder]` | Upload env vars from local file | `leanmcp env push` |
353
+
354
+ ---
355
+
356
+ ## Integration with Other LeanMCP Packages
357
+
358
+ **@leanmcp/cli** works seamlessly with all LeanMCP packages:
359
+
360
+ - **[@leanmcp/core](https://www.npmjs.com/package/@leanmcp/core)** — Generated projects use `@leanmcp/core` as the foundation
361
+ - **[@leanmcp/auth](https://www.npmjs.com/package/@leanmcp/auth)** — CLI can scaffold projects with authentication setup
362
+ - **[@leanmcp/ui](https://www.npmjs.com/package/@leanmcp/ui)** — `leanmcp dev` automatically builds UI components with hot-reload
363
+ - **[@leanmcp/elicitation](https://www.npmjs.com/package/@leanmcp/elicitation)** — Generated services include elicitation examples
364
+ - **[@leanmcp/env-injection](https://www.npmjs.com/package/@leanmcp/env-injection)** — Deploy command handles user secrets configuration
365
+
366
+ **Generated project structure:**
367
+
368
+ ```
369
+ my-mcp-server/
370
+ ├── main.ts # Entry point with HTTP server
371
+ ├── package.json # Dependencies and scripts
372
+ ├── tsconfig.json # TypeScript configuration
373
+ └── mcp/ # Services directory (auto-discovered)
374
+ └── example/
375
+ └── index.ts # Example service with @Tool, @Prompt, @Resource
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Best Practices / Troubleshooting
381
+
382
+ ### NPM Scripts
274
383
 
275
384
  Generated projects include:
276
385
 
@@ -357,12 +466,12 @@ Choose a different subdomain when prompted.
357
466
  - [@leanmcp/auth](https://www.npmjs.com/package/@leanmcp/auth) — Authentication decorators
358
467
  - [@leanmcp/ui](https://www.npmjs.com/package/@leanmcp/ui) — MCP App UI components
359
468
 
360
- ## Links
361
-
362
- - [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
363
- - [NPM Package](https://www.npmjs.com/package/@leanmcp/cli)
364
- - [LeanMCP Dashboard](https://ship.leanmcp.com)
469
+ ---
365
470
 
366
- ## License
471
+ ## Links
367
472
 
368
- MIT
473
+ - **Documentation**: [https://docs.leanmcp.com/sdk/cli](https://docs.leanmcp.com/sdk/cli)
474
+ - **GitHub**: [https://github.com/LeanMCP/leanmcp-sdk/tree/main/packages/cli](https://github.com/LeanMCP/leanmcp-sdk/tree/main/packages/cli)
475
+ - **npm**: [https://www.npmjs.com/package/@leanmcp/cli](https://www.npmjs.com/package/@leanmcp/cli)
476
+ - **LeanMCP Dashboard**: [https://ship.leanmcp.com](https://ship.leanmcp.com)
477
+ - **License**: MIT
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3
3
 
4
4
  // src/index.ts
5
5
  import { Command } from "commander";
6
- import fs11 from "fs-extra";
7
- import path11 from "path";
8
- import ora8 from "ora";
6
+ import fs12 from "fs-extra";
7
+ import path12 from "path";
8
+ import ora9 from "ora";
9
9
  import { createRequire } from "module";
10
10
  import { confirm as confirm4 } from "@inquirer/prompts";
11
11
  import { spawn as spawn4 } from "child_process";
@@ -1786,6 +1786,42 @@ async function debugFetch(url, options = {}) {
1786
1786
  return response;
1787
1787
  }
1788
1788
  __name(debugFetch, "debugFetch");
1789
+ async function fetchWithRetry(fetchFn, options = {}) {
1790
+ const maxRetries = options.maxRetries ?? 15;
1791
+ const initialDelay = options.initialDelay ?? 1e3;
1792
+ const maxDelay = options.maxDelay ?? 1e4;
1793
+ const operation = options.operation ?? "Fetch";
1794
+ const spinner = options.spinner;
1795
+ const retryOnHttpErrors = options.retryOnHttpErrors ?? false;
1796
+ let lastError = null;
1797
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1798
+ try {
1799
+ const response = await fetchFn();
1800
+ if (retryOnHttpErrors && !response.ok && response.status >= 500) {
1801
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1802
+ }
1803
+ return response;
1804
+ } catch (error) {
1805
+ lastError = error instanceof Error ? error : new Error(String(error));
1806
+ if (attempt < maxRetries) {
1807
+ const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
1808
+ const message = `${operation} failed. Retrying... (${attempt + 1}/${maxRetries})`;
1809
+ if (spinner) {
1810
+ spinner.text = message;
1811
+ } else {
1812
+ process.stdout.write("\r" + chalk.yellow(message));
1813
+ }
1814
+ debug3(`Retry ${attempt + 1}/${maxRetries}: ${lastError.message}, waiting ${delay}ms`);
1815
+ await new Promise((resolve) => setTimeout(resolve, delay));
1816
+ }
1817
+ }
1818
+ }
1819
+ if (!spinner) {
1820
+ process.stdout.write("\x1B[2K\r");
1821
+ }
1822
+ throw new Error(`${operation} failed after ${maxRetries + 1} attempts (1 initial + ${maxRetries} retries): ${lastError?.message || "Unknown error"}`);
1823
+ }
1824
+ __name(fetchWithRetry, "fetchWithRetry");
1789
1825
  var API_ENDPOINTS = {
1790
1826
  // Projects
1791
1827
  projects: "/api/projects",
@@ -1872,10 +1908,14 @@ async function waitForBuild(apiUrl, apiKey, buildId, spinner) {
1872
1908
  const maxAttempts = 60;
1873
1909
  let attempts = 0;
1874
1910
  while (attempts < maxAttempts) {
1875
- const response = await debugFetch(`${apiUrl}${API_ENDPOINTS.getBuild}/${buildId}`, {
1911
+ const response = await fetchWithRetry(() => debugFetch(`${apiUrl}${API_ENDPOINTS.getBuild}/${buildId}`, {
1876
1912
  headers: {
1877
1913
  Authorization: `Bearer ${apiKey}`
1878
1914
  }
1915
+ }), {
1916
+ operation: "Build status check",
1917
+ spinner,
1918
+ maxRetries: 3
1879
1919
  });
1880
1920
  if (!response.ok) {
1881
1921
  throw new Error(`Failed to get build status: ${response.statusText}`);
@@ -1901,10 +1941,14 @@ async function waitForDeployment(apiUrl, apiKey, deploymentId, spinner) {
1901
1941
  const maxAttempts = 60;
1902
1942
  let attempts = 0;
1903
1943
  while (attempts < maxAttempts) {
1904
- const response = await debugFetch(`${apiUrl}${API_ENDPOINTS.getDeployment}/${deploymentId}`, {
1944
+ const response = await fetchWithRetry(() => debugFetch(`${apiUrl}${API_ENDPOINTS.getDeployment}/${deploymentId}`, {
1905
1945
  headers: {
1906
1946
  Authorization: `Bearer ${apiKey}`
1907
1947
  }
1948
+ }), {
1949
+ operation: "Deployment status check",
1950
+ spinner,
1951
+ maxRetries: 3
1908
1952
  });
1909
1953
  if (!response.ok) {
1910
1954
  throw new Error(`Failed to get deployment status: ${response.statusText}`);
@@ -2360,8 +2404,257 @@ ${error instanceof Error ? error.message : String(error)}`);
2360
2404
  }
2361
2405
  __name(deployCommand, "deployCommand");
2362
2406
 
2363
- // src/commands/projects.ts
2407
+ // src/commands/feedback.ts
2364
2408
  import ora6 from "ora";
2409
+ import fs10 from "fs-extra";
2410
+ import path10 from "path";
2411
+ import os4 from "os";
2412
+ var DEBUG_MODE4 = false;
2413
+ function debug4(message, ...args) {
2414
+ if (DEBUG_MODE4) {
2415
+ console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
2416
+ }
2417
+ }
2418
+ __name(debug4, "debug");
2419
+ async function readFileAsBase64(filePath) {
2420
+ try {
2421
+ const absolutePath = path10.resolve(filePath);
2422
+ const stats = await fs10.stat(absolutePath);
2423
+ if (!stats.isFile()) {
2424
+ throw new Error(`${filePath} is not a file`);
2425
+ }
2426
+ const content = await fs10.readFile(absolutePath, "base64");
2427
+ const ext = path10.extname(absolutePath).toLowerCase();
2428
+ let mimeType = "application/octet-stream";
2429
+ switch (ext) {
2430
+ case ".txt":
2431
+ case ".log":
2432
+ mimeType = "text/plain";
2433
+ break;
2434
+ case ".json":
2435
+ mimeType = "application/json";
2436
+ break;
2437
+ case ".js":
2438
+ mimeType = "application/javascript";
2439
+ break;
2440
+ case ".ts":
2441
+ mimeType = "application/typescript";
2442
+ break;
2443
+ case ".md":
2444
+ mimeType = "text/markdown";
2445
+ break;
2446
+ }
2447
+ return {
2448
+ content,
2449
+ size: stats.size,
2450
+ type: mimeType
2451
+ };
2452
+ } catch (error) {
2453
+ throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
2454
+ }
2455
+ }
2456
+ __name(readFileAsBase64, "readFileAsBase64");
2457
+ async function collectLogFiles() {
2458
+ const attachments = [];
2459
+ const logLocations = [
2460
+ path10.join(os4.homedir(), ".leanmcp", "logs"),
2461
+ path10.join(process.cwd(), "logs"),
2462
+ path10.join(process.cwd(), ".leanmcp", "logs")
2463
+ ];
2464
+ for (const logDir of logLocations) {
2465
+ try {
2466
+ if (await fs10.pathExists(logDir)) {
2467
+ const files = await fs10.readdir(logDir);
2468
+ for (const file of files) {
2469
+ const filePath = path10.join(logDir, file);
2470
+ try {
2471
+ const fileData = await readFileAsBase64(filePath);
2472
+ attachments.push({
2473
+ name: file,
2474
+ ...fileData
2475
+ });
2476
+ } catch (error) {
2477
+ debug4(`Failed to read log file ${filePath}: ${error}`);
2478
+ }
2479
+ }
2480
+ }
2481
+ } catch (error) {
2482
+ debug4(`Failed to scan log directory ${logDir}: ${error}`);
2483
+ }
2484
+ }
2485
+ const npmDebugLog = path10.join(os4.homedir(), ".npm", "_logs");
2486
+ try {
2487
+ if (await fs10.pathExists(npmDebugLog)) {
2488
+ const logFiles = await fs10.readdir(npmDebugLog);
2489
+ const latestLog = logFiles.filter((file) => file.endsWith(".log")).sort().pop();
2490
+ if (latestLog) {
2491
+ const filePath = path10.join(npmDebugLog, latestLog);
2492
+ try {
2493
+ const fileData = await readFileAsBase64(filePath);
2494
+ attachments.push({
2495
+ name: `npm-${latestLog}`,
2496
+ ...fileData
2497
+ });
2498
+ } catch (error) {
2499
+ debug4(`Failed to read npm debug log: ${error}`);
2500
+ }
2501
+ }
2502
+ }
2503
+ } catch (error) {
2504
+ debug4(`Failed to collect npm debug logs: ${error}`);
2505
+ }
2506
+ return attachments;
2507
+ }
2508
+ __name(collectLogFiles, "collectLogFiles");
2509
+ async function sendFeedbackToApi(message, attachments = [], isAnonymous = false) {
2510
+ const apiUrl = await getApiUrl();
2511
+ const endpoint = isAnonymous ? "/feedback/anonymous" : "/feedback";
2512
+ const url = `${apiUrl}${endpoint}`;
2513
+ debug4("API URL:", apiUrl);
2514
+ debug4("Endpoint:", endpoint);
2515
+ debug4("Message length:", message.length);
2516
+ debug4("Attachments count:", attachments.length);
2517
+ const headers = {
2518
+ "Content-Type": "application/json"
2519
+ };
2520
+ if (!isAnonymous) {
2521
+ const apiKey = await getApiKey();
2522
+ if (!apiKey) {
2523
+ throw new Error("Not authenticated. Please run `leanmcp login` first.");
2524
+ }
2525
+ headers["Authorization"] = `Bearer ${apiKey}`;
2526
+ }
2527
+ const payload = {
2528
+ message,
2529
+ attachments: attachments.map((att) => ({
2530
+ name: att.name,
2531
+ content: att.content,
2532
+ size: att.size,
2533
+ type: att.type
2534
+ }))
2535
+ };
2536
+ debug4("Sending feedback request...");
2537
+ const response = await fetch(url, {
2538
+ method: "POST",
2539
+ headers,
2540
+ body: JSON.stringify(payload)
2541
+ });
2542
+ debug4("Response status:", response.status);
2543
+ debug4("Response ok:", response.ok);
2544
+ if (!response.ok) {
2545
+ const errorText = await response.text();
2546
+ debug4("Error response:", errorText);
2547
+ if (response.status === 401) {
2548
+ throw new Error("Authentication failed. Please run `leanmcp login` to re-authenticate.");
2549
+ } else if (response.status === 413) {
2550
+ throw new Error("Attachments too large. Please try again without log files.");
2551
+ } else {
2552
+ throw new Error(`Failed to send feedback: ${response.status} ${response.statusText}`);
2553
+ }
2554
+ }
2555
+ return await response.json();
2556
+ }
2557
+ __name(sendFeedbackToApi, "sendFeedbackToApi");
2558
+ async function sendFeedbackCommand(message, options) {
2559
+ logger.info("\nLeanMCP Feedback\n");
2560
+ const isAnonymous = options.anon || false;
2561
+ const includeLogs = options.includeLogs || false;
2562
+ debug4("Feedback options:", {
2563
+ isAnonymous,
2564
+ includeLogs
2565
+ });
2566
+ let feedbackMessage = message;
2567
+ if (!feedbackMessage && !process.stdin.isTTY) {
2568
+ debug4("Reading feedback message from stdin...");
2569
+ feedbackMessage = await new Promise((resolve) => {
2570
+ let data = "";
2571
+ process.stdin.on("data", (chunk) => {
2572
+ data += chunk;
2573
+ });
2574
+ process.stdin.on("end", () => {
2575
+ resolve(data.trim());
2576
+ });
2577
+ });
2578
+ }
2579
+ if (!feedbackMessage || feedbackMessage.trim().length === 0) {
2580
+ logger.error("Feedback message cannot be empty.");
2581
+ logger.info("Usage examples:");
2582
+ logger.info(' leanmcp send-feedback "Your message"');
2583
+ logger.gray(" leanmcp send-feedback << EOF");
2584
+ logger.gray(" multi-line");
2585
+ logger.gray(" message");
2586
+ logger.gray(" EOF");
2587
+ logger.info(' leanmcp send-feedback --anon "Anonymous feedback"');
2588
+ logger.info(' leanmcp send-feedback "Issue with deploy" --include-logs');
2589
+ process.exit(1);
2590
+ }
2591
+ if (feedbackMessage.length > 5e3) {
2592
+ logger.error("Feedback message is too long (max 5000 characters).");
2593
+ process.exit(1);
2594
+ }
2595
+ if (!isAnonymous) {
2596
+ const apiKey = await getApiKey();
2597
+ if (!apiKey) {
2598
+ logger.error("need to login");
2599
+ logger.info("Please run `leanmcp login` to authenticate, or use `--anon` for anonymous feedback.");
2600
+ process.exit(1);
2601
+ }
2602
+ }
2603
+ let attachments = [];
2604
+ if (includeLogs) {
2605
+ const spinner2 = ora6("Collecting log files...").start();
2606
+ try {
2607
+ attachments = await collectLogFiles();
2608
+ spinner2.succeed(`Collected ${attachments.length} log file(s)`);
2609
+ if (attachments.length > 0) {
2610
+ logger.log("Log files:", chalk.gray);
2611
+ attachments.forEach((att) => {
2612
+ logger.log(` - ${att.name} (${(att.size / 1024).toFixed(1)} KB)`, chalk.gray);
2613
+ });
2614
+ logger.log("");
2615
+ } else {
2616
+ logger.log("No log files found.", chalk.gray);
2617
+ logger.log("");
2618
+ }
2619
+ } catch (error) {
2620
+ spinner2.fail("Failed to collect log files");
2621
+ debug4("Log collection error:", error);
2622
+ logger.warn("Continuing without log files...");
2623
+ }
2624
+ }
2625
+ const spinner = ora6("Sending feedback...").start();
2626
+ try {
2627
+ const result = await sendFeedbackToApi(feedbackMessage, attachments, isAnonymous);
2628
+ spinner.succeed("Feedback sent successfully!");
2629
+ logger.success("\nThank you for your feedback!");
2630
+ logger.log(`Feedback ID: ${result.id}`, chalk.gray);
2631
+ if (isAnonymous) {
2632
+ logger.log("Type: Anonymous", chalk.gray);
2633
+ } else {
2634
+ logger.log("Type: Authenticated", chalk.gray);
2635
+ }
2636
+ if (attachments.length > 0) {
2637
+ logger.log(`Attachments: ${attachments.length}`, chalk.gray);
2638
+ }
2639
+ logger.log("\nWe appreciate your input and will review it soon.", chalk.cyan);
2640
+ } catch (error) {
2641
+ spinner.fail("Failed to send feedback");
2642
+ if (error instanceof Error) {
2643
+ logger.error(`
2644
+ ${error.message}`);
2645
+ } else {
2646
+ logger.error("\nAn unknown error occurred.");
2647
+ }
2648
+ if (DEBUG_MODE4) {
2649
+ debug4("Full error:", error);
2650
+ }
2651
+ process.exit(1);
2652
+ }
2653
+ }
2654
+ __name(sendFeedbackCommand, "sendFeedbackCommand");
2655
+
2656
+ // src/commands/projects.ts
2657
+ import ora7 from "ora";
2365
2658
  var API_ENDPOINT = "/api/projects";
2366
2659
  async function projectsListCommand() {
2367
2660
  const apiKey = await getApiKey();
@@ -2370,7 +2663,7 @@ async function projectsListCommand() {
2370
2663
  logger.gray("Run `leanmcp login` first to authenticate.\n");
2371
2664
  process.exit(1);
2372
2665
  }
2373
- const spinner = ora6("Fetching projects...").start();
2666
+ const spinner = ora7("Fetching projects...").start();
2374
2667
  try {
2375
2668
  const apiUrl = await getApiUrl();
2376
2669
  const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
@@ -2415,7 +2708,7 @@ async function projectsGetCommand(projectId) {
2415
2708
  logger.gray("Run `leanmcp login` first to authenticate.\n");
2416
2709
  process.exit(1);
2417
2710
  }
2418
- const spinner = ora6("Fetching project...").start();
2711
+ const spinner = ora7("Fetching project...").start();
2419
2712
  try {
2420
2713
  const apiUrl = await getApiUrl();
2421
2714
  const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
@@ -2470,7 +2763,7 @@ async function projectsDeleteCommand(projectId, options = {}) {
2470
2763
  return;
2471
2764
  }
2472
2765
  }
2473
- const spinner = ora6("Deleting project...").start();
2766
+ const spinner = ora7("Deleting project...").start();
2474
2767
  try {
2475
2768
  const apiUrl = await getApiUrl();
2476
2769
  const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
@@ -2497,50 +2790,50 @@ ${error instanceof Error ? error.message : String(error)}`);
2497
2790
  __name(projectsDeleteCommand, "projectsDeleteCommand");
2498
2791
 
2499
2792
  // src/commands/env.ts
2500
- import ora7 from "ora";
2501
- import path10 from "path";
2502
- import fs10 from "fs-extra";
2793
+ import ora8 from "ora";
2794
+ import path11 from "path";
2795
+ import fs11 from "fs-extra";
2503
2796
  import { confirm as confirm3 } from "@inquirer/prompts";
2504
- var DEBUG_MODE4 = false;
2797
+ var DEBUG_MODE5 = false;
2505
2798
  function setEnvDebugMode(enabled) {
2506
- DEBUG_MODE4 = enabled;
2799
+ DEBUG_MODE5 = enabled;
2507
2800
  }
2508
2801
  __name(setEnvDebugMode, "setEnvDebugMode");
2509
- function debug4(message, ...args) {
2510
- if (DEBUG_MODE4) {
2802
+ function debug5(message, ...args) {
2803
+ if (DEBUG_MODE5) {
2511
2804
  console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
2512
2805
  }
2513
2806
  }
2514
- __name(debug4, "debug");
2807
+ __name(debug5, "debug");
2515
2808
  async function debugFetch2(url, options = {}) {
2516
- debug4(`HTTP ${options.method || "GET"} ${url}`);
2809
+ debug5(`HTTP ${options.method || "GET"} ${url}`);
2517
2810
  if (options.body && typeof options.body === "string") {
2518
2811
  try {
2519
2812
  const body = JSON.parse(options.body);
2520
- debug4("Request body:", JSON.stringify(body, null, 2));
2813
+ debug5("Request body:", JSON.stringify(body, null, 2));
2521
2814
  } catch {
2522
- debug4("Request body:", options.body);
2815
+ debug5("Request body:", options.body);
2523
2816
  }
2524
2817
  }
2525
2818
  const startTime = Date.now();
2526
2819
  const response = await fetch(url, options);
2527
2820
  const duration = Date.now() - startTime;
2528
- debug4(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
2821
+ debug5(`Response: ${response.status} ${response.statusText} (${duration}ms)`);
2529
2822
  return response;
2530
2823
  }
2531
2824
  __name(debugFetch2, "debugFetch");
2532
2825
  var LEANMCP_CONFIG_DIR2 = ".leanmcp";
2533
2826
  var LEANMCP_CONFIG_FILE2 = "config.json";
2534
2827
  async function readLeanMCPConfig2(projectPath) {
2535
- const configPath = path10.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
2828
+ const configPath = path11.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
2536
2829
  try {
2537
- if (await fs10.pathExists(configPath)) {
2538
- const config = await fs10.readJSON(configPath);
2539
- debug4("Found existing .leanmcp config:", config);
2830
+ if (await fs11.pathExists(configPath)) {
2831
+ const config = await fs11.readJSON(configPath);
2832
+ debug5("Found existing .leanmcp config:", config);
2540
2833
  return config;
2541
2834
  }
2542
2835
  } catch (e) {
2543
- debug4("Could not read .leanmcp config:", e);
2836
+ debug5("Could not read .leanmcp config:", e);
2544
2837
  }
2545
2838
  return null;
2546
2839
  }
@@ -2553,7 +2846,7 @@ async function getDeploymentContext(folderPath) {
2553
2846
  return null;
2554
2847
  }
2555
2848
  const apiUrl = await getApiUrl();
2556
- const absolutePath = path10.resolve(process.cwd(), folderPath);
2849
+ const absolutePath = path11.resolve(process.cwd(), folderPath);
2557
2850
  const config = await readLeanMCPConfig2(absolutePath);
2558
2851
  if (!config) {
2559
2852
  logger.error("No deployment found.");
@@ -2580,7 +2873,7 @@ async function envListCommand(folderPath, options = {}) {
2580
2873
  process.exit(1);
2581
2874
  }
2582
2875
  const { apiKey, apiUrl, config } = context;
2583
- const spinner = ora7("Fetching environment variables...").start();
2876
+ const spinner = ora8("Fetching environment variables...").start();
2584
2877
  try {
2585
2878
  const reveal = options.reveal ? "?reveal=true" : "";
2586
2879
  const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env${reveal}`, {
@@ -2621,7 +2914,7 @@ async function envSetCommand(keyValue, folderPath, options = {}) {
2621
2914
  const { apiKey, apiUrl, config } = context;
2622
2915
  let variables = {};
2623
2916
  if (options.file) {
2624
- const spinner2 = ora7(`Loading from ${options.file}...`).start();
2917
+ const spinner2 = ora8(`Loading from ${options.file}...`).start();
2625
2918
  try {
2626
2919
  variables = await loadEnvFile(options.file);
2627
2920
  spinner2.succeed(`Loaded ${Object.keys(variables).length} variable(s) from ${options.file}`);
@@ -2662,7 +2955,7 @@ ${error instanceof Error ? error.message : String(error)}`);
2662
2955
  logger.warn("No variables to set.\n");
2663
2956
  return;
2664
2957
  }
2665
- const spinner = ora7("Updating environment variables...").start();
2958
+ const spinner = ora8("Updating environment variables...").start();
2666
2959
  try {
2667
2960
  const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env`, {
2668
2961
  method: "PUT",
@@ -2747,7 +3040,7 @@ async function envRemoveCommand(key, folderPath, options = {}) {
2747
3040
  return;
2748
3041
  }
2749
3042
  }
2750
- const spinner = ora7(`Removing ${key}...`).start();
3043
+ const spinner = ora8(`Removing ${key}...`).start();
2751
3044
  try {
2752
3045
  const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env/${key}`, {
2753
3046
  method: "DELETE",
@@ -2776,7 +3069,7 @@ async function envPullCommand(folderPath, options = {}) {
2776
3069
  }
2777
3070
  const { apiKey, apiUrl, config } = context;
2778
3071
  const outputFile = options.file || ".env.remote";
2779
- const spinner = ora7("Fetching environment variables...").start();
3072
+ const spinner = ora8("Fetching environment variables...").start();
2780
3073
  try {
2781
3074
  const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env?reveal=true`, {
2782
3075
  headers: {
@@ -2815,7 +3108,7 @@ async function envPushCommand(folderPath, options = {}) {
2815
3108
  }
2816
3109
  const { apiKey, apiUrl, config } = context;
2817
3110
  const inputFile = options.file || ".env";
2818
- const loadSpinner = ora7(`Loading from ${inputFile}...`).start();
3111
+ const loadSpinner = ora8(`Loading from ${inputFile}...`).start();
2819
3112
  let variables;
2820
3113
  try {
2821
3114
  variables = await loadEnvFile(inputFile);
@@ -2842,7 +3135,7 @@ ${error instanceof Error ? error.message : String(error)}`);
2842
3135
  return;
2843
3136
  }
2844
3137
  }
2845
- const pushSpinner = ora7("Pushing environment variables...").start();
3138
+ const pushSpinner = ora8("Pushing environment variables...").start();
2846
3139
  try {
2847
3140
  const response = await debugFetch2(`${apiUrl}/api/lambda-deploy/${config.deploymentId}/env`, {
2848
3141
  method: "PUT",
@@ -3178,12 +3471,16 @@ vite.config.ts.timestamp-*
3178
3471
  var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
3179
3472
 
3180
3473
  /**
3181
- * Example service demonstrating LeanMCP SDK decorators
3474
+ * ${projectName} - Production-ready MCP server example
3182
3475
  *
3183
- * This is a simple example to get you started. Add your own tools, resources, and prompts here!
3476
+ * This example demonstrates LeanMCP's core features:
3477
+ * - Schema validation with decorators
3478
+ * - Type-safe tool definitions
3479
+ * - Resource and prompt capabilities
3480
+ * - Production-ready structure
3184
3481
  */
3185
3482
 
3186
- // Input schema with validation decorators
3483
+ // Input schemas with validation decorators
3187
3484
  class CalculateInput {
3188
3485
  @SchemaConstraint({ description: "First number" })
3189
3486
  a!: number;
@@ -3208,13 +3505,13 @@ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import
3208
3505
  message!: string;
3209
3506
  }
3210
3507
 
3211
- export class ExampleService {
3508
+ export class ${projectName}Service {
3509
+ // CALCULATION TOOL - Shows schema validation
3212
3510
  @Tool({
3213
3511
  description: "Perform arithmetic operations with automatic schema validation",
3214
3512
  inputClass: CalculateInput
3215
3513
  })
3216
3514
  async calculate(input: CalculateInput) {
3217
- // Ensure numerical operations by explicitly converting to numbers
3218
3515
  const a = Number(input.a);
3219
3516
  const b = Number(input.b);
3220
3517
  let result: number;
@@ -3243,14 +3540,17 @@ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import
3243
3540
  text: JSON.stringify({
3244
3541
  operation: input.operation || "add",
3245
3542
  operands: { a: input.a, b: input.b },
3246
- result
3543
+ result,
3544
+ timestamp: new Date().toISOString(),
3545
+ server: "${projectName}"
3247
3546
  }, null, 2)
3248
3547
  }]
3249
3548
  };
3250
3549
  }
3251
3550
 
3551
+ // ECHO TOOL - Shows basic functionality
3252
3552
  @Tool({
3253
- description: "Echo a message back",
3553
+ description: "Echo a message back with timestamp",
3254
3554
  inputClass: EchoInput
3255
3555
  })
3256
3556
  async echo(input: EchoInput) {
@@ -3259,13 +3559,15 @@ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import
3259
3559
  type: "text" as const,
3260
3560
  text: JSON.stringify({
3261
3561
  echoed: input.message,
3262
- timestamp: new Date().toISOString()
3562
+ timestamp: new Date().toISOString(),
3563
+ server: "${projectName}"
3263
3564
  }, null, 2)
3264
3565
  }]
3265
3566
  };
3266
3567
  }
3267
3568
 
3268
- @Resource({ description: "Get server information" })
3569
+ // SERVER INFO RESOURCE - Shows resource capabilities
3570
+ @Resource({ description: "Get server information and health status" })
3269
3571
  async serverInfo() {
3270
3572
  return {
3271
3573
  contents: [{
@@ -3274,25 +3576,45 @@ var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import
3274
3576
  text: JSON.stringify({
3275
3577
  name: "${projectName}",
3276
3578
  version: "1.0.0",
3277
- uptime: process.uptime()
3579
+ status: "healthy",
3580
+ uptime: Math.floor(process.uptime()),
3581
+ memory: {
3582
+ used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
3583
+ total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024)
3584
+ },
3585
+ features: [
3586
+ "Schema validation with decorators",
3587
+ "Type-safe tool definitions",
3588
+ "Resource endpoints",
3589
+ "Prompt templates"
3590
+ ],
3591
+ timestamp: new Date().toISOString()
3278
3592
  }, null, 2)
3279
3593
  }]
3280
3594
  };
3281
3595
  }
3282
3596
 
3283
- @Prompt({ description: "Generate a greeting prompt" })
3284
- async greeting(args: { name?: string }) {
3597
+ // WELCOME PROMPT - Shows prompt capabilities
3598
+ @Prompt({ description: "Generate a welcome prompt for the server" })
3599
+ async welcome(args: { name?: string }) {
3285
3600
  return {
3286
3601
  messages: [{
3287
3602
  role: "user" as const,
3288
3603
  content: {
3289
3604
  type: "text" as const,
3290
- text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
3291
- }
3292
- }]
3293
- };
3294
- }
3295
- }
3605
+ text: \`Welcome \${args.name || 'there'} to ${projectName}!
3606
+
3607
+ Your MCP server is running with these tools:
3608
+ - calculate: Perform arithmetic operations
3609
+ - echo: Echo messages back
3610
+ - serverInfo: Get server status and information
3611
+
3612
+ Try calling these tools to see LeanMCP in action!\`
3613
+ }
3614
+ }]
3615
+ };
3616
+ }
3617
+ }
3296
3618
  `, "getExampleServiceTemplate");
3297
3619
 
3298
3620
  // src/templates/main_ts_v1.ts
@@ -3302,6 +3624,14 @@ import { createHTTPServer } from "@leanmcp/core";
3302
3624
  // Load environment variables
3303
3625
  dotenv.config();
3304
3626
 
3627
+ console.log("Starting ${projectName} MCP Server...");
3628
+ console.log("Features included:");
3629
+ console.log(" Schema validation with decorators");
3630
+ console.log(" Resource endpoints");
3631
+ console.log(" Prompt templates");
3632
+ console.log(" Type-safe tool definitions");
3633
+ console.log("");
3634
+
3305
3635
  // Services are automatically discovered from ./mcp directory
3306
3636
  await createHTTPServer({
3307
3637
  name: "${projectName}",
@@ -3309,10 +3639,27 @@ await createHTTPServer({
3309
3639
  port: 3001,
3310
3640
  cors: true,
3311
3641
  logging: true${dashboardLine}
3312
- // stateless: false, // Enable stateful mode (uses DynamoDB on Lambda for session persistence)
3313
3642
  });
3314
3643
 
3315
- console.log("\\n${projectName} MCP Server");
3644
+ console.log("\\n${projectName} MCP Server is running!");
3645
+ console.log("\\nTry these commands to test your server:");
3646
+ console.log("");
3647
+ console.log("# Test calculation tool (schema validation)");
3648
+ console.log('curl -X POST http://localhost:3001/mcp \\\\');
3649
+ console.log(' -H "Content-Type: application/json" \\\\');
3650
+ console.log(' -d '{"method": "tools/call", "params": {"name": "calculate", "arguments": {"a": 10, "b": 5, "operation": "add"}}}'');
3651
+ console.log("");
3652
+ console.log("# Test echo tool");
3653
+ console.log('curl -X POST http://localhost:3001/mcp \\\\');
3654
+ console.log(' -H "Content-Type: application/json" \\\\');
3655
+ console.log(' -d '{"method": "tools/call", "params": {"name": "echo", "arguments": {"message": "Hello LeanMCP!"}}}'');
3656
+ console.log("");
3657
+ console.log("# Get server information (resource)");
3658
+ console.log('curl -X POST http://localhost:3001/mcp \\\\');
3659
+ console.log(' -H "Content-Type: application/json" \\\\');
3660
+ console.log(' -d '{"method": "resources/read", "params": {"uri": "server://info"}}'');
3661
+ console.log("");
3662
+ console.log("Ready to customize - add your own tools, resources, and prompts!");
3316
3663
  `, "getMainTsTemplate");
3317
3664
 
3318
3665
  // src/templates/service_index_v1.ts
@@ -3635,30 +3982,30 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
3635
3982
  projectName,
3636
3983
  ...options
3637
3984
  });
3638
- const spinner = ora8(`Creating project ${projectName}...`).start();
3639
- const targetDir = path11.join(process.cwd(), projectName);
3640
- if (fs11.existsSync(targetDir)) {
3985
+ const spinner = ora9(`Creating project ${projectName}...`).start();
3986
+ const targetDir = path12.join(process.cwd(), projectName);
3987
+ if (fs12.existsSync(targetDir)) {
3641
3988
  spinner.fail(`Folder ${projectName} already exists.`);
3642
3989
  process.exit(1);
3643
3990
  }
3644
- await fs11.mkdirp(targetDir);
3991
+ await fs12.mkdirp(targetDir);
3645
3992
  const isPython = options.python === true;
3646
3993
  if (isPython) {
3647
3994
  const requirements = getPythonRequirementsTemplate();
3648
- await fs11.writeFile(path11.join(targetDir, "requirements.txt"), requirements);
3995
+ await fs12.writeFile(path12.join(targetDir, "requirements.txt"), requirements);
3649
3996
  const mainPy = getPythonMainTemplate(projectName);
3650
- await fs11.writeFile(path11.join(targetDir, "main.py"), mainPy);
3651
- await fs11.writeFile(path11.join(targetDir, ".gitignore"), pythonGitignoreTemplate);
3997
+ await fs12.writeFile(path12.join(targetDir, "main.py"), mainPy);
3998
+ await fs12.writeFile(path12.join(targetDir, ".gitignore"), pythonGitignoreTemplate);
3652
3999
  const env = `# Server Configuration
3653
4000
  PORT=3001
3654
4001
 
3655
4002
  # Add your environment variables here
3656
4003
  `;
3657
- await fs11.writeFile(path11.join(targetDir, ".env"), env);
4004
+ await fs12.writeFile(path12.join(targetDir, ".env"), env);
3658
4005
  const readme = getPythonReadmeTemplate(projectName);
3659
- await fs11.writeFile(path11.join(targetDir, "README.md"), readme);
4006
+ await fs12.writeFile(path12.join(targetDir, "README.md"), readme);
3660
4007
  } else {
3661
- await fs11.mkdirp(path11.join(targetDir, "mcp", "example"));
4008
+ await fs12.mkdirp(path12.join(targetDir, "mcp", "example"));
3662
4009
  const pkg2 = {
3663
4010
  name: projectName,
3664
4011
  version: "1.0.0",
@@ -3694,7 +4041,7 @@ PORT=3001
3694
4041
  typescript: "^5.6.3"
3695
4042
  }
3696
4043
  };
3697
- await fs11.writeJSON(path11.join(targetDir, "package.json"), pkg2, {
4044
+ await fs12.writeJSON(path12.join(targetDir, "package.json"), pkg2, {
3698
4045
  spaces: 2
3699
4046
  });
3700
4047
  const tsconfig = {
@@ -3717,15 +4064,15 @@ PORT=3001
3717
4064
  "dist"
3718
4065
  ]
3719
4066
  };
3720
- await fs11.writeJSON(path11.join(targetDir, "tsconfig.json"), tsconfig, {
4067
+ await fs12.writeJSON(path12.join(targetDir, "tsconfig.json"), tsconfig, {
3721
4068
  spaces: 2
3722
4069
  });
3723
4070
  const dashboardLine = options.dashboard === false ? `
3724
4071
  dashboard: false, // Dashboard disabled via --no-dashboard` : "";
3725
4072
  const mainTs = getMainTsTemplate(projectName, dashboardLine);
3726
- await fs11.writeFile(path11.join(targetDir, "main.ts"), mainTs);
4073
+ await fs12.writeFile(path12.join(targetDir, "main.ts"), mainTs);
3727
4074
  const exampleServiceTs = getExampleServiceTemplate(projectName);
3728
- await fs11.writeFile(path11.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
4075
+ await fs12.writeFile(path12.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
3729
4076
  const gitignore = gitignoreTemplate;
3730
4077
  const env = `# Server Configuration
3731
4078
  PORT=3001
@@ -3733,10 +4080,10 @@ NODE_ENV=development
3733
4080
 
3734
4081
  # Add your environment variables here
3735
4082
  `;
3736
- await fs11.writeFile(path11.join(targetDir, ".gitignore"), gitignore);
3737
- await fs11.writeFile(path11.join(targetDir, ".env"), env);
4083
+ await fs12.writeFile(path12.join(targetDir, ".gitignore"), gitignore);
4084
+ await fs12.writeFile(path12.join(targetDir, ".env"), env);
3738
4085
  const readme = getReadmeTemplate(projectName);
3739
- await fs11.writeFile(path11.join(targetDir, "README.md"), readme);
4086
+ await fs12.writeFile(path12.join(targetDir, "README.md"), readme);
3740
4087
  }
3741
4088
  spinner.succeed(`Project ${projectName} created!`);
3742
4089
  logger.log("\nSuccess! Your MCP server is ready.\n", chalk.green);
@@ -3783,7 +4130,7 @@ NODE_ENV=development
3783
4130
  default: true
3784
4131
  });
3785
4132
  if (shouldInstall) {
3786
- const installSpinner = ora8("Installing dependencies...").start();
4133
+ const installSpinner = ora9("Installing dependencies...").start();
3787
4134
  try {
3788
4135
  await new Promise((resolve, reject) => {
3789
4136
  const npmInstall = spawn4("npm", [
@@ -3856,24 +4203,33 @@ NODE_ENV=development
3856
4203
  logger.log("To deploy to LeanMCP cloud:", chalk.cyan);
3857
4204
  logger.log(` cd ${projectName}`, chalk.gray);
3858
4205
  logger.log(` leanmcp deploy .`, chalk.gray);
4206
+ logger.log("\nSend us feedback:", chalk.cyan);
4207
+ logger.log(' leanmcp send-feedback "Great tool!"\n', chalk.gray);
3859
4208
  }
3860
4209
  });
4210
+ program.command("send-feedback [message]").description("Send feedback to the LeanMCP team").option("--anon", "Send feedback anonymously").option("--include-logs", "Include local log files with feedback").action(async (message, options) => {
4211
+ trackCommand("send-feedback", {
4212
+ hasMessage: !!message,
4213
+ ...options
4214
+ });
4215
+ await sendFeedbackCommand(message, options);
4216
+ });
3861
4217
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
3862
4218
  const cwd = process.cwd();
3863
- const mcpDir = path11.join(cwd, "mcp");
3864
- if (!fs11.existsSync(path11.join(cwd, "main.ts"))) {
4219
+ const mcpDir = path12.join(cwd, "mcp");
4220
+ if (!fs12.existsSync(path12.join(cwd, "main.ts"))) {
3865
4221
  logger.log("ERROR: Not a LeanMCP project (main.ts missing).", chalk.red);
3866
4222
  process.exit(1);
3867
4223
  }
3868
- const serviceDir = path11.join(mcpDir, serviceName);
3869
- const serviceFile = path11.join(serviceDir, "index.ts");
3870
- if (fs11.existsSync(serviceDir)) {
4224
+ const serviceDir = path12.join(mcpDir, serviceName);
4225
+ const serviceFile = path12.join(serviceDir, "index.ts");
4226
+ if (fs12.existsSync(serviceDir)) {
3871
4227
  logger.log(`ERROR: Service ${serviceName} already exists.`, chalk.red);
3872
4228
  process.exit(1);
3873
4229
  }
3874
- await fs11.mkdirp(serviceDir);
4230
+ await fs12.mkdirp(serviceDir);
3875
4231
  const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
3876
- await fs11.writeFile(serviceFile, indexTs);
4232
+ await fs12.writeFile(serviceFile, indexTs);
3877
4233
  logger.log(`\\nCreated new service: ${chalk.bold(serviceName)}`, chalk.green);
3878
4234
  logger.log(` File: mcp/${serviceName}/index.ts`, chalk.gray);
3879
4235
  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.5",
3
+ "version": "0.5.7",
4
4
  "description": "Command-line interface for scaffolding LeanMCP projects",
5
5
  "bin": {
6
6
  "leanmcp": "bin/leanmcp.js"