@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.
- package/README.md +124 -15
- package/dist/index.js +435 -79
- 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://
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
##
|
|
471
|
+
## Links
|
|
367
472
|
|
|
368
|
-
|
|
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
|
|
7
|
-
import
|
|
8
|
-
import
|
|
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/
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2501
|
-
import
|
|
2502
|
-
import
|
|
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
|
|
2797
|
+
var DEBUG_MODE5 = false;
|
|
2505
2798
|
function setEnvDebugMode(enabled) {
|
|
2506
|
-
|
|
2799
|
+
DEBUG_MODE5 = enabled;
|
|
2507
2800
|
}
|
|
2508
2801
|
__name(setEnvDebugMode, "setEnvDebugMode");
|
|
2509
|
-
function
|
|
2510
|
-
if (
|
|
2802
|
+
function debug5(message, ...args) {
|
|
2803
|
+
if (DEBUG_MODE5) {
|
|
2511
2804
|
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
2512
2805
|
}
|
|
2513
2806
|
}
|
|
2514
|
-
__name(
|
|
2807
|
+
__name(debug5, "debug");
|
|
2515
2808
|
async function debugFetch2(url, options = {}) {
|
|
2516
|
-
|
|
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
|
-
|
|
2813
|
+
debug5("Request body:", JSON.stringify(body, null, 2));
|
|
2521
2814
|
} catch {
|
|
2522
|
-
|
|
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
|
-
|
|
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 =
|
|
2828
|
+
const configPath = path11.join(projectPath, LEANMCP_CONFIG_DIR2, LEANMCP_CONFIG_FILE2);
|
|
2536
2829
|
try {
|
|
2537
|
-
if (await
|
|
2538
|
-
const config = await
|
|
2539
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
*
|
|
3474
|
+
* ${projectName} - Production-ready MCP server example
|
|
3182
3475
|
*
|
|
3183
|
-
* This
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3284
|
-
|
|
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: \`
|
|
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 =
|
|
3639
|
-
const targetDir =
|
|
3640
|
-
if (
|
|
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
|
|
3991
|
+
await fs12.mkdirp(targetDir);
|
|
3645
3992
|
const isPython = options.python === true;
|
|
3646
3993
|
if (isPython) {
|
|
3647
3994
|
const requirements = getPythonRequirementsTemplate();
|
|
3648
|
-
await
|
|
3995
|
+
await fs12.writeFile(path12.join(targetDir, "requirements.txt"), requirements);
|
|
3649
3996
|
const mainPy = getPythonMainTemplate(projectName);
|
|
3650
|
-
await
|
|
3651
|
-
await
|
|
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
|
|
4004
|
+
await fs12.writeFile(path12.join(targetDir, ".env"), env);
|
|
3658
4005
|
const readme = getPythonReadmeTemplate(projectName);
|
|
3659
|
-
await
|
|
4006
|
+
await fs12.writeFile(path12.join(targetDir, "README.md"), readme);
|
|
3660
4007
|
} else {
|
|
3661
|
-
await
|
|
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
|
|
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
|
|
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
|
|
4073
|
+
await fs12.writeFile(path12.join(targetDir, "main.ts"), mainTs);
|
|
3727
4074
|
const exampleServiceTs = getExampleServiceTemplate(projectName);
|
|
3728
|
-
await
|
|
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
|
|
3737
|
-
await
|
|
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
|
|
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 =
|
|
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 =
|
|
3864
|
-
if (!
|
|
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 =
|
|
3869
|
-
const serviceFile =
|
|
3870
|
-
if (
|
|
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
|
|
4230
|
+
await fs12.mkdirp(serviceDir);
|
|
3875
4231
|
const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
|
|
3876
|
-
await
|
|
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);
|