@leanmcp/cli 0.2.14 → 0.3.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 +770 -552
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,13 +3,13 @@ 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
|
|
9
|
-
import
|
|
6
|
+
import chalk7 from "chalk";
|
|
7
|
+
import fs8 from "fs-extra";
|
|
8
|
+
import path8 from "path";
|
|
9
|
+
import ora7 from "ora";
|
|
10
10
|
import { createRequire } from "module";
|
|
11
11
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
12
|
-
import { spawn as
|
|
12
|
+
import { spawn as spawn4 } from "child_process";
|
|
13
13
|
|
|
14
14
|
// src/commands/dev.ts
|
|
15
15
|
import { spawn } from "child_process";
|
|
@@ -17,6 +17,7 @@ import chalk from "chalk";
|
|
|
17
17
|
import ora from "ora";
|
|
18
18
|
import path3 from "path";
|
|
19
19
|
import fs3 from "fs-extra";
|
|
20
|
+
import crypto from "crypto";
|
|
20
21
|
import chokidar from "chokidar";
|
|
21
22
|
|
|
22
23
|
// src/vite/scanUIApp.ts
|
|
@@ -131,6 +132,29 @@ import react from "@vitejs/plugin-react";
|
|
|
131
132
|
import { viteSingleFile } from "vite-plugin-singlefile";
|
|
132
133
|
import fs2 from "fs-extra";
|
|
133
134
|
import path2 from "path";
|
|
135
|
+
function resolveReactDependency(startDir, packageName) {
|
|
136
|
+
const localPath = path2.join(startDir, "node_modules", packageName);
|
|
137
|
+
if (fs2.existsSync(localPath)) {
|
|
138
|
+
return localPath;
|
|
139
|
+
}
|
|
140
|
+
let currentDir = path2.dirname(startDir);
|
|
141
|
+
const maxDepth = 10;
|
|
142
|
+
let depth = 0;
|
|
143
|
+
while (depth < maxDepth) {
|
|
144
|
+
const nodeModulesPath = path2.join(currentDir, "node_modules", packageName);
|
|
145
|
+
if (fs2.existsSync(nodeModulesPath)) {
|
|
146
|
+
return nodeModulesPath;
|
|
147
|
+
}
|
|
148
|
+
const parentDir = path2.dirname(currentDir);
|
|
149
|
+
if (parentDir === currentDir) {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
currentDir = parentDir;
|
|
153
|
+
depth++;
|
|
154
|
+
}
|
|
155
|
+
return localPath;
|
|
156
|
+
}
|
|
157
|
+
__name(resolveReactDependency, "resolveReactDependency");
|
|
134
158
|
async function buildUIComponent(uiApp, projectDir, isDev = false) {
|
|
135
159
|
const { componentPath, componentName, resourceUri } = uiApp;
|
|
136
160
|
const safeFileName = resourceUri.replace("ui://", "").replace(/\//g, "-") + ".html";
|
|
@@ -318,6 +342,8 @@ createRoot(document.getElementById('root')!).render(
|
|
|
318
342
|
);
|
|
319
343
|
`);
|
|
320
344
|
try {
|
|
345
|
+
const reactPath = resolveReactDependency(projectDir, "react");
|
|
346
|
+
const reactDomPath = resolveReactDependency(projectDir, "react-dom");
|
|
321
347
|
await vite.build({
|
|
322
348
|
root: tempDir,
|
|
323
349
|
plugins: [
|
|
@@ -326,11 +352,11 @@ createRoot(document.getElementById('root')!).render(
|
|
|
326
352
|
],
|
|
327
353
|
resolve: {
|
|
328
354
|
alias: {
|
|
329
|
-
// Resolve React from
|
|
330
|
-
"react":
|
|
331
|
-
"react-dom":
|
|
332
|
-
"react/jsx-runtime": path2.join(
|
|
333
|
-
"react/jsx-dev-runtime": path2.join(
|
|
355
|
+
// Resolve React from project or workspace root node_modules
|
|
356
|
+
"react": reactPath,
|
|
357
|
+
"react-dom": reactDomPath,
|
|
358
|
+
"react/jsx-runtime": path2.join(reactPath, "jsx-runtime"),
|
|
359
|
+
"react/jsx-dev-runtime": path2.join(reactPath, "jsx-dev-runtime")
|
|
334
360
|
}
|
|
335
361
|
},
|
|
336
362
|
css: {
|
|
@@ -386,8 +412,49 @@ async function writeUIManifest(manifest, projectDir) {
|
|
|
386
412
|
});
|
|
387
413
|
}
|
|
388
414
|
__name(writeUIManifest, "writeUIManifest");
|
|
415
|
+
async function deleteUIComponent(uri, projectDir) {
|
|
416
|
+
const safeFileName = uri.replace("ui://", "").replace(/\//g, "-") + ".html";
|
|
417
|
+
const htmlPath = path2.join(projectDir, "dist", "ui", safeFileName);
|
|
418
|
+
if (await fs2.pathExists(htmlPath)) {
|
|
419
|
+
await fs2.remove(htmlPath);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
__name(deleteUIComponent, "deleteUIComponent");
|
|
389
423
|
|
|
390
424
|
// src/commands/dev.ts
|
|
425
|
+
function computeHash(filePath) {
|
|
426
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
427
|
+
return crypto.createHash("md5").update(content).digest("hex");
|
|
428
|
+
}
|
|
429
|
+
__name(computeHash, "computeHash");
|
|
430
|
+
function computeDiff(previousUIApps, currentUIApps, hashCache) {
|
|
431
|
+
const previousURIs = new Set(previousUIApps.map((app) => app.resourceUri));
|
|
432
|
+
const currentURIs = new Set(currentUIApps.map((app) => app.resourceUri));
|
|
433
|
+
const removed = Array.from(previousURIs).filter((uri) => !currentURIs.has(uri));
|
|
434
|
+
const added = new Set(Array.from(currentURIs).filter((uri) => !previousURIs.has(uri)));
|
|
435
|
+
const addedOrChanged = [];
|
|
436
|
+
for (const app of currentUIApps) {
|
|
437
|
+
const isNew = added.has(app.resourceUri);
|
|
438
|
+
if (isNew) {
|
|
439
|
+
addedOrChanged.push(app);
|
|
440
|
+
} else {
|
|
441
|
+
const oldHash = hashCache.get(app.resourceUri);
|
|
442
|
+
let newHash;
|
|
443
|
+
if (fs3.existsSync(app.componentPath)) {
|
|
444
|
+
newHash = computeHash(app.componentPath);
|
|
445
|
+
}
|
|
446
|
+
if (oldHash !== newHash) {
|
|
447
|
+
addedOrChanged.push(app);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
removed,
|
|
453
|
+
added,
|
|
454
|
+
addedOrChanged
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
__name(computeDiff, "computeDiff");
|
|
391
458
|
async function devCommand() {
|
|
392
459
|
const cwd = process.cwd();
|
|
393
460
|
if (!await fs3.pathExists(path3.join(cwd, "main.ts"))) {
|
|
@@ -395,16 +462,13 @@ async function devCommand() {
|
|
|
395
462
|
console.error(chalk.gray("Run this command from your project root."));
|
|
396
463
|
process.exit(1);
|
|
397
464
|
}
|
|
398
|
-
console.log(chalk.cyan("\
|
|
465
|
+
console.log(chalk.cyan("\nLeanMCP Development Server\n"));
|
|
399
466
|
const scanSpinner = ora("Scanning for @UIApp components...").start();
|
|
400
467
|
const uiApps = await scanUIApp(cwd);
|
|
401
468
|
if (uiApps.length === 0) {
|
|
402
|
-
scanSpinner.
|
|
469
|
+
scanSpinner.info("No @UIApp components found");
|
|
403
470
|
} else {
|
|
404
|
-
scanSpinner.
|
|
405
|
-
for (const app of uiApps) {
|
|
406
|
-
console.log(chalk.gray(` \u2022 ${app.componentName} \u2192 ${app.resourceUri}`));
|
|
407
|
-
}
|
|
471
|
+
scanSpinner.info(`Found ${uiApps.length} @UIApp component(s)`);
|
|
408
472
|
}
|
|
409
473
|
const manifest = {};
|
|
410
474
|
if (uiApps.length > 0) {
|
|
@@ -422,10 +486,10 @@ async function devCommand() {
|
|
|
422
486
|
if (errors.length > 0) {
|
|
423
487
|
buildSpinner.warn("Built with warnings");
|
|
424
488
|
for (const error of errors) {
|
|
425
|
-
console.error(chalk.yellow(`
|
|
489
|
+
console.error(chalk.yellow(` ${error}`));
|
|
426
490
|
}
|
|
427
491
|
} else {
|
|
428
|
-
buildSpinner.
|
|
492
|
+
buildSpinner.info("UI components built");
|
|
429
493
|
}
|
|
430
494
|
}
|
|
431
495
|
console.log(chalk.cyan("\nStarting development server...\n"));
|
|
@@ -439,26 +503,59 @@ async function devCommand() {
|
|
|
439
503
|
shell: true
|
|
440
504
|
});
|
|
441
505
|
let watcher = null;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
watcher.on("change", async (changedPath) => {
|
|
448
|
-
const app = uiApps.find((a) => a.componentPath === changedPath);
|
|
449
|
-
if (!app) return;
|
|
450
|
-
console.log(chalk.cyan(`
|
|
451
|
-
[UI] Rebuilding ${app.componentName}...`));
|
|
452
|
-
const result = await buildUIComponent(app, cwd, true);
|
|
453
|
-
if (result.success) {
|
|
454
|
-
manifest[app.resourceUri] = result.htmlPath;
|
|
455
|
-
await writeUIManifest(manifest, cwd);
|
|
456
|
-
console.log(chalk.green(`[UI] ${app.componentName} rebuilt successfully`));
|
|
457
|
-
} else {
|
|
458
|
-
console.log(chalk.yellow(`[UI] ${app.componentName} build failed: ${result.error}`));
|
|
459
|
-
}
|
|
460
|
-
});
|
|
506
|
+
const componentHashCache = /* @__PURE__ */ new Map();
|
|
507
|
+
for (const app of uiApps) {
|
|
508
|
+
if (await fs3.pathExists(app.componentPath)) {
|
|
509
|
+
componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
|
|
510
|
+
}
|
|
461
511
|
}
|
|
512
|
+
let previousUIApps = uiApps;
|
|
513
|
+
const mcpPath = path3.join(cwd, "mcp");
|
|
514
|
+
watcher = chokidar.watch(mcpPath, {
|
|
515
|
+
ignoreInitial: true,
|
|
516
|
+
ignored: [
|
|
517
|
+
"**/node_modules/**",
|
|
518
|
+
"**/*.d.ts"
|
|
519
|
+
],
|
|
520
|
+
persistent: true,
|
|
521
|
+
awaitWriteFinish: {
|
|
522
|
+
stabilityThreshold: 100,
|
|
523
|
+
pollInterval: 50
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
let debounceTimer = null;
|
|
527
|
+
watcher.on("all", async (event, changedPath) => {
|
|
528
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
529
|
+
debounceTimer = setTimeout(async () => {
|
|
530
|
+
const currentUIApps = await scanUIApp(cwd);
|
|
531
|
+
const diff = computeDiff(previousUIApps, currentUIApps, componentHashCache);
|
|
532
|
+
if (diff.removed.length === 0 && diff.addedOrChanged.length === 0) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
for (const uri of diff.removed) {
|
|
536
|
+
console.log(chalk.yellow(`Removing ${uri}...`));
|
|
537
|
+
await deleteUIComponent(uri, cwd);
|
|
538
|
+
delete manifest[uri];
|
|
539
|
+
componentHashCache.delete(uri);
|
|
540
|
+
}
|
|
541
|
+
for (const app of diff.addedOrChanged) {
|
|
542
|
+
const action = diff.added.has(app.resourceUri) ? "Building" : "Rebuilding";
|
|
543
|
+
console.log(chalk.cyan(`${action} ${app.componentName}...`));
|
|
544
|
+
const result = await buildUIComponent(app, cwd, true);
|
|
545
|
+
if (result.success) {
|
|
546
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
547
|
+
if (await fs3.pathExists(app.componentPath)) {
|
|
548
|
+
componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
|
|
549
|
+
}
|
|
550
|
+
console.log(chalk.green(`${app.componentName} ${action.toLowerCase()} complete`));
|
|
551
|
+
} else {
|
|
552
|
+
console.log(chalk.yellow(`Build failed: ${result.error}`));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
await writeUIManifest(manifest, cwd);
|
|
556
|
+
previousUIApps = currentUIApps;
|
|
557
|
+
}, 150);
|
|
558
|
+
});
|
|
462
559
|
let isCleaningUp = false;
|
|
463
560
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
464
561
|
if (isCleaningUp) return;
|
|
@@ -476,20 +573,20 @@ async function devCommand() {
|
|
|
476
573
|
}
|
|
477
574
|
__name(devCommand, "devCommand");
|
|
478
575
|
|
|
479
|
-
// src/commands/
|
|
576
|
+
// src/commands/build.ts
|
|
480
577
|
import { spawn as spawn2 } from "child_process";
|
|
481
578
|
import chalk2 from "chalk";
|
|
482
579
|
import ora2 from "ora";
|
|
483
580
|
import path4 from "path";
|
|
484
581
|
import fs4 from "fs-extra";
|
|
485
|
-
async function
|
|
582
|
+
async function buildCommand() {
|
|
486
583
|
const cwd = process.cwd();
|
|
487
584
|
if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
|
|
488
585
|
console.error(chalk2.red("ERROR: Not a LeanMCP project (main.ts not found)."));
|
|
489
586
|
console.error(chalk2.gray("Run this command from your project root."));
|
|
490
587
|
process.exit(1);
|
|
491
588
|
}
|
|
492
|
-
console.log(chalk2.cyan("\n\u{
|
|
589
|
+
console.log(chalk2.cyan("\n\u{1F528} LeanMCP Build\n"));
|
|
493
590
|
const scanSpinner = ora2("Scanning for @UIApp components...").start();
|
|
494
591
|
const uiApps = await scanUIApp(cwd);
|
|
495
592
|
if (uiApps.length === 0) {
|
|
@@ -545,8 +642,83 @@ async function startCommand() {
|
|
|
545
642
|
console.error(chalk2.red(error instanceof Error ? error.message : String(error)));
|
|
546
643
|
process.exit(1);
|
|
547
644
|
}
|
|
548
|
-
console.log(chalk2.
|
|
549
|
-
|
|
645
|
+
console.log(chalk2.green("\nBuild complete!"));
|
|
646
|
+
console.log(chalk2.gray("\nTo start the server:"));
|
|
647
|
+
console.log(chalk2.cyan(" npm run start:node\n"));
|
|
648
|
+
}
|
|
649
|
+
__name(buildCommand, "buildCommand");
|
|
650
|
+
|
|
651
|
+
// src/commands/start.ts
|
|
652
|
+
import { spawn as spawn3 } from "child_process";
|
|
653
|
+
import chalk3 from "chalk";
|
|
654
|
+
import ora3 from "ora";
|
|
655
|
+
import path5 from "path";
|
|
656
|
+
import fs5 from "fs-extra";
|
|
657
|
+
async function startCommand() {
|
|
658
|
+
const cwd = process.cwd();
|
|
659
|
+
if (!await fs5.pathExists(path5.join(cwd, "main.ts"))) {
|
|
660
|
+
console.error(chalk3.red("ERROR: Not a LeanMCP project (main.ts not found)."));
|
|
661
|
+
console.error(chalk3.gray("Run this command from your project root."));
|
|
662
|
+
process.exit(1);
|
|
663
|
+
}
|
|
664
|
+
console.log(chalk3.cyan("\n\u{1F680} LeanMCP Production Build\n"));
|
|
665
|
+
const scanSpinner = ora3("Scanning for @UIApp components...").start();
|
|
666
|
+
const uiApps = await scanUIApp(cwd);
|
|
667
|
+
if (uiApps.length === 0) {
|
|
668
|
+
scanSpinner.succeed("No @UIApp components found");
|
|
669
|
+
} else {
|
|
670
|
+
scanSpinner.succeed(`Found ${uiApps.length} @UIApp component(s)`);
|
|
671
|
+
}
|
|
672
|
+
const manifest = {};
|
|
673
|
+
if (uiApps.length > 0) {
|
|
674
|
+
const buildSpinner = ora3("Building UI components...").start();
|
|
675
|
+
const errors = [];
|
|
676
|
+
for (const app of uiApps) {
|
|
677
|
+
const result = await buildUIComponent(app, cwd, false);
|
|
678
|
+
if (result.success) {
|
|
679
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
680
|
+
} else {
|
|
681
|
+
errors.push(`${app.componentName}: ${result.error}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
await writeUIManifest(manifest, cwd);
|
|
685
|
+
if (errors.length > 0) {
|
|
686
|
+
buildSpinner.fail("Build failed");
|
|
687
|
+
for (const error of errors) {
|
|
688
|
+
console.error(chalk3.red(` \u2717 ${error}`));
|
|
689
|
+
}
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
buildSpinner.succeed("UI components built");
|
|
693
|
+
}
|
|
694
|
+
const tscSpinner = ora3("Compiling TypeScript...").start();
|
|
695
|
+
try {
|
|
696
|
+
await new Promise((resolve, reject) => {
|
|
697
|
+
const tsc = spawn3("npx", [
|
|
698
|
+
"tsc"
|
|
699
|
+
], {
|
|
700
|
+
cwd,
|
|
701
|
+
stdio: "pipe",
|
|
702
|
+
shell: true
|
|
703
|
+
});
|
|
704
|
+
let stderr = "";
|
|
705
|
+
tsc.stderr?.on("data", (data) => {
|
|
706
|
+
stderr += data;
|
|
707
|
+
});
|
|
708
|
+
tsc.on("close", (code) => {
|
|
709
|
+
if (code === 0) resolve();
|
|
710
|
+
else reject(new Error(stderr || `tsc exited with code ${code}`));
|
|
711
|
+
});
|
|
712
|
+
tsc.on("error", reject);
|
|
713
|
+
});
|
|
714
|
+
tscSpinner.succeed("TypeScript compiled");
|
|
715
|
+
} catch (error) {
|
|
716
|
+
tscSpinner.fail("TypeScript compilation failed");
|
|
717
|
+
console.error(chalk3.red(error instanceof Error ? error.message : String(error)));
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
console.log(chalk3.cyan("\nStarting production server...\n"));
|
|
721
|
+
const server = spawn3("node", [
|
|
550
722
|
"dist/main.js"
|
|
551
723
|
], {
|
|
552
724
|
cwd,
|
|
@@ -557,7 +729,7 @@ async function startCommand() {
|
|
|
557
729
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
558
730
|
if (isCleaningUp) return;
|
|
559
731
|
isCleaningUp = true;
|
|
560
|
-
console.log(
|
|
732
|
+
console.log(chalk3.gray("\nShutting down..."));
|
|
561
733
|
server.kill("SIGTERM");
|
|
562
734
|
}, "cleanup");
|
|
563
735
|
process.on("SIGINT", cleanup);
|
|
@@ -569,10 +741,10 @@ async function startCommand() {
|
|
|
569
741
|
__name(startCommand, "startCommand");
|
|
570
742
|
|
|
571
743
|
// src/commands/login.ts
|
|
572
|
-
import
|
|
573
|
-
import
|
|
574
|
-
import
|
|
575
|
-
import
|
|
744
|
+
import chalk4 from "chalk";
|
|
745
|
+
import ora4 from "ora";
|
|
746
|
+
import path6 from "path";
|
|
747
|
+
import fs6 from "fs-extra";
|
|
576
748
|
import os from "os";
|
|
577
749
|
import { input, confirm } from "@inquirer/prompts";
|
|
578
750
|
var DEBUG_MODE = false;
|
|
@@ -582,16 +754,16 @@ function setDebugMode(enabled) {
|
|
|
582
754
|
__name(setDebugMode, "setDebugMode");
|
|
583
755
|
function debug(message, ...args) {
|
|
584
756
|
if (DEBUG_MODE) {
|
|
585
|
-
console.log(
|
|
757
|
+
console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
|
|
586
758
|
}
|
|
587
759
|
}
|
|
588
760
|
__name(debug, "debug");
|
|
589
|
-
var CONFIG_DIR =
|
|
590
|
-
var CONFIG_FILE =
|
|
761
|
+
var CONFIG_DIR = path6.join(os.homedir(), ".leanmcp");
|
|
762
|
+
var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
|
|
591
763
|
async function loadConfig() {
|
|
592
764
|
try {
|
|
593
|
-
if (await
|
|
594
|
-
return await
|
|
765
|
+
if (await fs6.pathExists(CONFIG_FILE)) {
|
|
766
|
+
return await fs6.readJSON(CONFIG_FILE);
|
|
595
767
|
}
|
|
596
768
|
} catch (error) {
|
|
597
769
|
}
|
|
@@ -599,8 +771,8 @@ async function loadConfig() {
|
|
|
599
771
|
}
|
|
600
772
|
__name(loadConfig, "loadConfig");
|
|
601
773
|
async function saveConfig(config) {
|
|
602
|
-
await
|
|
603
|
-
await
|
|
774
|
+
await fs6.ensureDir(CONFIG_DIR);
|
|
775
|
+
await fs6.writeJSON(CONFIG_FILE, config, {
|
|
604
776
|
spaces: 2
|
|
605
777
|
});
|
|
606
778
|
}
|
|
@@ -638,24 +810,24 @@ Allowed URLs:
|
|
|
638
810
|
}
|
|
639
811
|
__name(getApiUrl, "getApiUrl");
|
|
640
812
|
async function loginCommand() {
|
|
641
|
-
console.log(
|
|
813
|
+
console.log(chalk4.cyan("\nLeanMCP Login\n"));
|
|
642
814
|
const existingConfig = await loadConfig();
|
|
643
815
|
if (existingConfig.apiKey) {
|
|
644
|
-
console.log(
|
|
816
|
+
console.log(chalk4.yellow("You are already logged in."));
|
|
645
817
|
const shouldRelogin = await confirm({
|
|
646
818
|
message: "Do you want to replace the existing API key?",
|
|
647
819
|
default: false
|
|
648
820
|
});
|
|
649
821
|
if (!shouldRelogin) {
|
|
650
|
-
console.log(
|
|
822
|
+
console.log(chalk4.gray("\nLogin cancelled. Existing API key preserved."));
|
|
651
823
|
return;
|
|
652
824
|
}
|
|
653
825
|
}
|
|
654
|
-
console.log(
|
|
655
|
-
console.log(
|
|
656
|
-
console.log(
|
|
657
|
-
console.log(
|
|
658
|
-
console.log(
|
|
826
|
+
console.log(chalk4.white("To authenticate, you need an API key from LeanMCP.\n"));
|
|
827
|
+
console.log(chalk4.cyan("Steps:"));
|
|
828
|
+
console.log(chalk4.gray(" 1. Go to: ") + chalk4.blue.underline("https://ship.leanmcp.com/api-keys"));
|
|
829
|
+
console.log(chalk4.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
|
|
830
|
+
console.log(chalk4.gray(" 3. Copy the API key and paste it below\n"));
|
|
659
831
|
const apiKey = await input({
|
|
660
832
|
message: "Enter your API key:",
|
|
661
833
|
validate: /* @__PURE__ */ __name((value) => {
|
|
@@ -668,7 +840,7 @@ async function loginCommand() {
|
|
|
668
840
|
return true;
|
|
669
841
|
}, "validate")
|
|
670
842
|
});
|
|
671
|
-
const spinner =
|
|
843
|
+
const spinner = ora4("Validating API key...").start();
|
|
672
844
|
try {
|
|
673
845
|
const apiUrl = await getApiUrl();
|
|
674
846
|
const validateUrl = `${apiUrl}/api-keys/validate`;
|
|
@@ -688,10 +860,10 @@ async function loginCommand() {
|
|
|
688
860
|
const errorText = await response.text();
|
|
689
861
|
debug("Error response:", errorText);
|
|
690
862
|
spinner.fail("Invalid API key");
|
|
691
|
-
console.error(
|
|
692
|
-
console.log(
|
|
863
|
+
console.error(chalk4.red("\nThe API key is invalid or has expired."));
|
|
864
|
+
console.log(chalk4.gray("Please check your API key and try again."));
|
|
693
865
|
if (DEBUG_MODE) {
|
|
694
|
-
console.log(
|
|
866
|
+
console.log(chalk4.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
|
|
695
867
|
}
|
|
696
868
|
process.exit(1);
|
|
697
869
|
}
|
|
@@ -701,24 +873,24 @@ async function loginCommand() {
|
|
|
701
873
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
702
874
|
});
|
|
703
875
|
spinner.succeed("API key validated and saved");
|
|
704
|
-
console.log(
|
|
705
|
-
console.log(
|
|
876
|
+
console.log(chalk4.green("\nLogin successful!"));
|
|
877
|
+
console.log(chalk4.gray(` Config saved to: ${CONFIG_FILE}
|
|
706
878
|
`));
|
|
707
|
-
console.log(
|
|
708
|
-
console.log(
|
|
709
|
-
console.log(
|
|
879
|
+
console.log(chalk4.cyan("You can now use:"));
|
|
880
|
+
console.log(chalk4.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
|
|
881
|
+
console.log(chalk4.gray(" leanmcp logout - Remove your API key"));
|
|
710
882
|
} catch (error) {
|
|
711
883
|
spinner.fail("Failed to validate API key");
|
|
712
884
|
debug("Error:", error);
|
|
713
885
|
if (error instanceof Error && error.message.includes("fetch")) {
|
|
714
|
-
console.error(
|
|
715
|
-
console.log(
|
|
886
|
+
console.error(chalk4.red("\nCould not connect to LeanMCP servers."));
|
|
887
|
+
console.log(chalk4.gray("Please check your internet connection and try again."));
|
|
716
888
|
} else {
|
|
717
|
-
console.error(
|
|
889
|
+
console.error(chalk4.red(`
|
|
718
890
|
Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
719
891
|
}
|
|
720
892
|
if (DEBUG_MODE) {
|
|
721
|
-
console.log(
|
|
893
|
+
console.log(chalk4.gray(`
|
|
722
894
|
Debug: Full error: ${error}`));
|
|
723
895
|
}
|
|
724
896
|
process.exit(1);
|
|
@@ -726,10 +898,10 @@ Debug: Full error: ${error}`));
|
|
|
726
898
|
}
|
|
727
899
|
__name(loginCommand, "loginCommand");
|
|
728
900
|
async function logoutCommand() {
|
|
729
|
-
console.log(
|
|
901
|
+
console.log(chalk4.cyan("\nLeanMCP Logout\n"));
|
|
730
902
|
const config = await loadConfig();
|
|
731
903
|
if (!config.apiKey) {
|
|
732
|
-
console.log(
|
|
904
|
+
console.log(chalk4.yellow("You are not currently logged in."));
|
|
733
905
|
return;
|
|
734
906
|
}
|
|
735
907
|
const shouldLogout = await confirm({
|
|
@@ -737,7 +909,7 @@ async function logoutCommand() {
|
|
|
737
909
|
default: false
|
|
738
910
|
});
|
|
739
911
|
if (!shouldLogout) {
|
|
740
|
-
console.log(
|
|
912
|
+
console.log(chalk4.gray("\nLogout cancelled."));
|
|
741
913
|
return;
|
|
742
914
|
}
|
|
743
915
|
await saveConfig({
|
|
@@ -745,33 +917,33 @@ async function logoutCommand() {
|
|
|
745
917
|
apiKey: void 0,
|
|
746
918
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
747
919
|
});
|
|
748
|
-
console.log(
|
|
749
|
-
console.log(
|
|
920
|
+
console.log(chalk4.green("\nLogged out successfully!"));
|
|
921
|
+
console.log(chalk4.gray(` API key removed from: ${CONFIG_FILE}`));
|
|
750
922
|
}
|
|
751
923
|
__name(logoutCommand, "logoutCommand");
|
|
752
924
|
async function whoamiCommand() {
|
|
753
925
|
const config = await loadConfig();
|
|
754
926
|
if (!config.apiKey) {
|
|
755
|
-
console.log(
|
|
756
|
-
console.log(
|
|
927
|
+
console.log(chalk4.yellow("\nYou are not logged in."));
|
|
928
|
+
console.log(chalk4.gray("Run `leanmcp login` to authenticate.\n"));
|
|
757
929
|
return;
|
|
758
930
|
}
|
|
759
|
-
console.log(
|
|
760
|
-
console.log(
|
|
761
|
-
console.log(
|
|
762
|
-
console.log(
|
|
931
|
+
console.log(chalk4.cyan("\nLeanMCP Authentication Status\n"));
|
|
932
|
+
console.log(chalk4.green("Logged in"));
|
|
933
|
+
console.log(chalk4.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
|
|
934
|
+
console.log(chalk4.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
|
|
763
935
|
if (config.lastUpdated) {
|
|
764
|
-
console.log(
|
|
936
|
+
console.log(chalk4.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
|
|
765
937
|
}
|
|
766
938
|
console.log();
|
|
767
939
|
}
|
|
768
940
|
__name(whoamiCommand, "whoamiCommand");
|
|
769
941
|
|
|
770
942
|
// src/commands/deploy.ts
|
|
771
|
-
import
|
|
772
|
-
import
|
|
773
|
-
import
|
|
774
|
-
import
|
|
943
|
+
import chalk5 from "chalk";
|
|
944
|
+
import ora5 from "ora";
|
|
945
|
+
import path7 from "path";
|
|
946
|
+
import fs7 from "fs-extra";
|
|
775
947
|
import os2 from "os";
|
|
776
948
|
import archiver from "archiver";
|
|
777
949
|
import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
|
|
@@ -961,7 +1133,7 @@ function setDeployDebugMode(enabled) {
|
|
|
961
1133
|
__name(setDeployDebugMode, "setDeployDebugMode");
|
|
962
1134
|
function debug2(message, ...args) {
|
|
963
1135
|
if (DEBUG_MODE2) {
|
|
964
|
-
console.log(
|
|
1136
|
+
console.log(chalk5.gray(`[DEBUG] ${message}`), ...args);
|
|
965
1137
|
}
|
|
966
1138
|
}
|
|
967
1139
|
__name(debug2, "debug");
|
|
@@ -998,7 +1170,7 @@ var API_ENDPOINTS = {
|
|
|
998
1170
|
};
|
|
999
1171
|
async function createZipArchive(folderPath, outputPath) {
|
|
1000
1172
|
return new Promise((resolve, reject) => {
|
|
1001
|
-
const output =
|
|
1173
|
+
const output = fs7.createWriteStream(outputPath);
|
|
1002
1174
|
const archive = archiver("zip", {
|
|
1003
1175
|
zlib: {
|
|
1004
1176
|
level: 9
|
|
@@ -1085,26 +1257,26 @@ async function waitForDeployment(apiUrl, apiKey, deploymentId, spinner) {
|
|
|
1085
1257
|
__name(waitForDeployment, "waitForDeployment");
|
|
1086
1258
|
async function deployCommand(folderPath, options = {}) {
|
|
1087
1259
|
const deployStartTime = Date.now();
|
|
1088
|
-
console.log(
|
|
1260
|
+
console.log(chalk5.cyan("\nLeanMCP Deploy\n"));
|
|
1089
1261
|
debug2("Starting deployment...");
|
|
1090
1262
|
const apiKey = await getApiKey();
|
|
1091
1263
|
if (!apiKey) {
|
|
1092
|
-
console.error(
|
|
1093
|
-
console.log(
|
|
1264
|
+
console.error(chalk5.red("Not logged in."));
|
|
1265
|
+
console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1094
1266
|
process.exit(1);
|
|
1095
1267
|
}
|
|
1096
1268
|
const apiUrl = await getApiUrl();
|
|
1097
1269
|
debug2("API URL:", apiUrl);
|
|
1098
|
-
const absolutePath =
|
|
1099
|
-
if (!await
|
|
1100
|
-
console.error(
|
|
1270
|
+
const absolutePath = path7.resolve(process.cwd(), folderPath);
|
|
1271
|
+
if (!await fs7.pathExists(absolutePath)) {
|
|
1272
|
+
console.error(chalk5.red(`Folder not found: ${absolutePath}`));
|
|
1101
1273
|
process.exit(1);
|
|
1102
1274
|
}
|
|
1103
|
-
const hasMainTs = await
|
|
1104
|
-
const hasPackageJson = await
|
|
1275
|
+
const hasMainTs = await fs7.pathExists(path7.join(absolutePath, "main.ts"));
|
|
1276
|
+
const hasPackageJson = await fs7.pathExists(path7.join(absolutePath, "package.json"));
|
|
1105
1277
|
if (!hasMainTs && !hasPackageJson) {
|
|
1106
|
-
console.error(
|
|
1107
|
-
console.log(
|
|
1278
|
+
console.error(chalk5.red("Not a valid project folder."));
|
|
1279
|
+
console.log(chalk5.gray("Expected main.ts or package.json in the folder.\n"));
|
|
1108
1280
|
process.exit(1);
|
|
1109
1281
|
}
|
|
1110
1282
|
debug2("Fetching existing projects...");
|
|
@@ -1125,17 +1297,17 @@ async function deployCommand(folderPath, options = {}) {
|
|
|
1125
1297
|
let projectName;
|
|
1126
1298
|
let existingProject = null;
|
|
1127
1299
|
let isUpdate = false;
|
|
1128
|
-
let folderName =
|
|
1300
|
+
let folderName = path7.basename(absolutePath);
|
|
1129
1301
|
if (hasPackageJson) {
|
|
1130
1302
|
try {
|
|
1131
|
-
const pkg2 = await
|
|
1303
|
+
const pkg2 = await fs7.readJSON(path7.join(absolutePath, "package.json"));
|
|
1132
1304
|
folderName = pkg2.name || folderName;
|
|
1133
1305
|
} catch (e) {
|
|
1134
1306
|
}
|
|
1135
1307
|
}
|
|
1136
1308
|
const matchingProject = existingProjects.find((p) => p.name === folderName);
|
|
1137
1309
|
if (matchingProject) {
|
|
1138
|
-
console.log(
|
|
1310
|
+
console.log(chalk5.yellow(`Project '${folderName}' already exists.
|
|
1139
1311
|
`));
|
|
1140
1312
|
const choice = await select({
|
|
1141
1313
|
message: "What would you like to do?",
|
|
@@ -1155,26 +1327,26 @@ async function deployCommand(folderPath, options = {}) {
|
|
|
1155
1327
|
]
|
|
1156
1328
|
});
|
|
1157
1329
|
if (choice === "cancel") {
|
|
1158
|
-
console.log(
|
|
1330
|
+
console.log(chalk5.gray("\nDeployment cancelled.\n"));
|
|
1159
1331
|
return;
|
|
1160
1332
|
}
|
|
1161
1333
|
if (choice === "update") {
|
|
1162
1334
|
existingProject = matchingProject;
|
|
1163
1335
|
projectName = matchingProject.name;
|
|
1164
1336
|
isUpdate = true;
|
|
1165
|
-
console.log(
|
|
1166
|
-
console.log(
|
|
1337
|
+
console.log(chalk5.yellow("\nWARNING: This will replace the existing deployment."));
|
|
1338
|
+
console.log(chalk5.gray("The previous version will be overwritten.\n"));
|
|
1167
1339
|
} else {
|
|
1168
1340
|
projectName = generateProjectName();
|
|
1169
|
-
console.log(
|
|
1170
|
-
Generated project name: ${
|
|
1341
|
+
console.log(chalk5.cyan(`
|
|
1342
|
+
Generated project name: ${chalk5.bold(projectName)}
|
|
1171
1343
|
`));
|
|
1172
1344
|
}
|
|
1173
1345
|
} else {
|
|
1174
1346
|
projectName = generateProjectName();
|
|
1175
|
-
console.log(
|
|
1347
|
+
console.log(chalk5.cyan(`Generated project name: ${chalk5.bold(projectName)}`));
|
|
1176
1348
|
}
|
|
1177
|
-
console.log(
|
|
1349
|
+
console.log(chalk5.gray(`Path: ${absolutePath}
|
|
1178
1350
|
`));
|
|
1179
1351
|
let subdomain = options.subdomain;
|
|
1180
1352
|
if (!subdomain) {
|
|
@@ -1196,7 +1368,7 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1196
1368
|
}, "validate")
|
|
1197
1369
|
});
|
|
1198
1370
|
}
|
|
1199
|
-
const checkSpinner =
|
|
1371
|
+
const checkSpinner = ora5("Checking subdomain availability...").start();
|
|
1200
1372
|
try {
|
|
1201
1373
|
debug2("Checking subdomain:", subdomain);
|
|
1202
1374
|
const checkResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.checkSubdomain}/${subdomain}`, {
|
|
@@ -1208,7 +1380,7 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1208
1380
|
const result = await checkResponse.json();
|
|
1209
1381
|
if (!result.available) {
|
|
1210
1382
|
checkSpinner.fail(`Subdomain '${subdomain}' is already taken`);
|
|
1211
|
-
console.log(
|
|
1383
|
+
console.log(chalk5.gray("\nPlease choose a different subdomain.\n"));
|
|
1212
1384
|
process.exit(1);
|
|
1213
1385
|
}
|
|
1214
1386
|
}
|
|
@@ -1217,17 +1389,17 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1217
1389
|
checkSpinner.warn("Could not verify subdomain availability");
|
|
1218
1390
|
}
|
|
1219
1391
|
if (!options.skipConfirm) {
|
|
1220
|
-
console.log(
|
|
1221
|
-
console.log(
|
|
1222
|
-
console.log(
|
|
1223
|
-
console.log(
|
|
1392
|
+
console.log(chalk5.cyan("\nDeployment Details:"));
|
|
1393
|
+
console.log(chalk5.gray(` Project: ${projectName}`));
|
|
1394
|
+
console.log(chalk5.gray(` Subdomain: ${subdomain}`));
|
|
1395
|
+
console.log(chalk5.gray(` URL: https://${subdomain}.leanmcp.dev
|
|
1224
1396
|
`));
|
|
1225
1397
|
const shouldDeploy = await confirm2({
|
|
1226
1398
|
message: "Proceed with deployment?",
|
|
1227
1399
|
default: true
|
|
1228
1400
|
});
|
|
1229
1401
|
if (!shouldDeploy) {
|
|
1230
|
-
console.log(
|
|
1402
|
+
console.log(chalk5.gray("\nDeployment cancelled.\n"));
|
|
1231
1403
|
return;
|
|
1232
1404
|
}
|
|
1233
1405
|
}
|
|
@@ -1235,9 +1407,9 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1235
1407
|
let projectId;
|
|
1236
1408
|
if (isUpdate && existingProject) {
|
|
1237
1409
|
projectId = existingProject.id;
|
|
1238
|
-
console.log(
|
|
1410
|
+
console.log(chalk5.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
|
|
1239
1411
|
} else {
|
|
1240
|
-
const projectSpinner =
|
|
1412
|
+
const projectSpinner = ora5("Creating project...").start();
|
|
1241
1413
|
try {
|
|
1242
1414
|
debug2("Step 1: Creating project:", projectName);
|
|
1243
1415
|
const createResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
|
|
@@ -1259,14 +1431,14 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1259
1431
|
projectSpinner.succeed(`Project created: ${projectId.substring(0, 8)}...`);
|
|
1260
1432
|
} catch (error) {
|
|
1261
1433
|
projectSpinner.fail("Failed to create project");
|
|
1262
|
-
console.error(
|
|
1434
|
+
console.error(chalk5.red(`
|
|
1263
1435
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1264
1436
|
process.exit(1);
|
|
1265
1437
|
}
|
|
1266
1438
|
}
|
|
1267
|
-
const uploadSpinner =
|
|
1439
|
+
const uploadSpinner = ora5("Packaging and uploading...").start();
|
|
1268
1440
|
try {
|
|
1269
|
-
const tempZip =
|
|
1441
|
+
const tempZip = path7.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
|
|
1270
1442
|
const zipSize = await createZipArchive(absolutePath, tempZip);
|
|
1271
1443
|
uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
|
|
1272
1444
|
debug2("Step 2a: Getting upload URL for project:", projectId);
|
|
@@ -1293,7 +1465,7 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1293
1465
|
throw new Error("Backend did not return upload URL");
|
|
1294
1466
|
}
|
|
1295
1467
|
debug2("Step 2b: Uploading to S3...");
|
|
1296
|
-
const zipBuffer = await
|
|
1468
|
+
const zipBuffer = await fs7.readFile(tempZip);
|
|
1297
1469
|
const s3Response = await fetch(uploadUrl, {
|
|
1298
1470
|
method: "PUT",
|
|
1299
1471
|
body: zipBuffer,
|
|
@@ -1316,15 +1488,15 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1316
1488
|
s3Location
|
|
1317
1489
|
})
|
|
1318
1490
|
});
|
|
1319
|
-
await
|
|
1491
|
+
await fs7.remove(tempZip);
|
|
1320
1492
|
uploadSpinner.succeed("Project uploaded");
|
|
1321
1493
|
} catch (error) {
|
|
1322
1494
|
uploadSpinner.fail("Failed to upload");
|
|
1323
|
-
console.error(
|
|
1495
|
+
console.error(chalk5.red(`
|
|
1324
1496
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1325
1497
|
process.exit(1);
|
|
1326
1498
|
}
|
|
1327
|
-
const buildSpinner =
|
|
1499
|
+
const buildSpinner = ora5("Building...").start();
|
|
1328
1500
|
const buildStartTime = Date.now();
|
|
1329
1501
|
let buildId;
|
|
1330
1502
|
let imageUri;
|
|
@@ -1348,11 +1520,11 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1348
1520
|
buildSpinner.succeed(`Build complete (${buildDuration}s)`);
|
|
1349
1521
|
} catch (error) {
|
|
1350
1522
|
buildSpinner.fail("Build failed");
|
|
1351
|
-
console.error(
|
|
1523
|
+
console.error(chalk5.red(`
|
|
1352
1524
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1353
1525
|
process.exit(1);
|
|
1354
1526
|
}
|
|
1355
|
-
const deploySpinner =
|
|
1527
|
+
const deploySpinner = ora5("Deploying to LeanMCP...").start();
|
|
1356
1528
|
let deploymentId;
|
|
1357
1529
|
let functionUrl;
|
|
1358
1530
|
try {
|
|
@@ -1378,11 +1550,11 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1378
1550
|
deploySpinner.succeed("Deployed");
|
|
1379
1551
|
} catch (error) {
|
|
1380
1552
|
deploySpinner.fail("Deployment failed");
|
|
1381
|
-
console.error(
|
|
1553
|
+
console.error(chalk5.red(`
|
|
1382
1554
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1383
1555
|
process.exit(1);
|
|
1384
1556
|
}
|
|
1385
|
-
const mappingSpinner =
|
|
1557
|
+
const mappingSpinner = ora5("Configuring subdomain...").start();
|
|
1386
1558
|
try {
|
|
1387
1559
|
debug2("Step 5: Creating subdomain mapping:", subdomain);
|
|
1388
1560
|
const mappingResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createMapping}`, {
|
|
@@ -1406,38 +1578,43 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1406
1578
|
} catch (error) {
|
|
1407
1579
|
mappingSpinner.warn("Subdomain mapping may need manual setup");
|
|
1408
1580
|
}
|
|
1409
|
-
console.log(
|
|
1410
|
-
console.log(
|
|
1411
|
-
console.log(
|
|
1412
|
-
console.log(
|
|
1413
|
-
console.log(
|
|
1581
|
+
console.log(chalk5.green("\n" + "=".repeat(60)));
|
|
1582
|
+
console.log(chalk5.green.bold(" DEPLOYMENT SUCCESSFUL!"));
|
|
1583
|
+
console.log(chalk5.green("=".repeat(60) + "\n"));
|
|
1584
|
+
console.log(chalk5.white(" Your MCP server is now live:\n"));
|
|
1585
|
+
console.log(chalk5.cyan(` URL: `) + chalk5.white.bold(`https://${subdomain}.leanmcp.dev`));
|
|
1414
1586
|
console.log();
|
|
1415
|
-
console.log(
|
|
1416
|
-
console.log(
|
|
1417
|
-
console.log(
|
|
1587
|
+
console.log(chalk5.gray(" Test endpoints:"));
|
|
1588
|
+
console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/health`));
|
|
1589
|
+
console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
|
|
1418
1590
|
console.log();
|
|
1419
1591
|
const totalDuration = Math.round((Date.now() - deployStartTime) / 1e3);
|
|
1420
|
-
console.log(
|
|
1592
|
+
console.log(chalk5.gray(` Total time: ${totalDuration}s`));
|
|
1421
1593
|
console.log();
|
|
1422
|
-
|
|
1423
|
-
console.log(
|
|
1424
|
-
console.log(
|
|
1594
|
+
const dashboardBaseUrl = "https://ship.leanmcp.com";
|
|
1595
|
+
console.log(chalk5.cyan(" Dashboard links:"));
|
|
1596
|
+
console.log(chalk5.gray(` Project: ${dashboardBaseUrl}/projects/${projectId}`));
|
|
1597
|
+
console.log(chalk5.gray(` Build: ${dashboardBaseUrl}/builds/${buildId}`));
|
|
1598
|
+
console.log(chalk5.gray(` Deployment: ${dashboardBaseUrl}/deployments/${deploymentId}`));
|
|
1599
|
+
console.log();
|
|
1600
|
+
console.log(chalk5.cyan(" Need help? Join our Discord:"));
|
|
1601
|
+
console.log(chalk5.blue(" https://discord.com/invite/DsRcA3GwPy"));
|
|
1425
1602
|
console.log();
|
|
1426
1603
|
}
|
|
1427
1604
|
__name(deployCommand, "deployCommand");
|
|
1428
1605
|
|
|
1429
1606
|
// src/commands/projects.ts
|
|
1430
|
-
import
|
|
1431
|
-
import
|
|
1607
|
+
import chalk6 from "chalk";
|
|
1608
|
+
import ora6 from "ora";
|
|
1432
1609
|
var API_ENDPOINT = "/api/projects";
|
|
1433
1610
|
async function projectsListCommand() {
|
|
1434
1611
|
const apiKey = await getApiKey();
|
|
1435
1612
|
if (!apiKey) {
|
|
1436
|
-
console.error(
|
|
1437
|
-
console.log(
|
|
1613
|
+
console.error(chalk6.red("\nNot logged in."));
|
|
1614
|
+
console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1438
1615
|
process.exit(1);
|
|
1439
1616
|
}
|
|
1440
|
-
const spinner =
|
|
1617
|
+
const spinner = ora6("Fetching projects...").start();
|
|
1441
1618
|
try {
|
|
1442
1619
|
const apiUrl = await getApiUrl();
|
|
1443
1620
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
|
|
@@ -1451,25 +1628,25 @@ async function projectsListCommand() {
|
|
|
1451
1628
|
const projects = await response.json();
|
|
1452
1629
|
spinner.stop();
|
|
1453
1630
|
if (projects.length === 0) {
|
|
1454
|
-
console.log(
|
|
1455
|
-
console.log(
|
|
1631
|
+
console.log(chalk6.yellow("\nNo projects found."));
|
|
1632
|
+
console.log(chalk6.gray("Create one with: leanmcp deploy <folder>\n"));
|
|
1456
1633
|
return;
|
|
1457
1634
|
}
|
|
1458
|
-
console.log(
|
|
1635
|
+
console.log(chalk6.cyan(`
|
|
1459
1636
|
Your Projects (${projects.length})
|
|
1460
1637
|
`));
|
|
1461
|
-
console.log(
|
|
1638
|
+
console.log(chalk6.gray("\u2500".repeat(60)));
|
|
1462
1639
|
for (const project of projects) {
|
|
1463
|
-
const statusColor = project.status === "ACTIVE" ?
|
|
1464
|
-
console.log(
|
|
1465
|
-
console.log(
|
|
1466
|
-
console.log(
|
|
1467
|
-
console.log(
|
|
1640
|
+
const statusColor = project.status === "ACTIVE" ? chalk6.green : chalk6.yellow;
|
|
1641
|
+
console.log(chalk6.white.bold(` ${project.name}`));
|
|
1642
|
+
console.log(chalk6.gray(` ID: ${project.id}`));
|
|
1643
|
+
console.log(chalk6.gray(` Status: `) + statusColor(project.status));
|
|
1644
|
+
console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
|
|
1468
1645
|
console.log();
|
|
1469
1646
|
}
|
|
1470
1647
|
} catch (error) {
|
|
1471
1648
|
spinner.fail("Failed to fetch projects");
|
|
1472
|
-
console.error(
|
|
1649
|
+
console.error(chalk6.red(`
|
|
1473
1650
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1474
1651
|
process.exit(1);
|
|
1475
1652
|
}
|
|
@@ -1478,11 +1655,11 @@ __name(projectsListCommand, "projectsListCommand");
|
|
|
1478
1655
|
async function projectsGetCommand(projectId) {
|
|
1479
1656
|
const apiKey = await getApiKey();
|
|
1480
1657
|
if (!apiKey) {
|
|
1481
|
-
console.error(
|
|
1482
|
-
console.log(
|
|
1658
|
+
console.error(chalk6.red("\nNot logged in."));
|
|
1659
|
+
console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1483
1660
|
process.exit(1);
|
|
1484
1661
|
}
|
|
1485
|
-
const spinner =
|
|
1662
|
+
const spinner = ora6("Fetching project...").start();
|
|
1486
1663
|
try {
|
|
1487
1664
|
const apiUrl = await getApiUrl();
|
|
1488
1665
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
@@ -1498,22 +1675,22 @@ async function projectsGetCommand(projectId) {
|
|
|
1498
1675
|
}
|
|
1499
1676
|
const project = await response.json();
|
|
1500
1677
|
spinner.stop();
|
|
1501
|
-
console.log(
|
|
1502
|
-
console.log(
|
|
1503
|
-
console.log(
|
|
1504
|
-
console.log(
|
|
1505
|
-
console.log(
|
|
1678
|
+
console.log(chalk6.cyan("\nProject Details\n"));
|
|
1679
|
+
console.log(chalk6.gray("\u2500".repeat(60)));
|
|
1680
|
+
console.log(chalk6.white.bold(` Name: ${project.name}`));
|
|
1681
|
+
console.log(chalk6.gray(` ID: ${project.id}`));
|
|
1682
|
+
console.log(chalk6.gray(` Status: ${project.status}`));
|
|
1506
1683
|
if (project.s3Location) {
|
|
1507
|
-
console.log(
|
|
1684
|
+
console.log(chalk6.gray(` S3 Location: ${project.s3Location}`));
|
|
1508
1685
|
}
|
|
1509
|
-
console.log(
|
|
1686
|
+
console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
|
|
1510
1687
|
if (project.updatedAt) {
|
|
1511
|
-
console.log(
|
|
1688
|
+
console.log(chalk6.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
|
|
1512
1689
|
}
|
|
1513
1690
|
console.log();
|
|
1514
1691
|
} catch (error) {
|
|
1515
1692
|
spinner.fail("Failed to fetch project");
|
|
1516
|
-
console.error(
|
|
1693
|
+
console.error(chalk6.red(`
|
|
1517
1694
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1518
1695
|
process.exit(1);
|
|
1519
1696
|
}
|
|
@@ -1522,8 +1699,8 @@ __name(projectsGetCommand, "projectsGetCommand");
|
|
|
1522
1699
|
async function projectsDeleteCommand(projectId, options = {}) {
|
|
1523
1700
|
const apiKey = await getApiKey();
|
|
1524
1701
|
if (!apiKey) {
|
|
1525
|
-
console.error(
|
|
1526
|
-
console.log(
|
|
1702
|
+
console.error(chalk6.red("\nNot logged in."));
|
|
1703
|
+
console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1527
1704
|
process.exit(1);
|
|
1528
1705
|
}
|
|
1529
1706
|
if (!options.force) {
|
|
@@ -1533,11 +1710,11 @@ async function projectsDeleteCommand(projectId, options = {}) {
|
|
|
1533
1710
|
default: false
|
|
1534
1711
|
});
|
|
1535
1712
|
if (!shouldDelete) {
|
|
1536
|
-
console.log(
|
|
1713
|
+
console.log(chalk6.gray("\nDeletion cancelled.\n"));
|
|
1537
1714
|
return;
|
|
1538
1715
|
}
|
|
1539
1716
|
}
|
|
1540
|
-
const spinner =
|
|
1717
|
+
const spinner = ora6("Deleting project...").start();
|
|
1541
1718
|
try {
|
|
1542
1719
|
const apiUrl = await getApiUrl();
|
|
1543
1720
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
@@ -1556,238 +1733,102 @@ async function projectsDeleteCommand(projectId, options = {}) {
|
|
|
1556
1733
|
console.log();
|
|
1557
1734
|
} catch (error) {
|
|
1558
1735
|
spinner.fail("Failed to delete project");
|
|
1559
|
-
console.error(
|
|
1736
|
+
console.error(chalk6.red(`
|
|
1560
1737
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1561
1738
|
process.exit(1);
|
|
1562
1739
|
}
|
|
1563
1740
|
}
|
|
1564
1741
|
__name(projectsDeleteCommand, "projectsDeleteCommand");
|
|
1565
1742
|
|
|
1566
|
-
// src/
|
|
1567
|
-
var
|
|
1568
|
-
var pkg = require2("../package.json");
|
|
1569
|
-
function capitalize(str) {
|
|
1570
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1571
|
-
}
|
|
1572
|
-
__name(capitalize, "capitalize");
|
|
1573
|
-
var program = new Command();
|
|
1574
|
-
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
|
|
1575
|
-
Examples:
|
|
1576
|
-
$ leanmcp create my-app # Create new project (interactive)
|
|
1577
|
-
$ leanmcp create my-app --install # Create and install deps (non-interactive)
|
|
1578
|
-
$ leanmcp create my-app --no-install # Create without installing deps
|
|
1579
|
-
$ leanmcp dev # Start development server
|
|
1580
|
-
$ leanmcp login # Authenticate with LeanMCP cloud
|
|
1581
|
-
$ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
|
|
1582
|
-
$ leanmcp projects list # List your cloud projects
|
|
1583
|
-
$ leanmcp projects delete <id> # Delete a cloud project
|
|
1584
|
-
`);
|
|
1585
|
-
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("--install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
|
|
1586
|
-
const spinner = ora6(`Creating project ${projectName}...`).start();
|
|
1587
|
-
const targetDir = path7.join(process.cwd(), projectName);
|
|
1588
|
-
if (fs7.existsSync(targetDir)) {
|
|
1589
|
-
spinner.fail(`Folder ${projectName} already exists.`);
|
|
1590
|
-
process.exit(1);
|
|
1591
|
-
}
|
|
1592
|
-
await fs7.mkdirp(targetDir);
|
|
1593
|
-
await fs7.mkdirp(path7.join(targetDir, "mcp", "example"));
|
|
1594
|
-
const pkg2 = {
|
|
1595
|
-
name: projectName,
|
|
1596
|
-
version: "1.0.0",
|
|
1597
|
-
description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
|
|
1598
|
-
main: "dist/main.js",
|
|
1599
|
-
type: "module",
|
|
1600
|
-
scripts: {
|
|
1601
|
-
dev: "tsx watch main.ts",
|
|
1602
|
-
build: "tsc",
|
|
1603
|
-
start: "node dist/main.js",
|
|
1604
|
-
clean: "rm -rf dist"
|
|
1605
|
-
},
|
|
1606
|
-
keywords: [
|
|
1607
|
-
"mcp",
|
|
1608
|
-
"model-context-protocol",
|
|
1609
|
-
"streamable-http",
|
|
1610
|
-
"leanmcp"
|
|
1611
|
-
],
|
|
1612
|
-
author: "",
|
|
1613
|
-
license: "MIT",
|
|
1614
|
-
dependencies: {
|
|
1615
|
-
"@leanmcp/core": "^0.3.5",
|
|
1616
|
-
"dotenv": "^16.5.0"
|
|
1617
|
-
},
|
|
1618
|
-
devDependencies: {
|
|
1619
|
-
"@types/node": "^20.0.0",
|
|
1620
|
-
"tsx": "^4.20.3",
|
|
1621
|
-
"typescript": "^5.6.3"
|
|
1622
|
-
}
|
|
1623
|
-
};
|
|
1624
|
-
await fs7.writeJSON(path7.join(targetDir, "package.json"), pkg2, {
|
|
1625
|
-
spaces: 2
|
|
1626
|
-
});
|
|
1627
|
-
const tsconfig = {
|
|
1628
|
-
compilerOptions: {
|
|
1629
|
-
module: "ESNext",
|
|
1630
|
-
target: "ES2022",
|
|
1631
|
-
moduleResolution: "Node",
|
|
1632
|
-
esModuleInterop: true,
|
|
1633
|
-
strict: true,
|
|
1634
|
-
skipLibCheck: true,
|
|
1635
|
-
outDir: "dist",
|
|
1636
|
-
experimentalDecorators: true,
|
|
1637
|
-
emitDecoratorMetadata: true
|
|
1638
|
-
},
|
|
1639
|
-
include: [
|
|
1640
|
-
"**/*.ts"
|
|
1641
|
-
],
|
|
1642
|
-
exclude: [
|
|
1643
|
-
"node_modules",
|
|
1644
|
-
"dist"
|
|
1645
|
-
]
|
|
1646
|
-
};
|
|
1647
|
-
await fs7.writeJSON(path7.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
1648
|
-
spaces: 2
|
|
1649
|
-
});
|
|
1650
|
-
const dashboardLine = options.dashboard === false ? `
|
|
1651
|
-
dashboard: false, // Dashboard disabled via --no-dashboard` : "";
|
|
1652
|
-
const mainTs = `import dotenv from "dotenv";
|
|
1653
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
1743
|
+
// src/templates/readme_v1.ts
|
|
1744
|
+
var getReadmeTemplate = /* @__PURE__ */ __name((projectName) => `# ${projectName}
|
|
1654
1745
|
|
|
1655
|
-
|
|
1656
|
-
dotenv.config();
|
|
1746
|
+
MCP Server with Streamable HTTP Transport built with LeanMCP SDK
|
|
1657
1747
|
|
|
1658
|
-
|
|
1659
|
-
await createHTTPServer({
|
|
1660
|
-
name: "${projectName}",
|
|
1661
|
-
version: "1.0.0",
|
|
1662
|
-
port: 3001,
|
|
1663
|
-
cors: true,
|
|
1664
|
-
logging: true${dashboardLine}
|
|
1665
|
-
});
|
|
1748
|
+
## Quick Start
|
|
1666
1749
|
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
1750
|
+
\`\`\`bash
|
|
1751
|
+
# Install dependencies
|
|
1752
|
+
npm install
|
|
1671
1753
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
*
|
|
1675
|
-
* This is a simple example to get you started. Add your own tools, resources, and prompts here!
|
|
1676
|
-
*/
|
|
1754
|
+
# Start development server (hot reload)
|
|
1755
|
+
npm run dev
|
|
1677
1756
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
@SchemaConstraint({ description: "First number" })
|
|
1681
|
-
a!: number;
|
|
1757
|
+
# Build for production
|
|
1758
|
+
npm run build
|
|
1682
1759
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1760
|
+
# Run production server
|
|
1761
|
+
npm start
|
|
1762
|
+
\`\`\`
|
|
1685
1763
|
|
|
1686
|
-
|
|
1687
|
-
@SchemaConstraint({
|
|
1688
|
-
description: "Operation to perform",
|
|
1689
|
-
enum: ["add", "subtract", "multiply", "divide"],
|
|
1690
|
-
default: "add"
|
|
1691
|
-
})
|
|
1692
|
-
operation?: string;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
class EchoInput {
|
|
1696
|
-
@SchemaConstraint({
|
|
1697
|
-
description: "Message to echo back",
|
|
1698
|
-
minLength: 1
|
|
1699
|
-
})
|
|
1700
|
-
message!: string;
|
|
1701
|
-
}
|
|
1764
|
+
## Project Structure
|
|
1702
1765
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
let result: number;
|
|
1766
|
+
\`\`\`
|
|
1767
|
+
${projectName}/
|
|
1768
|
+
\u251C\u2500\u2500 main.ts # Server entry point
|
|
1769
|
+
\u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
|
|
1770
|
+
\u2502 \u2514\u2500\u2500 example/
|
|
1771
|
+
\u2502 \u2514\u2500\u2500 index.ts # Example service
|
|
1772
|
+
\u251C\u2500\u2500 .env # Environment variables
|
|
1773
|
+
\u2514\u2500\u2500 package.json
|
|
1774
|
+
\`\`\`
|
|
1713
1775
|
|
|
1714
|
-
|
|
1715
|
-
case "add":
|
|
1716
|
-
result = a + b;
|
|
1717
|
-
break;
|
|
1718
|
-
case "subtract":
|
|
1719
|
-
result = a - b;
|
|
1720
|
-
break;
|
|
1721
|
-
case "multiply":
|
|
1722
|
-
result = a * b;
|
|
1723
|
-
break;
|
|
1724
|
-
case "divide":
|
|
1725
|
-
if (b === 0) throw new Error("Cannot divide by zero");
|
|
1726
|
-
result = a / b;
|
|
1727
|
-
break;
|
|
1728
|
-
default:
|
|
1729
|
-
throw new Error("Invalid operation");
|
|
1730
|
-
}
|
|
1776
|
+
## Adding New Services
|
|
1731
1777
|
|
|
1732
|
-
|
|
1733
|
-
content: [{
|
|
1734
|
-
type: "text" as const,
|
|
1735
|
-
text: JSON.stringify({
|
|
1736
|
-
operation: input.operation || "add",
|
|
1737
|
-
operands: { a: input.a, b: input.b },
|
|
1738
|
-
result
|
|
1739
|
-
}, null, 2)
|
|
1740
|
-
}]
|
|
1741
|
-
};
|
|
1742
|
-
}
|
|
1778
|
+
Create a new service directory in \`mcp/\`:
|
|
1743
1779
|
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
})
|
|
1748
|
-
async echo(input: EchoInput) {
|
|
1749
|
-
return {
|
|
1750
|
-
content: [{
|
|
1751
|
-
type: "text" as const,
|
|
1752
|
-
text: JSON.stringify({
|
|
1753
|
-
echoed: input.message,
|
|
1754
|
-
timestamp: new Date().toISOString()
|
|
1755
|
-
}, null, 2)
|
|
1756
|
-
}]
|
|
1757
|
-
};
|
|
1758
|
-
}
|
|
1780
|
+
\`\`\`typescript
|
|
1781
|
+
// mcp/myservice/index.ts
|
|
1782
|
+
import { Tool, SchemaConstraint } from "@leanmcp/core";
|
|
1759
1783
|
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
version: "1.0.0",
|
|
1769
|
-
uptime: process.uptime()
|
|
1770
|
-
}, null, 2)
|
|
1771
|
-
}]
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1784
|
+
// Define input schema
|
|
1785
|
+
class MyToolInput {
|
|
1786
|
+
@SchemaConstraint({
|
|
1787
|
+
description: "Message to process",
|
|
1788
|
+
minLength: 1
|
|
1789
|
+
})
|
|
1790
|
+
message!: string;
|
|
1791
|
+
}
|
|
1774
1792
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1793
|
+
export class MyService {
|
|
1794
|
+
@Tool({
|
|
1795
|
+
description: "My awesome tool",
|
|
1796
|
+
inputClass: MyToolInput
|
|
1797
|
+
})
|
|
1798
|
+
async myTool(input: MyToolInput) {
|
|
1799
|
+
return {
|
|
1800
|
+
content: [{
|
|
1801
|
+
type: "text",
|
|
1802
|
+
text: \`You said: \${input.message}\`
|
|
1784
1803
|
}]
|
|
1785
1804
|
};
|
|
1786
1805
|
}
|
|
1787
1806
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1807
|
+
\`\`\`
|
|
1808
|
+
|
|
1809
|
+
Services are automatically discovered and registered - no need to modify \`main.ts\`!
|
|
1810
|
+
|
|
1811
|
+
## Features
|
|
1812
|
+
|
|
1813
|
+
- **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
|
|
1814
|
+
- **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
|
|
1815
|
+
- **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
|
|
1816
|
+
- **HTTP transport** - Production-ready HTTP server with session management
|
|
1817
|
+
- **Hot reload** - Development mode with automatic restart on file changes
|
|
1818
|
+
|
|
1819
|
+
## Testing with MCP Inspector
|
|
1820
|
+
|
|
1821
|
+
\`\`\`bash
|
|
1822
|
+
npx @modelcontextprotocol/inspector http://localhost:3001/mcp
|
|
1823
|
+
\`\`\`
|
|
1824
|
+
|
|
1825
|
+
## License
|
|
1826
|
+
|
|
1827
|
+
MIT
|
|
1828
|
+
`, "getReadmeTemplate");
|
|
1829
|
+
|
|
1830
|
+
// src/templates/gitignore_v1.ts
|
|
1831
|
+
var gitignoreTemplate = `# Logs
|
|
1791
1832
|
logs
|
|
1792
1833
|
*.log
|
|
1793
1834
|
npm-debug.log*
|
|
@@ -1929,111 +1970,333 @@ vite.config.js.timestamp-*
|
|
|
1929
1970
|
vite.config.ts.timestamp-*
|
|
1930
1971
|
.vite/
|
|
1931
1972
|
`;
|
|
1932
|
-
const env = `# Server Configuration
|
|
1933
|
-
PORT=3001
|
|
1934
|
-
NODE_ENV=development
|
|
1935
1973
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
await fs7.writeFile(path7.join(targetDir, ".gitignore"), gitignore);
|
|
1939
|
-
await fs7.writeFile(path7.join(targetDir, ".env"), env);
|
|
1940
|
-
const readme = `# ${projectName}
|
|
1974
|
+
// src/templates/example_service_v1.ts
|
|
1975
|
+
var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
1941
1976
|
|
|
1942
|
-
|
|
1977
|
+
/**
|
|
1978
|
+
* Example service demonstrating LeanMCP SDK decorators
|
|
1979
|
+
*
|
|
1980
|
+
* This is a simple example to get you started. Add your own tools, resources, and prompts here!
|
|
1981
|
+
*/
|
|
1943
1982
|
|
|
1944
|
-
|
|
1983
|
+
// Input schema with validation decorators
|
|
1984
|
+
class CalculateInput {
|
|
1985
|
+
@SchemaConstraint({ description: "First number" })
|
|
1986
|
+
a!: number;
|
|
1945
1987
|
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
npm install
|
|
1988
|
+
@SchemaConstraint({ description: "Second number" })
|
|
1989
|
+
b!: number;
|
|
1949
1990
|
|
|
1950
|
-
|
|
1951
|
-
|
|
1991
|
+
@Optional()
|
|
1992
|
+
@SchemaConstraint({
|
|
1993
|
+
description: "Operation to perform",
|
|
1994
|
+
enum: ["add", "subtract", "multiply", "divide"],
|
|
1995
|
+
default: "add"
|
|
1996
|
+
})
|
|
1997
|
+
operation?: string;
|
|
1998
|
+
}
|
|
1952
1999
|
|
|
1953
|
-
|
|
1954
|
-
|
|
2000
|
+
class EchoInput {
|
|
2001
|
+
@SchemaConstraint({
|
|
2002
|
+
description: "Message to echo back",
|
|
2003
|
+
minLength: 1
|
|
2004
|
+
})
|
|
2005
|
+
message!: string;
|
|
2006
|
+
}
|
|
1955
2007
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2008
|
+
export class ExampleService {
|
|
2009
|
+
@Tool({
|
|
2010
|
+
description: "Perform arithmetic operations with automatic schema validation",
|
|
2011
|
+
inputClass: CalculateInput
|
|
2012
|
+
})
|
|
2013
|
+
async calculate(input: CalculateInput) {
|
|
2014
|
+
// Ensure numerical operations by explicitly converting to numbers
|
|
2015
|
+
const a = Number(input.a);
|
|
2016
|
+
const b = Number(input.b);
|
|
2017
|
+
let result: number;
|
|
1959
2018
|
|
|
1960
|
-
|
|
2019
|
+
switch (input.operation || "add") {
|
|
2020
|
+
case "add":
|
|
2021
|
+
result = a + b;
|
|
2022
|
+
break;
|
|
2023
|
+
case "subtract":
|
|
2024
|
+
result = a - b;
|
|
2025
|
+
break;
|
|
2026
|
+
case "multiply":
|
|
2027
|
+
result = a * b;
|
|
2028
|
+
break;
|
|
2029
|
+
case "divide":
|
|
2030
|
+
if (b === 0) throw new Error("Cannot divide by zero");
|
|
2031
|
+
result = a / b;
|
|
2032
|
+
break;
|
|
2033
|
+
default:
|
|
2034
|
+
throw new Error("Invalid operation");
|
|
2035
|
+
}
|
|
1961
2036
|
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2037
|
+
return {
|
|
2038
|
+
content: [{
|
|
2039
|
+
type: "text" as const,
|
|
2040
|
+
text: JSON.stringify({
|
|
2041
|
+
operation: input.operation || "add",
|
|
2042
|
+
operands: { a: input.a, b: input.b },
|
|
2043
|
+
result
|
|
2044
|
+
}, null, 2)
|
|
2045
|
+
}]
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
1971
2048
|
|
|
1972
|
-
|
|
2049
|
+
@Tool({
|
|
2050
|
+
description: "Echo a message back",
|
|
2051
|
+
inputClass: EchoInput
|
|
2052
|
+
})
|
|
2053
|
+
async echo(input: EchoInput) {
|
|
2054
|
+
return {
|
|
2055
|
+
content: [{
|
|
2056
|
+
type: "text" as const,
|
|
2057
|
+
text: JSON.stringify({
|
|
2058
|
+
echoed: input.message,
|
|
2059
|
+
timestamp: new Date().toISOString()
|
|
2060
|
+
}, null, 2)
|
|
2061
|
+
}]
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
1973
2064
|
|
|
1974
|
-
|
|
2065
|
+
@Resource({ description: "Get server information" })
|
|
2066
|
+
async serverInfo() {
|
|
2067
|
+
return {
|
|
2068
|
+
contents: [{
|
|
2069
|
+
uri: "server://info",
|
|
2070
|
+
mimeType: "application/json",
|
|
2071
|
+
text: JSON.stringify({
|
|
2072
|
+
name: "${projectName}",
|
|
2073
|
+
version: "1.0.0",
|
|
2074
|
+
uptime: process.uptime()
|
|
2075
|
+
}, null, 2)
|
|
2076
|
+
}]
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
1975
2079
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2080
|
+
@Prompt({ description: "Generate a greeting prompt" })
|
|
2081
|
+
async greeting(args: { name?: string }) {
|
|
2082
|
+
return {
|
|
2083
|
+
messages: [{
|
|
2084
|
+
role: "user" as const,
|
|
2085
|
+
content: {
|
|
2086
|
+
type: "text" as const,
|
|
2087
|
+
text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
|
|
2088
|
+
}
|
|
2089
|
+
}]
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
`, "getExampleServiceTemplate");
|
|
1979
2094
|
|
|
1980
|
-
//
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2095
|
+
// src/templates/main_ts_v1.ts
|
|
2096
|
+
var getMainTsTemplate = /* @__PURE__ */ __name((projectName, dashboardLine) => `import dotenv from "dotenv";
|
|
2097
|
+
import { createHTTPServer } from "@leanmcp/core";
|
|
2098
|
+
|
|
2099
|
+
// Load environment variables
|
|
2100
|
+
dotenv.config();
|
|
2101
|
+
|
|
2102
|
+
// Services are automatically discovered from ./mcp directory
|
|
2103
|
+
await createHTTPServer({
|
|
2104
|
+
name: "${projectName}",
|
|
2105
|
+
version: "1.0.0",
|
|
2106
|
+
port: 3001,
|
|
2107
|
+
cors: true,
|
|
2108
|
+
logging: true${dashboardLine}
|
|
2109
|
+
});
|
|
2110
|
+
|
|
2111
|
+
console.log("\\n${projectName} MCP Server");
|
|
2112
|
+
`, "getMainTsTemplate");
|
|
2113
|
+
|
|
2114
|
+
// src/templates/service_index_v1.ts
|
|
2115
|
+
var getServiceIndexTemplate = /* @__PURE__ */ __name((serviceName, capitalizedName) => `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
|
|
2116
|
+
|
|
2117
|
+
// Input schema for greeting
|
|
2118
|
+
class GreetInput {
|
|
2119
|
+
@SchemaConstraint({
|
|
2120
|
+
description: "Name to greet",
|
|
1984
2121
|
minLength: 1
|
|
1985
2122
|
})
|
|
1986
|
-
|
|
2123
|
+
name!: string;
|
|
1987
2124
|
}
|
|
1988
2125
|
|
|
1989
|
-
|
|
2126
|
+
/**
|
|
2127
|
+
* ${capitalizedName} Service
|
|
2128
|
+
*
|
|
2129
|
+
* This service demonstrates the three types of MCP primitives:
|
|
2130
|
+
* - Tools: Callable functions (like API endpoints)
|
|
2131
|
+
* - Prompts: Reusable prompt templates
|
|
2132
|
+
* - Resources: Data sources/endpoints
|
|
2133
|
+
*/
|
|
2134
|
+
export class ${capitalizedName}Service {
|
|
2135
|
+
// TOOL - Callable function
|
|
2136
|
+
// Tool name: "greet" (from function name)
|
|
1990
2137
|
@Tool({
|
|
1991
|
-
description: "
|
|
1992
|
-
inputClass:
|
|
2138
|
+
description: "Greet a user by name",
|
|
2139
|
+
inputClass: GreetInput
|
|
1993
2140
|
})
|
|
1994
|
-
|
|
2141
|
+
greet(args: GreetInput) {
|
|
2142
|
+
return { message: \`Hello, \${args.name}! from ${serviceName}\` };
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// PROMPT - Prompt template
|
|
2146
|
+
// Prompt name: "welcomePrompt" (from function name)
|
|
2147
|
+
@Prompt({ description: "Welcome message prompt template" })
|
|
2148
|
+
welcomePrompt(args: { userName?: string }) {
|
|
1995
2149
|
return {
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2150
|
+
messages: [
|
|
2151
|
+
{
|
|
2152
|
+
role: "user",
|
|
2153
|
+
content: {
|
|
2154
|
+
type: "text",
|
|
2155
|
+
text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
]
|
|
2000
2159
|
};
|
|
2001
2160
|
}
|
|
2002
|
-
}
|
|
2003
|
-
\`\`\`
|
|
2004
|
-
|
|
2005
|
-
Services are automatically discovered and registered - no need to modify \`main.ts\`!
|
|
2006
|
-
|
|
2007
|
-
## Features
|
|
2008
2161
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2162
|
+
// RESOURCE - Data endpoint
|
|
2163
|
+
// Resource URI auto-generated from class and method name
|
|
2164
|
+
@Resource({ description: "${capitalizedName} service status" })
|
|
2165
|
+
getStatus() {
|
|
2166
|
+
return {
|
|
2167
|
+
service: "${serviceName}",
|
|
2168
|
+
status: "active",
|
|
2169
|
+
timestamp: new Date().toISOString()
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
`, "getServiceIndexTemplate");
|
|
2020
2174
|
|
|
2021
|
-
|
|
2175
|
+
// src/index.ts
|
|
2176
|
+
var require2 = createRequire(import.meta.url);
|
|
2177
|
+
var pkg = require2("../package.json");
|
|
2178
|
+
function capitalize(str) {
|
|
2179
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2180
|
+
}
|
|
2181
|
+
__name(capitalize, "capitalize");
|
|
2182
|
+
var program = new Command();
|
|
2183
|
+
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
|
|
2184
|
+
Examples:
|
|
2185
|
+
$ leanmcp create my-app # Create new project (interactive)
|
|
2186
|
+
$ leanmcp create my-app --install # Create and install deps (non-interactive)
|
|
2187
|
+
$ leanmcp create my-app --no-install # Create without installing deps
|
|
2188
|
+
$ leanmcp dev # Start development server
|
|
2189
|
+
$ leanmcp build # Build UI components and compile TypeScript
|
|
2190
|
+
$ leanmcp start # Build and start production server
|
|
2191
|
+
$ leanmcp login # Authenticate with LeanMCP cloud
|
|
2192
|
+
$ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
|
|
2193
|
+
$ leanmcp projects list # List your cloud projects
|
|
2194
|
+
$ leanmcp projects delete <id> # Delete a cloud project
|
|
2195
|
+
`);
|
|
2196
|
+
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("--install", "Install dependencies automatically (non-interactive, no dev server)").option("--no-install", "Skip dependency installation (non-interactive)").action(async (projectName, options) => {
|
|
2197
|
+
const spinner = ora7(`Creating project ${projectName}...`).start();
|
|
2198
|
+
const targetDir = path8.join(process.cwd(), projectName);
|
|
2199
|
+
if (fs8.existsSync(targetDir)) {
|
|
2200
|
+
spinner.fail(`Folder ${projectName} already exists.`);
|
|
2201
|
+
process.exit(1);
|
|
2202
|
+
}
|
|
2203
|
+
await fs8.mkdirp(targetDir);
|
|
2204
|
+
await fs8.mkdirp(path8.join(targetDir, "mcp", "example"));
|
|
2205
|
+
const pkg2 = {
|
|
2206
|
+
name: projectName,
|
|
2207
|
+
version: "1.0.0",
|
|
2208
|
+
description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
|
|
2209
|
+
main: "dist/main.js",
|
|
2210
|
+
type: "module",
|
|
2211
|
+
scripts: {
|
|
2212
|
+
dev: "leanmcp dev",
|
|
2213
|
+
build: "leanmcp build",
|
|
2214
|
+
start: "leanmcp start",
|
|
2215
|
+
"start:node": "node dist/main.js",
|
|
2216
|
+
clean: "rm -rf dist"
|
|
2217
|
+
},
|
|
2218
|
+
keywords: [
|
|
2219
|
+
"mcp",
|
|
2220
|
+
"model-context-protocol",
|
|
2221
|
+
"streamable-http",
|
|
2222
|
+
"leanmcp"
|
|
2223
|
+
],
|
|
2224
|
+
author: "",
|
|
2225
|
+
license: "MIT",
|
|
2226
|
+
dependencies: {
|
|
2227
|
+
"@leanmcp/core": "^0.3.9",
|
|
2228
|
+
"@leanmcp/ui": "^0.2.1",
|
|
2229
|
+
"@leanmcp/auth": "^0.3.2",
|
|
2230
|
+
"dotenv": "^16.5.0"
|
|
2231
|
+
},
|
|
2232
|
+
devDependencies: {
|
|
2233
|
+
"@leanmcp/cli": "^0.3.0",
|
|
2234
|
+
"@types/node": "^20.0.0",
|
|
2235
|
+
"tsx": "^4.20.3",
|
|
2236
|
+
"typescript": "^5.6.3"
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
await fs8.writeJSON(path8.join(targetDir, "package.json"), pkg2, {
|
|
2240
|
+
spaces: 2
|
|
2241
|
+
});
|
|
2242
|
+
const tsconfig = {
|
|
2243
|
+
compilerOptions: {
|
|
2244
|
+
module: "ESNext",
|
|
2245
|
+
target: "ES2022",
|
|
2246
|
+
moduleResolution: "Node",
|
|
2247
|
+
esModuleInterop: true,
|
|
2248
|
+
strict: true,
|
|
2249
|
+
skipLibCheck: true,
|
|
2250
|
+
outDir: "dist",
|
|
2251
|
+
experimentalDecorators: true,
|
|
2252
|
+
emitDecoratorMetadata: true
|
|
2253
|
+
},
|
|
2254
|
+
include: [
|
|
2255
|
+
"**/*.ts"
|
|
2256
|
+
],
|
|
2257
|
+
exclude: [
|
|
2258
|
+
"node_modules",
|
|
2259
|
+
"dist"
|
|
2260
|
+
]
|
|
2261
|
+
};
|
|
2262
|
+
await fs8.writeJSON(path8.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
2263
|
+
spaces: 2
|
|
2264
|
+
});
|
|
2265
|
+
const dashboardLine = options.dashboard === false ? `
|
|
2266
|
+
dashboard: false, // Dashboard disabled via --no-dashboard` : "";
|
|
2267
|
+
const mainTs = getMainTsTemplate(projectName, dashboardLine);
|
|
2268
|
+
await fs8.writeFile(path8.join(targetDir, "main.ts"), mainTs);
|
|
2269
|
+
const exampleServiceTs = getExampleServiceTemplate(projectName);
|
|
2270
|
+
await fs8.writeFile(path8.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
|
|
2271
|
+
const gitignore = gitignoreTemplate;
|
|
2272
|
+
const env = `# Server Configuration
|
|
2273
|
+
PORT=3001
|
|
2274
|
+
NODE_ENV=development
|
|
2022
2275
|
|
|
2023
|
-
|
|
2276
|
+
# Add your environment variables here
|
|
2024
2277
|
`;
|
|
2025
|
-
await
|
|
2278
|
+
await fs8.writeFile(path8.join(targetDir, ".gitignore"), gitignore);
|
|
2279
|
+
await fs8.writeFile(path8.join(targetDir, ".env"), env);
|
|
2280
|
+
const readme = getReadmeTemplate(projectName);
|
|
2281
|
+
await fs8.writeFile(path8.join(targetDir, "README.md"), readme);
|
|
2026
2282
|
spinner.succeed(`Project ${projectName} created!`);
|
|
2027
|
-
console.log(
|
|
2028
|
-
console.log(
|
|
2029
|
-
cd ${projectName}
|
|
2283
|
+
console.log(chalk7.green("\nSuccess! Your MCP server is ready.\n"));
|
|
2284
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2285
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2286
|
+
console.log(chalk7.gray(` leanmcp deploy .
|
|
2030
2287
|
`));
|
|
2288
|
+
console.log(chalk7.cyan("Need help? Join our Discord:"));
|
|
2289
|
+
console.log(chalk7.blue(" https://discord.com/invite/DsRcA3GwPy\n"));
|
|
2031
2290
|
const isNonInteractive = options.install !== void 0 || options.allowAll;
|
|
2032
2291
|
if (options.install === false) {
|
|
2033
|
-
console.log(
|
|
2034
|
-
console.log(
|
|
2035
|
-
console.log(
|
|
2036
|
-
console.log(
|
|
2292
|
+
console.log(chalk7.cyan("To get started:"));
|
|
2293
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2294
|
+
console.log(chalk7.gray(` npm install`));
|
|
2295
|
+
console.log(chalk7.gray(` npm run dev`));
|
|
2296
|
+
console.log();
|
|
2297
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2298
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2299
|
+
console.log(chalk7.gray(` leanmcp deploy .`));
|
|
2037
2300
|
return;
|
|
2038
2301
|
}
|
|
2039
2302
|
const shouldInstall = isNonInteractive ? true : await confirm3({
|
|
@@ -2041,10 +2304,10 @@ MIT
|
|
|
2041
2304
|
default: true
|
|
2042
2305
|
});
|
|
2043
2306
|
if (shouldInstall) {
|
|
2044
|
-
const installSpinner =
|
|
2307
|
+
const installSpinner = ora7("Installing dependencies...").start();
|
|
2045
2308
|
try {
|
|
2046
2309
|
await new Promise((resolve, reject) => {
|
|
2047
|
-
const npmInstall =
|
|
2310
|
+
const npmInstall = spawn4("npm", [
|
|
2048
2311
|
"install"
|
|
2049
2312
|
], {
|
|
2050
2313
|
cwd: targetDir,
|
|
@@ -2062,9 +2325,13 @@ MIT
|
|
|
2062
2325
|
});
|
|
2063
2326
|
installSpinner.succeed("Dependencies installed successfully!");
|
|
2064
2327
|
if (options.install === true) {
|
|
2065
|
-
console.log(
|
|
2066
|
-
console.log(
|
|
2067
|
-
console.log(
|
|
2328
|
+
console.log(chalk7.cyan("\nTo start the development server:"));
|
|
2329
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2330
|
+
console.log(chalk7.gray(` npm run dev`));
|
|
2331
|
+
console.log();
|
|
2332
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2333
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2334
|
+
console.log(chalk7.gray(` leanmcp deploy .`));
|
|
2068
2335
|
return;
|
|
2069
2336
|
}
|
|
2070
2337
|
const shouldStartDev = options.allowAll ? true : await confirm3({
|
|
@@ -2072,8 +2339,8 @@ MIT
|
|
|
2072
2339
|
default: true
|
|
2073
2340
|
});
|
|
2074
2341
|
if (shouldStartDev) {
|
|
2075
|
-
console.log(
|
|
2076
|
-
const devServer =
|
|
2342
|
+
console.log(chalk7.cyan("\nStarting development server...\n"));
|
|
2343
|
+
const devServer = spawn4("npm", [
|
|
2077
2344
|
"run",
|
|
2078
2345
|
"dev"
|
|
2079
2346
|
], {
|
|
@@ -2086,106 +2353,57 @@ MIT
|
|
|
2086
2353
|
process.exit(0);
|
|
2087
2354
|
});
|
|
2088
2355
|
} else {
|
|
2089
|
-
console.log(
|
|
2090
|
-
console.log(
|
|
2091
|
-
console.log(
|
|
2356
|
+
console.log(chalk7.cyan("\nTo start the development server later:"));
|
|
2357
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2358
|
+
console.log(chalk7.gray(` npm run dev`));
|
|
2359
|
+
console.log();
|
|
2360
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2361
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2362
|
+
console.log(chalk7.gray(` leanmcp deploy .`));
|
|
2092
2363
|
}
|
|
2093
2364
|
} catch (error) {
|
|
2094
2365
|
installSpinner.fail("Failed to install dependencies");
|
|
2095
|
-
console.error(
|
|
2096
|
-
console.log(
|
|
2097
|
-
console.log(
|
|
2098
|
-
console.log(
|
|
2366
|
+
console.error(chalk7.red(error instanceof Error ? error.message : String(error)));
|
|
2367
|
+
console.log(chalk7.cyan("\nYou can install dependencies manually:"));
|
|
2368
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2369
|
+
console.log(chalk7.gray(` npm install`));
|
|
2099
2370
|
}
|
|
2100
2371
|
} else {
|
|
2101
|
-
console.log(
|
|
2102
|
-
console.log(
|
|
2103
|
-
console.log(
|
|
2104
|
-
console.log(
|
|
2372
|
+
console.log(chalk7.cyan("\nTo get started:"));
|
|
2373
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2374
|
+
console.log(chalk7.gray(` npm install`));
|
|
2375
|
+
console.log(chalk7.gray(` npm run dev`));
|
|
2376
|
+
console.log();
|
|
2377
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2378
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2379
|
+
console.log(chalk7.gray(` leanmcp deploy .`));
|
|
2105
2380
|
}
|
|
2106
2381
|
});
|
|
2107
2382
|
program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
|
|
2108
2383
|
const cwd = process.cwd();
|
|
2109
|
-
const mcpDir =
|
|
2110
|
-
if (!
|
|
2111
|
-
console.error(
|
|
2384
|
+
const mcpDir = path8.join(cwd, "mcp");
|
|
2385
|
+
if (!fs8.existsSync(path8.join(cwd, "main.ts"))) {
|
|
2386
|
+
console.error(chalk7.red("ERROR: Not a LeanMCP project (main.ts missing)."));
|
|
2112
2387
|
process.exit(1);
|
|
2113
2388
|
}
|
|
2114
|
-
const serviceDir =
|
|
2115
|
-
const serviceFile =
|
|
2116
|
-
if (
|
|
2117
|
-
console.error(
|
|
2389
|
+
const serviceDir = path8.join(mcpDir, serviceName);
|
|
2390
|
+
const serviceFile = path8.join(serviceDir, "index.ts");
|
|
2391
|
+
if (fs8.existsSync(serviceDir)) {
|
|
2392
|
+
console.error(chalk7.red(`ERROR: Service ${serviceName} already exists.`));
|
|
2118
2393
|
process.exit(1);
|
|
2119
2394
|
}
|
|
2120
|
-
await
|
|
2121
|
-
const indexTs =
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
name!: string;
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
/**
|
|
2133
|
-
* ${capitalize(serviceName)} Service
|
|
2134
|
-
*
|
|
2135
|
-
* This service demonstrates the three types of MCP primitives:
|
|
2136
|
-
* - Tools: Callable functions (like API endpoints)
|
|
2137
|
-
* - Prompts: Reusable prompt templates
|
|
2138
|
-
* - Resources: Data sources/endpoints
|
|
2139
|
-
*/
|
|
2140
|
-
export class ${capitalize(serviceName)}Service {
|
|
2141
|
-
// TOOL - Callable function
|
|
2142
|
-
// Tool name: "greet" (from function name)
|
|
2143
|
-
@Tool({
|
|
2144
|
-
description: "Greet a user by name",
|
|
2145
|
-
inputClass: GreetInput
|
|
2146
|
-
})
|
|
2147
|
-
greet(args: GreetInput) {
|
|
2148
|
-
return { message: \`Hello, \${args.name}! from ${serviceName}\` };
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
// PROMPT - Prompt template
|
|
2152
|
-
// Prompt name: "welcomePrompt" (from function name)
|
|
2153
|
-
@Prompt({ description: "Welcome message prompt template" })
|
|
2154
|
-
welcomePrompt(args: { userName?: string }) {
|
|
2155
|
-
return {
|
|
2156
|
-
messages: [
|
|
2157
|
-
{
|
|
2158
|
-
role: "user",
|
|
2159
|
-
content: {
|
|
2160
|
-
type: "text",
|
|
2161
|
-
text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
]
|
|
2165
|
-
};
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
// RESOURCE - Data endpoint
|
|
2169
|
-
// Resource URI auto-generated from class and method name
|
|
2170
|
-
@Resource({ description: "${capitalize(serviceName)} service status" })
|
|
2171
|
-
getStatus() {
|
|
2172
|
-
return {
|
|
2173
|
-
service: "${serviceName}",
|
|
2174
|
-
status: "active",
|
|
2175
|
-
timestamp: new Date().toISOString()
|
|
2176
|
-
};
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
`;
|
|
2180
|
-
await fs7.writeFile(serviceFile, indexTs);
|
|
2181
|
-
console.log(chalk6.green(`\\nCreated new service: ${chalk6.bold(serviceName)}`));
|
|
2182
|
-
console.log(chalk6.gray(` File: mcp/${serviceName}/index.ts`));
|
|
2183
|
-
console.log(chalk6.gray(` Tool: greet`));
|
|
2184
|
-
console.log(chalk6.gray(` Prompt: welcomePrompt`));
|
|
2185
|
-
console.log(chalk6.gray(` Resource: getStatus`));
|
|
2186
|
-
console.log(chalk6.green(`\\nService will be automatically discovered on next server start!`));
|
|
2395
|
+
await fs8.mkdirp(serviceDir);
|
|
2396
|
+
const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
|
|
2397
|
+
await fs8.writeFile(serviceFile, indexTs);
|
|
2398
|
+
console.log(chalk7.green(`\\nCreated new service: ${chalk7.bold(serviceName)}`));
|
|
2399
|
+
console.log(chalk7.gray(` File: mcp/${serviceName}/index.ts`));
|
|
2400
|
+
console.log(chalk7.gray(` Tool: greet`));
|
|
2401
|
+
console.log(chalk7.gray(` Prompt: welcomePrompt`));
|
|
2402
|
+
console.log(chalk7.gray(` Resource: getStatus`));
|
|
2403
|
+
console.log(chalk7.green(`\\nService will be automatically discovered on next server start!`));
|
|
2187
2404
|
});
|
|
2188
2405
|
program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(devCommand);
|
|
2406
|
+
program.command("build").description("Build UI components and compile TypeScript for production").action(buildCommand);
|
|
2189
2407
|
program.command("start").description("Build UI components and start production server").action(startCommand);
|
|
2190
2408
|
program.command("login").description("Authenticate with LeanMCP cloud using an API key").option("--debug", "Enable debug logging").action(async (options) => {
|
|
2191
2409
|
if (options.debug) {
|