@leanmcp/cli 0.3.0 → 0.4.0
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 +758 -543
- 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";
|
|
@@ -41,7 +41,7 @@ async function scanUIApp(projectDir) {
|
|
|
41
41
|
for (const relativeFile of tsFiles) {
|
|
42
42
|
const filePath = path.join(mcpDir, relativeFile);
|
|
43
43
|
const content = await fs.readFile(filePath, "utf-8");
|
|
44
|
-
if (!content.includes("@UIApp") || !content.includes("@leanmcp/ui")) {
|
|
44
|
+
if (!content.includes("@UIApp") && !content.includes("@GPTApp") || !content.includes("@leanmcp/ui")) {
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
47
|
const uiApps = parseUIAppDecorators(content, filePath);
|
|
@@ -55,11 +55,13 @@ function parseUIAppDecorators(content, filePath) {
|
|
|
55
55
|
const classMatch = content.match(/export\s+class\s+(\w+)/);
|
|
56
56
|
const serviceName = classMatch ? classMatch[1] : "Unknown";
|
|
57
57
|
const importMap = parseImports(content, filePath);
|
|
58
|
-
const uiAppRegex = /@UIApp\s*\(\s*\{([
|
|
58
|
+
const uiAppRegex = /@(UIApp|GPTApp)\s*\(\s*\{([\s\S]+?)\}\s*\)\s*(?:async\s+)?(\w+)/g;
|
|
59
59
|
let match;
|
|
60
60
|
while ((match = uiAppRegex.exec(content)) !== null) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
61
|
+
const decoratorName = match[1];
|
|
62
|
+
const decoratorBody = match[2];
|
|
63
|
+
const methodName = match[3];
|
|
64
|
+
const isGPTApp = decoratorName === "GPTApp";
|
|
63
65
|
let componentPath;
|
|
64
66
|
let componentName;
|
|
65
67
|
const stringMatch = decoratorBody.match(/component\s*:\s*['"]([^'"]+)['"]/);
|
|
@@ -88,14 +90,32 @@ function parseUIAppDecorators(content, filePath) {
|
|
|
88
90
|
}
|
|
89
91
|
if (!componentPath) continue;
|
|
90
92
|
const servicePrefix = serviceName.replace(/Service$/i, "").toLowerCase();
|
|
91
|
-
|
|
93
|
+
let resourceUri = `ui://${servicePrefix}/${methodName}`;
|
|
94
|
+
const uriMatch = decoratorBody.match(/uri\s*:\s*['"]([^'"]+)['"]/);
|
|
95
|
+
if (uriMatch) {
|
|
96
|
+
resourceUri = uriMatch[1];
|
|
97
|
+
}
|
|
98
|
+
let gptOptions = void 0;
|
|
99
|
+
if (isGPTApp) {
|
|
100
|
+
gptOptions = {};
|
|
101
|
+
if (decoratorBody.includes("widgetAccessible: true")) gptOptions.widgetAccessible = true;
|
|
102
|
+
if (decoratorBody.includes("prefersBorder: true")) gptOptions.prefersBorder = true;
|
|
103
|
+
const visibilityMatch = decoratorBody.match(/visibility\s*:\s*['"](public|private)['"]/);
|
|
104
|
+
if (visibilityMatch) gptOptions.visibility = visibilityMatch[1];
|
|
105
|
+
const domainMatch = decoratorBody.match(/widgetDomain\s*:\s*['"]([^'"]+)['"]/);
|
|
106
|
+
if (domainMatch) gptOptions.widgetDomain = domainMatch[1];
|
|
107
|
+
const descriptionMatch = decoratorBody.match(/widgetDescription\s*:\s*['"]([^'"]+)['"]/);
|
|
108
|
+
if (descriptionMatch) gptOptions.widgetDescription = descriptionMatch[1];
|
|
109
|
+
}
|
|
92
110
|
results.push({
|
|
93
111
|
servicePath: filePath,
|
|
94
112
|
componentPath,
|
|
95
113
|
componentName,
|
|
96
114
|
resourceUri,
|
|
97
115
|
methodName,
|
|
98
|
-
serviceName
|
|
116
|
+
serviceName,
|
|
117
|
+
isGPTApp,
|
|
118
|
+
gptOptions
|
|
99
119
|
});
|
|
100
120
|
}
|
|
101
121
|
return results;
|
|
@@ -313,7 +333,30 @@ module.exports = {
|
|
|
313
333
|
}
|
|
314
334
|
`);
|
|
315
335
|
const relativeComponentPath = path2.relative(tempDir, componentPath).replace(/\\/g, "/");
|
|
316
|
-
|
|
336
|
+
const isGPTApp = uiApp.isGPTApp;
|
|
337
|
+
const entryContent = isGPTApp ? `
|
|
338
|
+
import React, { StrictMode } from 'react';
|
|
339
|
+
import { createRoot } from 'react-dom/client';
|
|
340
|
+
import { GPTAppProvider, Toaster } from '@leanmcp/ui';
|
|
341
|
+
import '@leanmcp/ui/styles.css';
|
|
342
|
+
import './styles.css';
|
|
343
|
+
import { ${componentName} } from '${relativeComponentPath.replace(/\.tsx?$/, "")}';
|
|
344
|
+
|
|
345
|
+
function App() {
|
|
346
|
+
return (
|
|
347
|
+
<GPTAppProvider appName="${componentName}">
|
|
348
|
+
<${componentName} />
|
|
349
|
+
<Toaster />
|
|
350
|
+
</GPTAppProvider>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
createRoot(document.getElementById('root')!).render(
|
|
355
|
+
<StrictMode>
|
|
356
|
+
<App />
|
|
357
|
+
</StrictMode>
|
|
358
|
+
);
|
|
359
|
+
` : `
|
|
317
360
|
import React, { StrictMode } from 'react';
|
|
318
361
|
import { createRoot } from 'react-dom/client';
|
|
319
362
|
import { AppProvider, Toaster } from '@leanmcp/ui';
|
|
@@ -340,7 +383,8 @@ createRoot(document.getElementById('root')!).render(
|
|
|
340
383
|
<App />
|
|
341
384
|
</StrictMode>
|
|
342
385
|
);
|
|
343
|
-
|
|
386
|
+
`;
|
|
387
|
+
await fs2.writeFile(entryJs, entryContent);
|
|
344
388
|
try {
|
|
345
389
|
const reactPath = resolveReactDependency(projectDir, "react");
|
|
346
390
|
const reactDomPath = resolveReactDependency(projectDir, "react-dom");
|
|
@@ -477,7 +521,15 @@ async function devCommand() {
|
|
|
477
521
|
for (const app of uiApps) {
|
|
478
522
|
const result = await buildUIComponent(app, cwd, true);
|
|
479
523
|
if (result.success) {
|
|
480
|
-
|
|
524
|
+
if (app.isGPTApp) {
|
|
525
|
+
manifest[app.resourceUri] = {
|
|
526
|
+
htmlPath: result.htmlPath,
|
|
527
|
+
isGPTApp: true,
|
|
528
|
+
gptMeta: app.gptOptions
|
|
529
|
+
};
|
|
530
|
+
} else {
|
|
531
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
532
|
+
}
|
|
481
533
|
} else {
|
|
482
534
|
errors.push(`${app.componentName}: ${result.error}`);
|
|
483
535
|
}
|
|
@@ -543,7 +595,15 @@ async function devCommand() {
|
|
|
543
595
|
console.log(chalk.cyan(`${action} ${app.componentName}...`));
|
|
544
596
|
const result = await buildUIComponent(app, cwd, true);
|
|
545
597
|
if (result.success) {
|
|
546
|
-
|
|
598
|
+
if (app.isGPTApp) {
|
|
599
|
+
manifest[app.resourceUri] = {
|
|
600
|
+
htmlPath: result.htmlPath,
|
|
601
|
+
isGPTApp: true,
|
|
602
|
+
gptMeta: app.gptOptions
|
|
603
|
+
};
|
|
604
|
+
} else {
|
|
605
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
606
|
+
}
|
|
547
607
|
if (await fs3.pathExists(app.componentPath)) {
|
|
548
608
|
componentHashCache.set(app.resourceUri, computeHash(app.componentPath));
|
|
549
609
|
}
|
|
@@ -556,37 +616,51 @@ async function devCommand() {
|
|
|
556
616
|
previousUIApps = currentUIApps;
|
|
557
617
|
}, 150);
|
|
558
618
|
});
|
|
619
|
+
const isWindows = process.platform === "win32";
|
|
559
620
|
let isCleaningUp = false;
|
|
560
621
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
561
622
|
if (isCleaningUp) return;
|
|
562
623
|
isCleaningUp = true;
|
|
563
624
|
console.log(chalk.gray("\nShutting down..."));
|
|
564
|
-
if (watcher)
|
|
565
|
-
|
|
625
|
+
if (watcher) {
|
|
626
|
+
watcher.close();
|
|
627
|
+
watcher = null;
|
|
628
|
+
}
|
|
629
|
+
if (!isWindows && !devServer.killed) {
|
|
630
|
+
devServer.kill("SIGTERM");
|
|
631
|
+
}
|
|
566
632
|
}, "cleanup");
|
|
567
|
-
process.
|
|
568
|
-
process.
|
|
569
|
-
devServer.on("
|
|
570
|
-
|
|
571
|
-
|
|
633
|
+
process.once("SIGINT", cleanup);
|
|
634
|
+
process.once("SIGTERM", cleanup);
|
|
635
|
+
devServer.on("error", (err) => {
|
|
636
|
+
console.error(chalk.red(`Dev server error: ${err.message}`));
|
|
637
|
+
});
|
|
638
|
+
devServer.on("exit", (code, signal) => {
|
|
639
|
+
if (watcher) {
|
|
640
|
+
watcher.close();
|
|
641
|
+
watcher = null;
|
|
642
|
+
}
|
|
643
|
+
setImmediate(() => {
|
|
644
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
645
|
+
});
|
|
572
646
|
});
|
|
573
647
|
}
|
|
574
648
|
__name(devCommand, "devCommand");
|
|
575
649
|
|
|
576
|
-
// src/commands/
|
|
650
|
+
// src/commands/build.ts
|
|
577
651
|
import { spawn as spawn2 } from "child_process";
|
|
578
652
|
import chalk2 from "chalk";
|
|
579
653
|
import ora2 from "ora";
|
|
580
654
|
import path4 from "path";
|
|
581
655
|
import fs4 from "fs-extra";
|
|
582
|
-
async function
|
|
656
|
+
async function buildCommand() {
|
|
583
657
|
const cwd = process.cwd();
|
|
584
658
|
if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
|
|
585
659
|
console.error(chalk2.red("ERROR: Not a LeanMCP project (main.ts not found)."));
|
|
586
660
|
console.error(chalk2.gray("Run this command from your project root."));
|
|
587
661
|
process.exit(1);
|
|
588
662
|
}
|
|
589
|
-
console.log(chalk2.cyan("\n\u{
|
|
663
|
+
console.log(chalk2.cyan("\n\u{1F528} LeanMCP Build\n"));
|
|
590
664
|
const scanSpinner = ora2("Scanning for @UIApp components...").start();
|
|
591
665
|
const uiApps = await scanUIApp(cwd);
|
|
592
666
|
if (uiApps.length === 0) {
|
|
@@ -601,7 +675,11 @@ async function startCommand() {
|
|
|
601
675
|
for (const app of uiApps) {
|
|
602
676
|
const result = await buildUIComponent(app, cwd, false);
|
|
603
677
|
if (result.success) {
|
|
604
|
-
manifest[app.resourceUri] =
|
|
678
|
+
manifest[app.resourceUri] = {
|
|
679
|
+
htmlPath: result.htmlPath,
|
|
680
|
+
isGPTApp: app.isGPTApp,
|
|
681
|
+
gptMeta: app.gptOptions
|
|
682
|
+
};
|
|
605
683
|
} else {
|
|
606
684
|
errors.push(`${app.componentName}: ${result.error}`);
|
|
607
685
|
}
|
|
@@ -642,34 +720,125 @@ async function startCommand() {
|
|
|
642
720
|
console.error(chalk2.red(error instanceof Error ? error.message : String(error)));
|
|
643
721
|
process.exit(1);
|
|
644
722
|
}
|
|
645
|
-
console.log(chalk2.
|
|
646
|
-
|
|
723
|
+
console.log(chalk2.green("\nBuild complete!"));
|
|
724
|
+
console.log(chalk2.gray("\nTo start the server:"));
|
|
725
|
+
console.log(chalk2.cyan(" npm run start:node\n"));
|
|
726
|
+
}
|
|
727
|
+
__name(buildCommand, "buildCommand");
|
|
728
|
+
|
|
729
|
+
// src/commands/start.ts
|
|
730
|
+
import { spawn as spawn3 } from "child_process";
|
|
731
|
+
import chalk3 from "chalk";
|
|
732
|
+
import ora3 from "ora";
|
|
733
|
+
import path5 from "path";
|
|
734
|
+
import fs5 from "fs-extra";
|
|
735
|
+
async function startCommand() {
|
|
736
|
+
const cwd = process.cwd();
|
|
737
|
+
if (!await fs5.pathExists(path5.join(cwd, "main.ts"))) {
|
|
738
|
+
console.error(chalk3.red("ERROR: Not a LeanMCP project (main.ts not found)."));
|
|
739
|
+
console.error(chalk3.gray("Run this command from your project root."));
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
console.log(chalk3.cyan("\n\u{1F680} LeanMCP Production Build\n"));
|
|
743
|
+
const scanSpinner = ora3("Scanning for @UIApp components...").start();
|
|
744
|
+
const uiApps = await scanUIApp(cwd);
|
|
745
|
+
if (uiApps.length === 0) {
|
|
746
|
+
scanSpinner.succeed("No @UIApp components found");
|
|
747
|
+
} else {
|
|
748
|
+
scanSpinner.succeed(`Found ${uiApps.length} @UIApp component(s)`);
|
|
749
|
+
}
|
|
750
|
+
const manifest = {};
|
|
751
|
+
if (uiApps.length > 0) {
|
|
752
|
+
const buildSpinner = ora3("Building UI components...").start();
|
|
753
|
+
const errors = [];
|
|
754
|
+
for (const app of uiApps) {
|
|
755
|
+
const result = await buildUIComponent(app, cwd, false);
|
|
756
|
+
if (result.success) {
|
|
757
|
+
if (app.isGPTApp) {
|
|
758
|
+
manifest[app.resourceUri] = {
|
|
759
|
+
htmlPath: result.htmlPath,
|
|
760
|
+
isGPTApp: true,
|
|
761
|
+
gptMeta: app.gptOptions
|
|
762
|
+
};
|
|
763
|
+
} else {
|
|
764
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
765
|
+
}
|
|
766
|
+
} else {
|
|
767
|
+
errors.push(`${app.componentName}: ${result.error}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
await writeUIManifest(manifest, cwd);
|
|
771
|
+
if (errors.length > 0) {
|
|
772
|
+
buildSpinner.fail("Build failed");
|
|
773
|
+
for (const error of errors) {
|
|
774
|
+
console.error(chalk3.red(` \u2717 ${error}`));
|
|
775
|
+
}
|
|
776
|
+
process.exit(1);
|
|
777
|
+
}
|
|
778
|
+
buildSpinner.succeed("UI components built");
|
|
779
|
+
}
|
|
780
|
+
const tscSpinner = ora3("Compiling TypeScript...").start();
|
|
781
|
+
try {
|
|
782
|
+
await new Promise((resolve, reject) => {
|
|
783
|
+
const tsc = spawn3("npx", [
|
|
784
|
+
"tsc"
|
|
785
|
+
], {
|
|
786
|
+
cwd,
|
|
787
|
+
stdio: "pipe",
|
|
788
|
+
shell: true
|
|
789
|
+
});
|
|
790
|
+
let stderr = "";
|
|
791
|
+
tsc.stderr?.on("data", (data) => {
|
|
792
|
+
stderr += data;
|
|
793
|
+
});
|
|
794
|
+
tsc.on("close", (code) => {
|
|
795
|
+
if (code === 0) resolve();
|
|
796
|
+
else reject(new Error(stderr || `tsc exited with code ${code}`));
|
|
797
|
+
});
|
|
798
|
+
tsc.on("error", reject);
|
|
799
|
+
});
|
|
800
|
+
tscSpinner.succeed("TypeScript compiled");
|
|
801
|
+
} catch (error) {
|
|
802
|
+
tscSpinner.fail("TypeScript compilation failed");
|
|
803
|
+
console.error(chalk3.red(error instanceof Error ? error.message : String(error)));
|
|
804
|
+
process.exit(1);
|
|
805
|
+
}
|
|
806
|
+
console.log(chalk3.cyan("\nStarting production server...\n"));
|
|
807
|
+
const server = spawn3("node", [
|
|
647
808
|
"dist/main.js"
|
|
648
809
|
], {
|
|
649
810
|
cwd,
|
|
650
811
|
stdio: "inherit",
|
|
651
812
|
shell: true
|
|
652
813
|
});
|
|
814
|
+
const isWindows = process.platform === "win32";
|
|
653
815
|
let isCleaningUp = false;
|
|
654
816
|
const cleanup = /* @__PURE__ */ __name(() => {
|
|
655
817
|
if (isCleaningUp) return;
|
|
656
818
|
isCleaningUp = true;
|
|
657
|
-
console.log(
|
|
658
|
-
server.
|
|
819
|
+
console.log(chalk3.gray("\nShutting down..."));
|
|
820
|
+
if (!isWindows && !server.killed) {
|
|
821
|
+
server.kill("SIGTERM");
|
|
822
|
+
}
|
|
659
823
|
}, "cleanup");
|
|
660
|
-
process.
|
|
661
|
-
process.
|
|
662
|
-
server.on("
|
|
663
|
-
|
|
824
|
+
process.once("SIGINT", cleanup);
|
|
825
|
+
process.once("SIGTERM", cleanup);
|
|
826
|
+
server.on("error", (err) => {
|
|
827
|
+
console.error(chalk3.red(`Server error: ${err.message}`));
|
|
828
|
+
});
|
|
829
|
+
server.on("exit", (code, signal) => {
|
|
830
|
+
setImmediate(() => {
|
|
831
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
832
|
+
});
|
|
664
833
|
});
|
|
665
834
|
}
|
|
666
835
|
__name(startCommand, "startCommand");
|
|
667
836
|
|
|
668
837
|
// src/commands/login.ts
|
|
669
|
-
import
|
|
670
|
-
import
|
|
671
|
-
import
|
|
672
|
-
import
|
|
838
|
+
import chalk4 from "chalk";
|
|
839
|
+
import ora4 from "ora";
|
|
840
|
+
import path6 from "path";
|
|
841
|
+
import fs6 from "fs-extra";
|
|
673
842
|
import os from "os";
|
|
674
843
|
import { input, confirm } from "@inquirer/prompts";
|
|
675
844
|
var DEBUG_MODE = false;
|
|
@@ -679,16 +848,16 @@ function setDebugMode(enabled) {
|
|
|
679
848
|
__name(setDebugMode, "setDebugMode");
|
|
680
849
|
function debug(message, ...args) {
|
|
681
850
|
if (DEBUG_MODE) {
|
|
682
|
-
console.log(
|
|
851
|
+
console.log(chalk4.gray(`[DEBUG] ${message}`), ...args);
|
|
683
852
|
}
|
|
684
853
|
}
|
|
685
854
|
__name(debug, "debug");
|
|
686
|
-
var CONFIG_DIR =
|
|
687
|
-
var CONFIG_FILE =
|
|
855
|
+
var CONFIG_DIR = path6.join(os.homedir(), ".leanmcp");
|
|
856
|
+
var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
|
|
688
857
|
async function loadConfig() {
|
|
689
858
|
try {
|
|
690
|
-
if (await
|
|
691
|
-
return await
|
|
859
|
+
if (await fs6.pathExists(CONFIG_FILE)) {
|
|
860
|
+
return await fs6.readJSON(CONFIG_FILE);
|
|
692
861
|
}
|
|
693
862
|
} catch (error) {
|
|
694
863
|
}
|
|
@@ -696,8 +865,8 @@ async function loadConfig() {
|
|
|
696
865
|
}
|
|
697
866
|
__name(loadConfig, "loadConfig");
|
|
698
867
|
async function saveConfig(config) {
|
|
699
|
-
await
|
|
700
|
-
await
|
|
868
|
+
await fs6.ensureDir(CONFIG_DIR);
|
|
869
|
+
await fs6.writeJSON(CONFIG_FILE, config, {
|
|
701
870
|
spaces: 2
|
|
702
871
|
});
|
|
703
872
|
}
|
|
@@ -735,24 +904,24 @@ Allowed URLs:
|
|
|
735
904
|
}
|
|
736
905
|
__name(getApiUrl, "getApiUrl");
|
|
737
906
|
async function loginCommand() {
|
|
738
|
-
console.log(
|
|
907
|
+
console.log(chalk4.cyan("\nLeanMCP Login\n"));
|
|
739
908
|
const existingConfig = await loadConfig();
|
|
740
909
|
if (existingConfig.apiKey) {
|
|
741
|
-
console.log(
|
|
910
|
+
console.log(chalk4.yellow("You are already logged in."));
|
|
742
911
|
const shouldRelogin = await confirm({
|
|
743
912
|
message: "Do you want to replace the existing API key?",
|
|
744
913
|
default: false
|
|
745
914
|
});
|
|
746
915
|
if (!shouldRelogin) {
|
|
747
|
-
console.log(
|
|
916
|
+
console.log(chalk4.gray("\nLogin cancelled. Existing API key preserved."));
|
|
748
917
|
return;
|
|
749
918
|
}
|
|
750
919
|
}
|
|
751
|
-
console.log(
|
|
752
|
-
console.log(
|
|
753
|
-
console.log(
|
|
754
|
-
console.log(
|
|
755
|
-
console.log(
|
|
920
|
+
console.log(chalk4.white("To authenticate, you need an API key from LeanMCP.\n"));
|
|
921
|
+
console.log(chalk4.cyan("Steps:"));
|
|
922
|
+
console.log(chalk4.gray(" 1. Go to: ") + chalk4.blue.underline("https://ship.leanmcp.com/api-keys"));
|
|
923
|
+
console.log(chalk4.gray(' 2. Create a new API key with "BUILD_AND_DEPLOY" scope'));
|
|
924
|
+
console.log(chalk4.gray(" 3. Copy the API key and paste it below\n"));
|
|
756
925
|
const apiKey = await input({
|
|
757
926
|
message: "Enter your API key:",
|
|
758
927
|
validate: /* @__PURE__ */ __name((value) => {
|
|
@@ -765,7 +934,7 @@ async function loginCommand() {
|
|
|
765
934
|
return true;
|
|
766
935
|
}, "validate")
|
|
767
936
|
});
|
|
768
|
-
const spinner =
|
|
937
|
+
const spinner = ora4("Validating API key...").start();
|
|
769
938
|
try {
|
|
770
939
|
const apiUrl = await getApiUrl();
|
|
771
940
|
const validateUrl = `${apiUrl}/api-keys/validate`;
|
|
@@ -785,10 +954,10 @@ async function loginCommand() {
|
|
|
785
954
|
const errorText = await response.text();
|
|
786
955
|
debug("Error response:", errorText);
|
|
787
956
|
spinner.fail("Invalid API key");
|
|
788
|
-
console.error(
|
|
789
|
-
console.log(
|
|
957
|
+
console.error(chalk4.red("\nThe API key is invalid or has expired."));
|
|
958
|
+
console.log(chalk4.gray("Please check your API key and try again."));
|
|
790
959
|
if (DEBUG_MODE) {
|
|
791
|
-
console.log(
|
|
960
|
+
console.log(chalk4.gray(`Debug: Status ${response.status}, Response: ${errorText}`));
|
|
792
961
|
}
|
|
793
962
|
process.exit(1);
|
|
794
963
|
}
|
|
@@ -798,24 +967,24 @@ async function loginCommand() {
|
|
|
798
967
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
799
968
|
});
|
|
800
969
|
spinner.succeed("API key validated and saved");
|
|
801
|
-
console.log(
|
|
802
|
-
console.log(
|
|
970
|
+
console.log(chalk4.green("\nLogin successful!"));
|
|
971
|
+
console.log(chalk4.gray(` Config saved to: ${CONFIG_FILE}
|
|
803
972
|
`));
|
|
804
|
-
console.log(
|
|
805
|
-
console.log(
|
|
806
|
-
console.log(
|
|
973
|
+
console.log(chalk4.cyan("You can now use:"));
|
|
974
|
+
console.log(chalk4.gray(" leanmcp deploy <folder> - Deploy your MCP server"));
|
|
975
|
+
console.log(chalk4.gray(" leanmcp logout - Remove your API key"));
|
|
807
976
|
} catch (error) {
|
|
808
977
|
spinner.fail("Failed to validate API key");
|
|
809
978
|
debug("Error:", error);
|
|
810
979
|
if (error instanceof Error && error.message.includes("fetch")) {
|
|
811
|
-
console.error(
|
|
812
|
-
console.log(
|
|
980
|
+
console.error(chalk4.red("\nCould not connect to LeanMCP servers."));
|
|
981
|
+
console.log(chalk4.gray("Please check your internet connection and try again."));
|
|
813
982
|
} else {
|
|
814
|
-
console.error(
|
|
983
|
+
console.error(chalk4.red(`
|
|
815
984
|
Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
816
985
|
}
|
|
817
986
|
if (DEBUG_MODE) {
|
|
818
|
-
console.log(
|
|
987
|
+
console.log(chalk4.gray(`
|
|
819
988
|
Debug: Full error: ${error}`));
|
|
820
989
|
}
|
|
821
990
|
process.exit(1);
|
|
@@ -823,10 +992,10 @@ Debug: Full error: ${error}`));
|
|
|
823
992
|
}
|
|
824
993
|
__name(loginCommand, "loginCommand");
|
|
825
994
|
async function logoutCommand() {
|
|
826
|
-
console.log(
|
|
995
|
+
console.log(chalk4.cyan("\nLeanMCP Logout\n"));
|
|
827
996
|
const config = await loadConfig();
|
|
828
997
|
if (!config.apiKey) {
|
|
829
|
-
console.log(
|
|
998
|
+
console.log(chalk4.yellow("You are not currently logged in."));
|
|
830
999
|
return;
|
|
831
1000
|
}
|
|
832
1001
|
const shouldLogout = await confirm({
|
|
@@ -834,7 +1003,7 @@ async function logoutCommand() {
|
|
|
834
1003
|
default: false
|
|
835
1004
|
});
|
|
836
1005
|
if (!shouldLogout) {
|
|
837
|
-
console.log(
|
|
1006
|
+
console.log(chalk4.gray("\nLogout cancelled."));
|
|
838
1007
|
return;
|
|
839
1008
|
}
|
|
840
1009
|
await saveConfig({
|
|
@@ -842,33 +1011,33 @@ async function logoutCommand() {
|
|
|
842
1011
|
apiKey: void 0,
|
|
843
1012
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
844
1013
|
});
|
|
845
|
-
console.log(
|
|
846
|
-
console.log(
|
|
1014
|
+
console.log(chalk4.green("\nLogged out successfully!"));
|
|
1015
|
+
console.log(chalk4.gray(` API key removed from: ${CONFIG_FILE}`));
|
|
847
1016
|
}
|
|
848
1017
|
__name(logoutCommand, "logoutCommand");
|
|
849
1018
|
async function whoamiCommand() {
|
|
850
1019
|
const config = await loadConfig();
|
|
851
1020
|
if (!config.apiKey) {
|
|
852
|
-
console.log(
|
|
853
|
-
console.log(
|
|
1021
|
+
console.log(chalk4.yellow("\nYou are not logged in."));
|
|
1022
|
+
console.log(chalk4.gray("Run `leanmcp login` to authenticate.\n"));
|
|
854
1023
|
return;
|
|
855
1024
|
}
|
|
856
|
-
console.log(
|
|
857
|
-
console.log(
|
|
858
|
-
console.log(
|
|
859
|
-
console.log(
|
|
1025
|
+
console.log(chalk4.cyan("\nLeanMCP Authentication Status\n"));
|
|
1026
|
+
console.log(chalk4.green("Logged in"));
|
|
1027
|
+
console.log(chalk4.gray(` API Key: ${config.apiKey.substring(0, 15)}...`));
|
|
1028
|
+
console.log(chalk4.gray(` API URL: ${config.apiUrl || "https://ship.leanmcp.com"}`));
|
|
860
1029
|
if (config.lastUpdated) {
|
|
861
|
-
console.log(
|
|
1030
|
+
console.log(chalk4.gray(` Last updated: ${new Date(config.lastUpdated).toLocaleString()}`));
|
|
862
1031
|
}
|
|
863
1032
|
console.log();
|
|
864
1033
|
}
|
|
865
1034
|
__name(whoamiCommand, "whoamiCommand");
|
|
866
1035
|
|
|
867
1036
|
// src/commands/deploy.ts
|
|
868
|
-
import
|
|
869
|
-
import
|
|
870
|
-
import
|
|
871
|
-
import
|
|
1037
|
+
import chalk5 from "chalk";
|
|
1038
|
+
import ora5 from "ora";
|
|
1039
|
+
import path7 from "path";
|
|
1040
|
+
import fs7 from "fs-extra";
|
|
872
1041
|
import os2 from "os";
|
|
873
1042
|
import archiver from "archiver";
|
|
874
1043
|
import { input as input2, confirm as confirm2, select } from "@inquirer/prompts";
|
|
@@ -1058,7 +1227,7 @@ function setDeployDebugMode(enabled) {
|
|
|
1058
1227
|
__name(setDeployDebugMode, "setDeployDebugMode");
|
|
1059
1228
|
function debug2(message, ...args) {
|
|
1060
1229
|
if (DEBUG_MODE2) {
|
|
1061
|
-
console.log(
|
|
1230
|
+
console.log(chalk5.gray(`[DEBUG] ${message}`), ...args);
|
|
1062
1231
|
}
|
|
1063
1232
|
}
|
|
1064
1233
|
__name(debug2, "debug");
|
|
@@ -1095,7 +1264,7 @@ var API_ENDPOINTS = {
|
|
|
1095
1264
|
};
|
|
1096
1265
|
async function createZipArchive(folderPath, outputPath) {
|
|
1097
1266
|
return new Promise((resolve, reject) => {
|
|
1098
|
-
const output =
|
|
1267
|
+
const output = fs7.createWriteStream(outputPath);
|
|
1099
1268
|
const archive = archiver("zip", {
|
|
1100
1269
|
zlib: {
|
|
1101
1270
|
level: 9
|
|
@@ -1182,26 +1351,26 @@ async function waitForDeployment(apiUrl, apiKey, deploymentId, spinner) {
|
|
|
1182
1351
|
__name(waitForDeployment, "waitForDeployment");
|
|
1183
1352
|
async function deployCommand(folderPath, options = {}) {
|
|
1184
1353
|
const deployStartTime = Date.now();
|
|
1185
|
-
console.log(
|
|
1354
|
+
console.log(chalk5.cyan("\nLeanMCP Deploy\n"));
|
|
1186
1355
|
debug2("Starting deployment...");
|
|
1187
1356
|
const apiKey = await getApiKey();
|
|
1188
1357
|
if (!apiKey) {
|
|
1189
|
-
console.error(
|
|
1190
|
-
console.log(
|
|
1358
|
+
console.error(chalk5.red("Not logged in."));
|
|
1359
|
+
console.log(chalk5.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1191
1360
|
process.exit(1);
|
|
1192
1361
|
}
|
|
1193
1362
|
const apiUrl = await getApiUrl();
|
|
1194
1363
|
debug2("API URL:", apiUrl);
|
|
1195
|
-
const absolutePath =
|
|
1196
|
-
if (!await
|
|
1197
|
-
console.error(
|
|
1364
|
+
const absolutePath = path7.resolve(process.cwd(), folderPath);
|
|
1365
|
+
if (!await fs7.pathExists(absolutePath)) {
|
|
1366
|
+
console.error(chalk5.red(`Folder not found: ${absolutePath}`));
|
|
1198
1367
|
process.exit(1);
|
|
1199
1368
|
}
|
|
1200
|
-
const hasMainTs = await
|
|
1201
|
-
const hasPackageJson = await
|
|
1369
|
+
const hasMainTs = await fs7.pathExists(path7.join(absolutePath, "main.ts"));
|
|
1370
|
+
const hasPackageJson = await fs7.pathExists(path7.join(absolutePath, "package.json"));
|
|
1202
1371
|
if (!hasMainTs && !hasPackageJson) {
|
|
1203
|
-
console.error(
|
|
1204
|
-
console.log(
|
|
1372
|
+
console.error(chalk5.red("Not a valid project folder."));
|
|
1373
|
+
console.log(chalk5.gray("Expected main.ts or package.json in the folder.\n"));
|
|
1205
1374
|
process.exit(1);
|
|
1206
1375
|
}
|
|
1207
1376
|
debug2("Fetching existing projects...");
|
|
@@ -1222,17 +1391,17 @@ async function deployCommand(folderPath, options = {}) {
|
|
|
1222
1391
|
let projectName;
|
|
1223
1392
|
let existingProject = null;
|
|
1224
1393
|
let isUpdate = false;
|
|
1225
|
-
let folderName =
|
|
1394
|
+
let folderName = path7.basename(absolutePath);
|
|
1226
1395
|
if (hasPackageJson) {
|
|
1227
1396
|
try {
|
|
1228
|
-
const pkg2 = await
|
|
1397
|
+
const pkg2 = await fs7.readJSON(path7.join(absolutePath, "package.json"));
|
|
1229
1398
|
folderName = pkg2.name || folderName;
|
|
1230
1399
|
} catch (e) {
|
|
1231
1400
|
}
|
|
1232
1401
|
}
|
|
1233
1402
|
const matchingProject = existingProjects.find((p) => p.name === folderName);
|
|
1234
1403
|
if (matchingProject) {
|
|
1235
|
-
console.log(
|
|
1404
|
+
console.log(chalk5.yellow(`Project '${folderName}' already exists.
|
|
1236
1405
|
`));
|
|
1237
1406
|
const choice = await select({
|
|
1238
1407
|
message: "What would you like to do?",
|
|
@@ -1252,26 +1421,26 @@ async function deployCommand(folderPath, options = {}) {
|
|
|
1252
1421
|
]
|
|
1253
1422
|
});
|
|
1254
1423
|
if (choice === "cancel") {
|
|
1255
|
-
console.log(
|
|
1424
|
+
console.log(chalk5.gray("\nDeployment cancelled.\n"));
|
|
1256
1425
|
return;
|
|
1257
1426
|
}
|
|
1258
1427
|
if (choice === "update") {
|
|
1259
1428
|
existingProject = matchingProject;
|
|
1260
1429
|
projectName = matchingProject.name;
|
|
1261
1430
|
isUpdate = true;
|
|
1262
|
-
console.log(
|
|
1263
|
-
console.log(
|
|
1431
|
+
console.log(chalk5.yellow("\nWARNING: This will replace the existing deployment."));
|
|
1432
|
+
console.log(chalk5.gray("The previous version will be overwritten.\n"));
|
|
1264
1433
|
} else {
|
|
1265
1434
|
projectName = generateProjectName();
|
|
1266
|
-
console.log(
|
|
1267
|
-
Generated project name: ${
|
|
1435
|
+
console.log(chalk5.cyan(`
|
|
1436
|
+
Generated project name: ${chalk5.bold(projectName)}
|
|
1268
1437
|
`));
|
|
1269
1438
|
}
|
|
1270
1439
|
} else {
|
|
1271
1440
|
projectName = generateProjectName();
|
|
1272
|
-
console.log(
|
|
1441
|
+
console.log(chalk5.cyan(`Generated project name: ${chalk5.bold(projectName)}`));
|
|
1273
1442
|
}
|
|
1274
|
-
console.log(
|
|
1443
|
+
console.log(chalk5.gray(`Path: ${absolutePath}
|
|
1275
1444
|
`));
|
|
1276
1445
|
let subdomain = options.subdomain;
|
|
1277
1446
|
if (!subdomain) {
|
|
@@ -1293,7 +1462,7 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1293
1462
|
}, "validate")
|
|
1294
1463
|
});
|
|
1295
1464
|
}
|
|
1296
|
-
const checkSpinner =
|
|
1465
|
+
const checkSpinner = ora5("Checking subdomain availability...").start();
|
|
1297
1466
|
try {
|
|
1298
1467
|
debug2("Checking subdomain:", subdomain);
|
|
1299
1468
|
const checkResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.checkSubdomain}/${subdomain}`, {
|
|
@@ -1305,7 +1474,7 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1305
1474
|
const result = await checkResponse.json();
|
|
1306
1475
|
if (!result.available) {
|
|
1307
1476
|
checkSpinner.fail(`Subdomain '${subdomain}' is already taken`);
|
|
1308
|
-
console.log(
|
|
1477
|
+
console.log(chalk5.gray("\nPlease choose a different subdomain.\n"));
|
|
1309
1478
|
process.exit(1);
|
|
1310
1479
|
}
|
|
1311
1480
|
}
|
|
@@ -1314,17 +1483,17 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1314
1483
|
checkSpinner.warn("Could not verify subdomain availability");
|
|
1315
1484
|
}
|
|
1316
1485
|
if (!options.skipConfirm) {
|
|
1317
|
-
console.log(
|
|
1318
|
-
console.log(
|
|
1319
|
-
console.log(
|
|
1320
|
-
console.log(
|
|
1486
|
+
console.log(chalk5.cyan("\nDeployment Details:"));
|
|
1487
|
+
console.log(chalk5.gray(` Project: ${projectName}`));
|
|
1488
|
+
console.log(chalk5.gray(` Subdomain: ${subdomain}`));
|
|
1489
|
+
console.log(chalk5.gray(` URL: https://${subdomain}.leanmcp.dev
|
|
1321
1490
|
`));
|
|
1322
1491
|
const shouldDeploy = await confirm2({
|
|
1323
1492
|
message: "Proceed with deployment?",
|
|
1324
1493
|
default: true
|
|
1325
1494
|
});
|
|
1326
1495
|
if (!shouldDeploy) {
|
|
1327
|
-
console.log(
|
|
1496
|
+
console.log(chalk5.gray("\nDeployment cancelled.\n"));
|
|
1328
1497
|
return;
|
|
1329
1498
|
}
|
|
1330
1499
|
}
|
|
@@ -1332,9 +1501,9 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1332
1501
|
let projectId;
|
|
1333
1502
|
if (isUpdate && existingProject) {
|
|
1334
1503
|
projectId = existingProject.id;
|
|
1335
|
-
console.log(
|
|
1504
|
+
console.log(chalk5.gray(`Using existing project: ${projectId.substring(0, 8)}...`));
|
|
1336
1505
|
} else {
|
|
1337
|
-
const projectSpinner =
|
|
1506
|
+
const projectSpinner = ora5("Creating project...").start();
|
|
1338
1507
|
try {
|
|
1339
1508
|
debug2("Step 1: Creating project:", projectName);
|
|
1340
1509
|
const createResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.projects}`, {
|
|
@@ -1356,14 +1525,14 @@ Generated project name: ${chalk4.bold(projectName)}
|
|
|
1356
1525
|
projectSpinner.succeed(`Project created: ${projectId.substring(0, 8)}...`);
|
|
1357
1526
|
} catch (error) {
|
|
1358
1527
|
projectSpinner.fail("Failed to create project");
|
|
1359
|
-
console.error(
|
|
1528
|
+
console.error(chalk5.red(`
|
|
1360
1529
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1361
1530
|
process.exit(1);
|
|
1362
1531
|
}
|
|
1363
1532
|
}
|
|
1364
|
-
const uploadSpinner =
|
|
1533
|
+
const uploadSpinner = ora5("Packaging and uploading...").start();
|
|
1365
1534
|
try {
|
|
1366
|
-
const tempZip =
|
|
1535
|
+
const tempZip = path7.join(os2.tmpdir(), `leanmcp-${Date.now()}.zip`);
|
|
1367
1536
|
const zipSize = await createZipArchive(absolutePath, tempZip);
|
|
1368
1537
|
uploadSpinner.text = `Packaging... (${Math.round(zipSize / 1024)}KB)`;
|
|
1369
1538
|
debug2("Step 2a: Getting upload URL for project:", projectId);
|
|
@@ -1390,7 +1559,7 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1390
1559
|
throw new Error("Backend did not return upload URL");
|
|
1391
1560
|
}
|
|
1392
1561
|
debug2("Step 2b: Uploading to S3...");
|
|
1393
|
-
const zipBuffer = await
|
|
1562
|
+
const zipBuffer = await fs7.readFile(tempZip);
|
|
1394
1563
|
const s3Response = await fetch(uploadUrl, {
|
|
1395
1564
|
method: "PUT",
|
|
1396
1565
|
body: zipBuffer,
|
|
@@ -1413,15 +1582,15 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1413
1582
|
s3Location
|
|
1414
1583
|
})
|
|
1415
1584
|
});
|
|
1416
|
-
await
|
|
1585
|
+
await fs7.remove(tempZip);
|
|
1417
1586
|
uploadSpinner.succeed("Project uploaded");
|
|
1418
1587
|
} catch (error) {
|
|
1419
1588
|
uploadSpinner.fail("Failed to upload");
|
|
1420
|
-
console.error(
|
|
1589
|
+
console.error(chalk5.red(`
|
|
1421
1590
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1422
1591
|
process.exit(1);
|
|
1423
1592
|
}
|
|
1424
|
-
const buildSpinner =
|
|
1593
|
+
const buildSpinner = ora5("Building...").start();
|
|
1425
1594
|
const buildStartTime = Date.now();
|
|
1426
1595
|
let buildId;
|
|
1427
1596
|
let imageUri;
|
|
@@ -1445,11 +1614,11 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1445
1614
|
buildSpinner.succeed(`Build complete (${buildDuration}s)`);
|
|
1446
1615
|
} catch (error) {
|
|
1447
1616
|
buildSpinner.fail("Build failed");
|
|
1448
|
-
console.error(
|
|
1617
|
+
console.error(chalk5.red(`
|
|
1449
1618
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1450
1619
|
process.exit(1);
|
|
1451
1620
|
}
|
|
1452
|
-
const deploySpinner =
|
|
1621
|
+
const deploySpinner = ora5("Deploying to LeanMCP...").start();
|
|
1453
1622
|
let deploymentId;
|
|
1454
1623
|
let functionUrl;
|
|
1455
1624
|
try {
|
|
@@ -1475,11 +1644,11 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1475
1644
|
deploySpinner.succeed("Deployed");
|
|
1476
1645
|
} catch (error) {
|
|
1477
1646
|
deploySpinner.fail("Deployment failed");
|
|
1478
|
-
console.error(
|
|
1647
|
+
console.error(chalk5.red(`
|
|
1479
1648
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1480
1649
|
process.exit(1);
|
|
1481
1650
|
}
|
|
1482
|
-
const mappingSpinner =
|
|
1651
|
+
const mappingSpinner = ora5("Configuring subdomain...").start();
|
|
1483
1652
|
try {
|
|
1484
1653
|
debug2("Step 5: Creating subdomain mapping:", subdomain);
|
|
1485
1654
|
const mappingResponse = await debugFetch(`${apiUrl}${API_ENDPOINTS.createMapping}`, {
|
|
@@ -1503,38 +1672,43 @@ ${error instanceof Error ? error.message : String(error)}`));
|
|
|
1503
1672
|
} catch (error) {
|
|
1504
1673
|
mappingSpinner.warn("Subdomain mapping may need manual setup");
|
|
1505
1674
|
}
|
|
1506
|
-
console.log(
|
|
1507
|
-
console.log(
|
|
1508
|
-
console.log(
|
|
1509
|
-
console.log(
|
|
1510
|
-
console.log(
|
|
1675
|
+
console.log(chalk5.green("\n" + "=".repeat(60)));
|
|
1676
|
+
console.log(chalk5.green.bold(" DEPLOYMENT SUCCESSFUL!"));
|
|
1677
|
+
console.log(chalk5.green("=".repeat(60) + "\n"));
|
|
1678
|
+
console.log(chalk5.white(" Your MCP server is now live:\n"));
|
|
1679
|
+
console.log(chalk5.cyan(` URL: `) + chalk5.white.bold(`https://${subdomain}.leanmcp.dev`));
|
|
1511
1680
|
console.log();
|
|
1512
|
-
console.log(
|
|
1513
|
-
console.log(
|
|
1514
|
-
console.log(
|
|
1681
|
+
console.log(chalk5.gray(" Test endpoints:"));
|
|
1682
|
+
console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/health`));
|
|
1683
|
+
console.log(chalk5.gray(` curl https://${subdomain}.leanmcp.dev/mcp`));
|
|
1515
1684
|
console.log();
|
|
1516
1685
|
const totalDuration = Math.round((Date.now() - deployStartTime) / 1e3);
|
|
1517
|
-
console.log(
|
|
1686
|
+
console.log(chalk5.gray(` Total time: ${totalDuration}s`));
|
|
1687
|
+
console.log();
|
|
1688
|
+
const dashboardBaseUrl = "https://ship.leanmcp.com";
|
|
1689
|
+
console.log(chalk5.cyan(" Dashboard links:"));
|
|
1690
|
+
console.log(chalk5.gray(` Project: ${dashboardBaseUrl}/projects/${projectId}`));
|
|
1691
|
+
console.log(chalk5.gray(` Build: ${dashboardBaseUrl}/builds/${buildId}`));
|
|
1692
|
+
console.log(chalk5.gray(` Deployment: ${dashboardBaseUrl}/deployments/${deploymentId}`));
|
|
1518
1693
|
console.log();
|
|
1519
|
-
console.log(
|
|
1520
|
-
console.log(
|
|
1521
|
-
console.log(chalk4.gray(` Deployment ID: ${deploymentId}`));
|
|
1694
|
+
console.log(chalk5.cyan(" Need help? Join our Discord:"));
|
|
1695
|
+
console.log(chalk5.blue(" https://discord.com/invite/DsRcA3GwPy"));
|
|
1522
1696
|
console.log();
|
|
1523
1697
|
}
|
|
1524
1698
|
__name(deployCommand, "deployCommand");
|
|
1525
1699
|
|
|
1526
1700
|
// src/commands/projects.ts
|
|
1527
|
-
import
|
|
1528
|
-
import
|
|
1701
|
+
import chalk6 from "chalk";
|
|
1702
|
+
import ora6 from "ora";
|
|
1529
1703
|
var API_ENDPOINT = "/api/projects";
|
|
1530
1704
|
async function projectsListCommand() {
|
|
1531
1705
|
const apiKey = await getApiKey();
|
|
1532
1706
|
if (!apiKey) {
|
|
1533
|
-
console.error(
|
|
1534
|
-
console.log(
|
|
1707
|
+
console.error(chalk6.red("\nNot logged in."));
|
|
1708
|
+
console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1535
1709
|
process.exit(1);
|
|
1536
1710
|
}
|
|
1537
|
-
const spinner =
|
|
1711
|
+
const spinner = ora6("Fetching projects...").start();
|
|
1538
1712
|
try {
|
|
1539
1713
|
const apiUrl = await getApiUrl();
|
|
1540
1714
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}`, {
|
|
@@ -1548,25 +1722,25 @@ async function projectsListCommand() {
|
|
|
1548
1722
|
const projects = await response.json();
|
|
1549
1723
|
spinner.stop();
|
|
1550
1724
|
if (projects.length === 0) {
|
|
1551
|
-
console.log(
|
|
1552
|
-
console.log(
|
|
1725
|
+
console.log(chalk6.yellow("\nNo projects found."));
|
|
1726
|
+
console.log(chalk6.gray("Create one with: leanmcp deploy <folder>\n"));
|
|
1553
1727
|
return;
|
|
1554
1728
|
}
|
|
1555
|
-
console.log(
|
|
1729
|
+
console.log(chalk6.cyan(`
|
|
1556
1730
|
Your Projects (${projects.length})
|
|
1557
1731
|
`));
|
|
1558
|
-
console.log(
|
|
1732
|
+
console.log(chalk6.gray("\u2500".repeat(60)));
|
|
1559
1733
|
for (const project of projects) {
|
|
1560
|
-
const statusColor = project.status === "ACTIVE" ?
|
|
1561
|
-
console.log(
|
|
1562
|
-
console.log(
|
|
1563
|
-
console.log(
|
|
1564
|
-
console.log(
|
|
1734
|
+
const statusColor = project.status === "ACTIVE" ? chalk6.green : chalk6.yellow;
|
|
1735
|
+
console.log(chalk6.white.bold(` ${project.name}`));
|
|
1736
|
+
console.log(chalk6.gray(` ID: ${project.id}`));
|
|
1737
|
+
console.log(chalk6.gray(` Status: `) + statusColor(project.status));
|
|
1738
|
+
console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleDateString()}`));
|
|
1565
1739
|
console.log();
|
|
1566
1740
|
}
|
|
1567
1741
|
} catch (error) {
|
|
1568
1742
|
spinner.fail("Failed to fetch projects");
|
|
1569
|
-
console.error(
|
|
1743
|
+
console.error(chalk6.red(`
|
|
1570
1744
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1571
1745
|
process.exit(1);
|
|
1572
1746
|
}
|
|
@@ -1575,11 +1749,11 @@ __name(projectsListCommand, "projectsListCommand");
|
|
|
1575
1749
|
async function projectsGetCommand(projectId) {
|
|
1576
1750
|
const apiKey = await getApiKey();
|
|
1577
1751
|
if (!apiKey) {
|
|
1578
|
-
console.error(
|
|
1579
|
-
console.log(
|
|
1752
|
+
console.error(chalk6.red("\nNot logged in."));
|
|
1753
|
+
console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1580
1754
|
process.exit(1);
|
|
1581
1755
|
}
|
|
1582
|
-
const spinner =
|
|
1756
|
+
const spinner = ora6("Fetching project...").start();
|
|
1583
1757
|
try {
|
|
1584
1758
|
const apiUrl = await getApiUrl();
|
|
1585
1759
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
@@ -1595,22 +1769,22 @@ async function projectsGetCommand(projectId) {
|
|
|
1595
1769
|
}
|
|
1596
1770
|
const project = await response.json();
|
|
1597
1771
|
spinner.stop();
|
|
1598
|
-
console.log(
|
|
1599
|
-
console.log(
|
|
1600
|
-
console.log(
|
|
1601
|
-
console.log(
|
|
1602
|
-
console.log(
|
|
1772
|
+
console.log(chalk6.cyan("\nProject Details\n"));
|
|
1773
|
+
console.log(chalk6.gray("\u2500".repeat(60)));
|
|
1774
|
+
console.log(chalk6.white.bold(` Name: ${project.name}`));
|
|
1775
|
+
console.log(chalk6.gray(` ID: ${project.id}`));
|
|
1776
|
+
console.log(chalk6.gray(` Status: ${project.status}`));
|
|
1603
1777
|
if (project.s3Location) {
|
|
1604
|
-
console.log(
|
|
1778
|
+
console.log(chalk6.gray(` S3 Location: ${project.s3Location}`));
|
|
1605
1779
|
}
|
|
1606
|
-
console.log(
|
|
1780
|
+
console.log(chalk6.gray(` Created: ${new Date(project.createdAt).toLocaleString()}`));
|
|
1607
1781
|
if (project.updatedAt) {
|
|
1608
|
-
console.log(
|
|
1782
|
+
console.log(chalk6.gray(` Updated: ${new Date(project.updatedAt).toLocaleString()}`));
|
|
1609
1783
|
}
|
|
1610
1784
|
console.log();
|
|
1611
1785
|
} catch (error) {
|
|
1612
1786
|
spinner.fail("Failed to fetch project");
|
|
1613
|
-
console.error(
|
|
1787
|
+
console.error(chalk6.red(`
|
|
1614
1788
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1615
1789
|
process.exit(1);
|
|
1616
1790
|
}
|
|
@@ -1619,8 +1793,8 @@ __name(projectsGetCommand, "projectsGetCommand");
|
|
|
1619
1793
|
async function projectsDeleteCommand(projectId, options = {}) {
|
|
1620
1794
|
const apiKey = await getApiKey();
|
|
1621
1795
|
if (!apiKey) {
|
|
1622
|
-
console.error(
|
|
1623
|
-
console.log(
|
|
1796
|
+
console.error(chalk6.red("\nNot logged in."));
|
|
1797
|
+
console.log(chalk6.gray("Run `leanmcp login` first to authenticate.\n"));
|
|
1624
1798
|
process.exit(1);
|
|
1625
1799
|
}
|
|
1626
1800
|
if (!options.force) {
|
|
@@ -1630,11 +1804,11 @@ async function projectsDeleteCommand(projectId, options = {}) {
|
|
|
1630
1804
|
default: false
|
|
1631
1805
|
});
|
|
1632
1806
|
if (!shouldDelete) {
|
|
1633
|
-
console.log(
|
|
1807
|
+
console.log(chalk6.gray("\nDeletion cancelled.\n"));
|
|
1634
1808
|
return;
|
|
1635
1809
|
}
|
|
1636
1810
|
}
|
|
1637
|
-
const spinner =
|
|
1811
|
+
const spinner = ora6("Deleting project...").start();
|
|
1638
1812
|
try {
|
|
1639
1813
|
const apiUrl = await getApiUrl();
|
|
1640
1814
|
const response = await fetch(`${apiUrl}${API_ENDPOINT}/${projectId}`, {
|
|
@@ -1653,238 +1827,102 @@ async function projectsDeleteCommand(projectId, options = {}) {
|
|
|
1653
1827
|
console.log();
|
|
1654
1828
|
} catch (error) {
|
|
1655
1829
|
spinner.fail("Failed to delete project");
|
|
1656
|
-
console.error(
|
|
1830
|
+
console.error(chalk6.red(`
|
|
1657
1831
|
${error instanceof Error ? error.message : String(error)}`));
|
|
1658
1832
|
process.exit(1);
|
|
1659
1833
|
}
|
|
1660
1834
|
}
|
|
1661
1835
|
__name(projectsDeleteCommand, "projectsDeleteCommand");
|
|
1662
1836
|
|
|
1663
|
-
// src/
|
|
1664
|
-
var
|
|
1665
|
-
var pkg = require2("../package.json");
|
|
1666
|
-
function capitalize(str) {
|
|
1667
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1668
|
-
}
|
|
1669
|
-
__name(capitalize, "capitalize");
|
|
1670
|
-
var program = new Command();
|
|
1671
|
-
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
|
|
1672
|
-
Examples:
|
|
1673
|
-
$ leanmcp create my-app # Create new project (interactive)
|
|
1674
|
-
$ leanmcp create my-app --install # Create and install deps (non-interactive)
|
|
1675
|
-
$ leanmcp create my-app --no-install # Create without installing deps
|
|
1676
|
-
$ leanmcp dev # Start development server
|
|
1677
|
-
$ leanmcp login # Authenticate with LeanMCP cloud
|
|
1678
|
-
$ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
|
|
1679
|
-
$ leanmcp projects list # List your cloud projects
|
|
1680
|
-
$ leanmcp projects delete <id> # Delete a cloud project
|
|
1681
|
-
`);
|
|
1682
|
-
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) => {
|
|
1683
|
-
const spinner = ora6(`Creating project ${projectName}...`).start();
|
|
1684
|
-
const targetDir = path7.join(process.cwd(), projectName);
|
|
1685
|
-
if (fs7.existsSync(targetDir)) {
|
|
1686
|
-
spinner.fail(`Folder ${projectName} already exists.`);
|
|
1687
|
-
process.exit(1);
|
|
1688
|
-
}
|
|
1689
|
-
await fs7.mkdirp(targetDir);
|
|
1690
|
-
await fs7.mkdirp(path7.join(targetDir, "mcp", "example"));
|
|
1691
|
-
const pkg2 = {
|
|
1692
|
-
name: projectName,
|
|
1693
|
-
version: "1.0.0",
|
|
1694
|
-
description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
|
|
1695
|
-
main: "dist/main.js",
|
|
1696
|
-
type: "module",
|
|
1697
|
-
scripts: {
|
|
1698
|
-
dev: "tsx watch main.ts",
|
|
1699
|
-
build: "tsc",
|
|
1700
|
-
start: "node dist/main.js",
|
|
1701
|
-
clean: "rm -rf dist"
|
|
1702
|
-
},
|
|
1703
|
-
keywords: [
|
|
1704
|
-
"mcp",
|
|
1705
|
-
"model-context-protocol",
|
|
1706
|
-
"streamable-http",
|
|
1707
|
-
"leanmcp"
|
|
1708
|
-
],
|
|
1709
|
-
author: "",
|
|
1710
|
-
license: "MIT",
|
|
1711
|
-
dependencies: {
|
|
1712
|
-
"@leanmcp/core": "^0.3.5",
|
|
1713
|
-
"dotenv": "^16.5.0"
|
|
1714
|
-
},
|
|
1715
|
-
devDependencies: {
|
|
1716
|
-
"@types/node": "^20.0.0",
|
|
1717
|
-
"tsx": "^4.20.3",
|
|
1718
|
-
"typescript": "^5.6.3"
|
|
1719
|
-
}
|
|
1720
|
-
};
|
|
1721
|
-
await fs7.writeJSON(path7.join(targetDir, "package.json"), pkg2, {
|
|
1722
|
-
spaces: 2
|
|
1723
|
-
});
|
|
1724
|
-
const tsconfig = {
|
|
1725
|
-
compilerOptions: {
|
|
1726
|
-
module: "ESNext",
|
|
1727
|
-
target: "ES2022",
|
|
1728
|
-
moduleResolution: "Node",
|
|
1729
|
-
esModuleInterop: true,
|
|
1730
|
-
strict: true,
|
|
1731
|
-
skipLibCheck: true,
|
|
1732
|
-
outDir: "dist",
|
|
1733
|
-
experimentalDecorators: true,
|
|
1734
|
-
emitDecoratorMetadata: true
|
|
1735
|
-
},
|
|
1736
|
-
include: [
|
|
1737
|
-
"**/*.ts"
|
|
1738
|
-
],
|
|
1739
|
-
exclude: [
|
|
1740
|
-
"node_modules",
|
|
1741
|
-
"dist"
|
|
1742
|
-
]
|
|
1743
|
-
};
|
|
1744
|
-
await fs7.writeJSON(path7.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
1745
|
-
spaces: 2
|
|
1746
|
-
});
|
|
1747
|
-
const dashboardLine = options.dashboard === false ? `
|
|
1748
|
-
dashboard: false, // Dashboard disabled via --no-dashboard` : "";
|
|
1749
|
-
const mainTs = `import dotenv from "dotenv";
|
|
1750
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
1837
|
+
// src/templates/readme_v1.ts
|
|
1838
|
+
var getReadmeTemplate = /* @__PURE__ */ __name((projectName) => `# ${projectName}
|
|
1751
1839
|
|
|
1752
|
-
|
|
1753
|
-
dotenv.config();
|
|
1840
|
+
MCP Server with Streamable HTTP Transport built with LeanMCP SDK
|
|
1754
1841
|
|
|
1755
|
-
|
|
1756
|
-
await createHTTPServer({
|
|
1757
|
-
name: "${projectName}",
|
|
1758
|
-
version: "1.0.0",
|
|
1759
|
-
port: 3001,
|
|
1760
|
-
cors: true,
|
|
1761
|
-
logging: true${dashboardLine}
|
|
1762
|
-
});
|
|
1842
|
+
## Quick Start
|
|
1763
1843
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
1844
|
+
\`\`\`bash
|
|
1845
|
+
# Install dependencies
|
|
1846
|
+
npm install
|
|
1768
1847
|
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
*
|
|
1772
|
-
* This is a simple example to get you started. Add your own tools, resources, and prompts here!
|
|
1773
|
-
*/
|
|
1848
|
+
# Start development server (hot reload)
|
|
1849
|
+
npm run dev
|
|
1774
1850
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
@SchemaConstraint({ description: "First number" })
|
|
1778
|
-
a!: number;
|
|
1851
|
+
# Build for production
|
|
1852
|
+
npm run build
|
|
1779
1853
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1854
|
+
# Run production server
|
|
1855
|
+
npm start
|
|
1856
|
+
\`\`\`
|
|
1782
1857
|
|
|
1783
|
-
|
|
1784
|
-
@SchemaConstraint({
|
|
1785
|
-
description: "Operation to perform",
|
|
1786
|
-
enum: ["add", "subtract", "multiply", "divide"],
|
|
1787
|
-
default: "add"
|
|
1788
|
-
})
|
|
1789
|
-
operation?: string;
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
class EchoInput {
|
|
1793
|
-
@SchemaConstraint({
|
|
1794
|
-
description: "Message to echo back",
|
|
1795
|
-
minLength: 1
|
|
1796
|
-
})
|
|
1797
|
-
message!: string;
|
|
1798
|
-
}
|
|
1858
|
+
## Project Structure
|
|
1799
1859
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
let result: number;
|
|
1860
|
+
\`\`\`
|
|
1861
|
+
${projectName}/
|
|
1862
|
+
\u251C\u2500\u2500 main.ts # Server entry point
|
|
1863
|
+
\u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
|
|
1864
|
+
\u2502 \u2514\u2500\u2500 example/
|
|
1865
|
+
\u2502 \u2514\u2500\u2500 index.ts # Example service
|
|
1866
|
+
\u251C\u2500\u2500 .env # Environment variables
|
|
1867
|
+
\u2514\u2500\u2500 package.json
|
|
1868
|
+
\`\`\`
|
|
1810
1869
|
|
|
1811
|
-
|
|
1812
|
-
case "add":
|
|
1813
|
-
result = a + b;
|
|
1814
|
-
break;
|
|
1815
|
-
case "subtract":
|
|
1816
|
-
result = a - b;
|
|
1817
|
-
break;
|
|
1818
|
-
case "multiply":
|
|
1819
|
-
result = a * b;
|
|
1820
|
-
break;
|
|
1821
|
-
case "divide":
|
|
1822
|
-
if (b === 0) throw new Error("Cannot divide by zero");
|
|
1823
|
-
result = a / b;
|
|
1824
|
-
break;
|
|
1825
|
-
default:
|
|
1826
|
-
throw new Error("Invalid operation");
|
|
1827
|
-
}
|
|
1870
|
+
## Adding New Services
|
|
1828
1871
|
|
|
1829
|
-
|
|
1830
|
-
content: [{
|
|
1831
|
-
type: "text" as const,
|
|
1832
|
-
text: JSON.stringify({
|
|
1833
|
-
operation: input.operation || "add",
|
|
1834
|
-
operands: { a: input.a, b: input.b },
|
|
1835
|
-
result
|
|
1836
|
-
}, null, 2)
|
|
1837
|
-
}]
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1872
|
+
Create a new service directory in \`mcp/\`:
|
|
1840
1873
|
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
})
|
|
1845
|
-
async echo(input: EchoInput) {
|
|
1846
|
-
return {
|
|
1847
|
-
content: [{
|
|
1848
|
-
type: "text" as const,
|
|
1849
|
-
text: JSON.stringify({
|
|
1850
|
-
echoed: input.message,
|
|
1851
|
-
timestamp: new Date().toISOString()
|
|
1852
|
-
}, null, 2)
|
|
1853
|
-
}]
|
|
1854
|
-
};
|
|
1855
|
-
}
|
|
1874
|
+
\`\`\`typescript
|
|
1875
|
+
// mcp/myservice/index.ts
|
|
1876
|
+
import { Tool, SchemaConstraint } from "@leanmcp/core";
|
|
1856
1877
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
version: "1.0.0",
|
|
1866
|
-
uptime: process.uptime()
|
|
1867
|
-
}, null, 2)
|
|
1868
|
-
}]
|
|
1869
|
-
};
|
|
1870
|
-
}
|
|
1878
|
+
// Define input schema
|
|
1879
|
+
class MyToolInput {
|
|
1880
|
+
@SchemaConstraint({
|
|
1881
|
+
description: "Message to process",
|
|
1882
|
+
minLength: 1
|
|
1883
|
+
})
|
|
1884
|
+
message!: string;
|
|
1885
|
+
}
|
|
1871
1886
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1887
|
+
export class MyService {
|
|
1888
|
+
@Tool({
|
|
1889
|
+
description: "My awesome tool",
|
|
1890
|
+
inputClass: MyToolInput
|
|
1891
|
+
})
|
|
1892
|
+
async myTool(input: MyToolInput) {
|
|
1893
|
+
return {
|
|
1894
|
+
content: [{
|
|
1895
|
+
type: "text",
|
|
1896
|
+
text: \`You said: \${input.message}\`
|
|
1881
1897
|
}]
|
|
1882
1898
|
};
|
|
1883
1899
|
}
|
|
1884
1900
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1901
|
+
\`\`\`
|
|
1902
|
+
|
|
1903
|
+
Services are automatically discovered and registered - no need to modify \`main.ts\`!
|
|
1904
|
+
|
|
1905
|
+
## Features
|
|
1906
|
+
|
|
1907
|
+
- **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
|
|
1908
|
+
- **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
|
|
1909
|
+
- **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
|
|
1910
|
+
- **HTTP transport** - Production-ready HTTP server with session management
|
|
1911
|
+
- **Hot reload** - Development mode with automatic restart on file changes
|
|
1912
|
+
|
|
1913
|
+
## Testing with MCP Inspector
|
|
1914
|
+
|
|
1915
|
+
\`\`\`bash
|
|
1916
|
+
npx @modelcontextprotocol/inspector http://localhost:3001/mcp
|
|
1917
|
+
\`\`\`
|
|
1918
|
+
|
|
1919
|
+
## License
|
|
1920
|
+
|
|
1921
|
+
MIT
|
|
1922
|
+
`, "getReadmeTemplate");
|
|
1923
|
+
|
|
1924
|
+
// src/templates/gitignore_v1.ts
|
|
1925
|
+
var gitignoreTemplate = `# Logs
|
|
1888
1926
|
logs
|
|
1889
1927
|
*.log
|
|
1890
1928
|
npm-debug.log*
|
|
@@ -2026,111 +2064,333 @@ vite.config.js.timestamp-*
|
|
|
2026
2064
|
vite.config.ts.timestamp-*
|
|
2027
2065
|
.vite/
|
|
2028
2066
|
`;
|
|
2029
|
-
const env = `# Server Configuration
|
|
2030
|
-
PORT=3001
|
|
2031
|
-
NODE_ENV=development
|
|
2032
2067
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
await fs7.writeFile(path7.join(targetDir, ".gitignore"), gitignore);
|
|
2036
|
-
await fs7.writeFile(path7.join(targetDir, ".env"), env);
|
|
2037
|
-
const readme = `# ${projectName}
|
|
2068
|
+
// src/templates/example_service_v1.ts
|
|
2069
|
+
var getExampleServiceTemplate = /* @__PURE__ */ __name((projectName) => `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
2038
2070
|
|
|
2039
|
-
|
|
2071
|
+
/**
|
|
2072
|
+
* Example service demonstrating LeanMCP SDK decorators
|
|
2073
|
+
*
|
|
2074
|
+
* This is a simple example to get you started. Add your own tools, resources, and prompts here!
|
|
2075
|
+
*/
|
|
2040
2076
|
|
|
2041
|
-
|
|
2077
|
+
// Input schema with validation decorators
|
|
2078
|
+
class CalculateInput {
|
|
2079
|
+
@SchemaConstraint({ description: "First number" })
|
|
2080
|
+
a!: number;
|
|
2042
2081
|
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
npm install
|
|
2082
|
+
@SchemaConstraint({ description: "Second number" })
|
|
2083
|
+
b!: number;
|
|
2046
2084
|
|
|
2047
|
-
|
|
2048
|
-
|
|
2085
|
+
@Optional()
|
|
2086
|
+
@SchemaConstraint({
|
|
2087
|
+
description: "Operation to perform",
|
|
2088
|
+
enum: ["add", "subtract", "multiply", "divide"],
|
|
2089
|
+
default: "add"
|
|
2090
|
+
})
|
|
2091
|
+
operation?: string;
|
|
2092
|
+
}
|
|
2049
2093
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2094
|
+
class EchoInput {
|
|
2095
|
+
@SchemaConstraint({
|
|
2096
|
+
description: "Message to echo back",
|
|
2097
|
+
minLength: 1
|
|
2098
|
+
})
|
|
2099
|
+
message!: string;
|
|
2100
|
+
}
|
|
2052
2101
|
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2102
|
+
export class ExampleService {
|
|
2103
|
+
@Tool({
|
|
2104
|
+
description: "Perform arithmetic operations with automatic schema validation",
|
|
2105
|
+
inputClass: CalculateInput
|
|
2106
|
+
})
|
|
2107
|
+
async calculate(input: CalculateInput) {
|
|
2108
|
+
// Ensure numerical operations by explicitly converting to numbers
|
|
2109
|
+
const a = Number(input.a);
|
|
2110
|
+
const b = Number(input.b);
|
|
2111
|
+
let result: number;
|
|
2056
2112
|
|
|
2057
|
-
|
|
2113
|
+
switch (input.operation || "add") {
|
|
2114
|
+
case "add":
|
|
2115
|
+
result = a + b;
|
|
2116
|
+
break;
|
|
2117
|
+
case "subtract":
|
|
2118
|
+
result = a - b;
|
|
2119
|
+
break;
|
|
2120
|
+
case "multiply":
|
|
2121
|
+
result = a * b;
|
|
2122
|
+
break;
|
|
2123
|
+
case "divide":
|
|
2124
|
+
if (b === 0) throw new Error("Cannot divide by zero");
|
|
2125
|
+
result = a / b;
|
|
2126
|
+
break;
|
|
2127
|
+
default:
|
|
2128
|
+
throw new Error("Invalid operation");
|
|
2129
|
+
}
|
|
2058
2130
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2131
|
+
return {
|
|
2132
|
+
content: [{
|
|
2133
|
+
type: "text" as const,
|
|
2134
|
+
text: JSON.stringify({
|
|
2135
|
+
operation: input.operation || "add",
|
|
2136
|
+
operands: { a: input.a, b: input.b },
|
|
2137
|
+
result
|
|
2138
|
+
}, null, 2)
|
|
2139
|
+
}]
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2068
2142
|
|
|
2069
|
-
|
|
2143
|
+
@Tool({
|
|
2144
|
+
description: "Echo a message back",
|
|
2145
|
+
inputClass: EchoInput
|
|
2146
|
+
})
|
|
2147
|
+
async echo(input: EchoInput) {
|
|
2148
|
+
return {
|
|
2149
|
+
content: [{
|
|
2150
|
+
type: "text" as const,
|
|
2151
|
+
text: JSON.stringify({
|
|
2152
|
+
echoed: input.message,
|
|
2153
|
+
timestamp: new Date().toISOString()
|
|
2154
|
+
}, null, 2)
|
|
2155
|
+
}]
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2070
2158
|
|
|
2071
|
-
|
|
2159
|
+
@Resource({ description: "Get server information" })
|
|
2160
|
+
async serverInfo() {
|
|
2161
|
+
return {
|
|
2162
|
+
contents: [{
|
|
2163
|
+
uri: "server://info",
|
|
2164
|
+
mimeType: "application/json",
|
|
2165
|
+
text: JSON.stringify({
|
|
2166
|
+
name: "${projectName}",
|
|
2167
|
+
version: "1.0.0",
|
|
2168
|
+
uptime: process.uptime()
|
|
2169
|
+
}, null, 2)
|
|
2170
|
+
}]
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2072
2173
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2174
|
+
@Prompt({ description: "Generate a greeting prompt" })
|
|
2175
|
+
async greeting(args: { name?: string }) {
|
|
2176
|
+
return {
|
|
2177
|
+
messages: [{
|
|
2178
|
+
role: "user" as const,
|
|
2179
|
+
content: {
|
|
2180
|
+
type: "text" as const,
|
|
2181
|
+
text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
|
|
2182
|
+
}
|
|
2183
|
+
}]
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
`, "getExampleServiceTemplate");
|
|
2076
2188
|
|
|
2077
|
-
//
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2189
|
+
// src/templates/main_ts_v1.ts
|
|
2190
|
+
var getMainTsTemplate = /* @__PURE__ */ __name((projectName, dashboardLine) => `import dotenv from "dotenv";
|
|
2191
|
+
import { createHTTPServer } from "@leanmcp/core";
|
|
2192
|
+
|
|
2193
|
+
// Load environment variables
|
|
2194
|
+
dotenv.config();
|
|
2195
|
+
|
|
2196
|
+
// Services are automatically discovered from ./mcp directory
|
|
2197
|
+
await createHTTPServer({
|
|
2198
|
+
name: "${projectName}",
|
|
2199
|
+
version: "1.0.0",
|
|
2200
|
+
port: 3001,
|
|
2201
|
+
cors: true,
|
|
2202
|
+
logging: true${dashboardLine}
|
|
2203
|
+
});
|
|
2204
|
+
|
|
2205
|
+
console.log("\\n${projectName} MCP Server");
|
|
2206
|
+
`, "getMainTsTemplate");
|
|
2207
|
+
|
|
2208
|
+
// src/templates/service_index_v1.ts
|
|
2209
|
+
var getServiceIndexTemplate = /* @__PURE__ */ __name((serviceName, capitalizedName) => `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
|
|
2210
|
+
|
|
2211
|
+
// Input schema for greeting
|
|
2212
|
+
class GreetInput {
|
|
2213
|
+
@SchemaConstraint({
|
|
2214
|
+
description: "Name to greet",
|
|
2081
2215
|
minLength: 1
|
|
2082
2216
|
})
|
|
2083
|
-
|
|
2217
|
+
name!: string;
|
|
2084
2218
|
}
|
|
2085
2219
|
|
|
2086
|
-
|
|
2220
|
+
/**
|
|
2221
|
+
* ${capitalizedName} Service
|
|
2222
|
+
*
|
|
2223
|
+
* This service demonstrates the three types of MCP primitives:
|
|
2224
|
+
* - Tools: Callable functions (like API endpoints)
|
|
2225
|
+
* - Prompts: Reusable prompt templates
|
|
2226
|
+
* - Resources: Data sources/endpoints
|
|
2227
|
+
*/
|
|
2228
|
+
export class ${capitalizedName}Service {
|
|
2229
|
+
// TOOL - Callable function
|
|
2230
|
+
// Tool name: "greet" (from function name)
|
|
2087
2231
|
@Tool({
|
|
2088
|
-
description: "
|
|
2089
|
-
inputClass:
|
|
2232
|
+
description: "Greet a user by name",
|
|
2233
|
+
inputClass: GreetInput
|
|
2090
2234
|
})
|
|
2091
|
-
|
|
2235
|
+
greet(args: GreetInput) {
|
|
2236
|
+
return { message: \`Hello, \${args.name}! from ${serviceName}\` };
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
// PROMPT - Prompt template
|
|
2240
|
+
// Prompt name: "welcomePrompt" (from function name)
|
|
2241
|
+
@Prompt({ description: "Welcome message prompt template" })
|
|
2242
|
+
welcomePrompt(args: { userName?: string }) {
|
|
2092
2243
|
return {
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2244
|
+
messages: [
|
|
2245
|
+
{
|
|
2246
|
+
role: "user",
|
|
2247
|
+
content: {
|
|
2248
|
+
type: "text",
|
|
2249
|
+
text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
]
|
|
2097
2253
|
};
|
|
2098
2254
|
}
|
|
2099
|
-
}
|
|
2100
|
-
\`\`\`
|
|
2101
|
-
|
|
2102
|
-
Services are automatically discovered and registered - no need to modify \`main.ts\`!
|
|
2103
|
-
|
|
2104
|
-
## Features
|
|
2105
2255
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2256
|
+
// RESOURCE - Data endpoint
|
|
2257
|
+
// Resource URI auto-generated from class and method name
|
|
2258
|
+
@Resource({ description: "${capitalizedName} service status" })
|
|
2259
|
+
getStatus() {
|
|
2260
|
+
return {
|
|
2261
|
+
service: "${serviceName}",
|
|
2262
|
+
status: "active",
|
|
2263
|
+
timestamp: new Date().toISOString()
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
`, "getServiceIndexTemplate");
|
|
2117
2268
|
|
|
2118
|
-
|
|
2269
|
+
// src/index.ts
|
|
2270
|
+
var require2 = createRequire(import.meta.url);
|
|
2271
|
+
var pkg = require2("../package.json");
|
|
2272
|
+
function capitalize(str) {
|
|
2273
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2274
|
+
}
|
|
2275
|
+
__name(capitalize, "capitalize");
|
|
2276
|
+
var program = new Command();
|
|
2277
|
+
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
|
|
2278
|
+
Examples:
|
|
2279
|
+
$ leanmcp create my-app # Create new project (interactive)
|
|
2280
|
+
$ leanmcp create my-app --install # Create and install deps (non-interactive)
|
|
2281
|
+
$ leanmcp create my-app --no-install # Create without installing deps
|
|
2282
|
+
$ leanmcp dev # Start development server
|
|
2283
|
+
$ leanmcp build # Build UI components and compile TypeScript
|
|
2284
|
+
$ leanmcp start # Build and start production server
|
|
2285
|
+
$ leanmcp login # Authenticate with LeanMCP cloud
|
|
2286
|
+
$ leanmcp deploy ./my-app # Deploy to LeanMCP cloud
|
|
2287
|
+
$ leanmcp projects list # List your cloud projects
|
|
2288
|
+
$ leanmcp projects delete <id> # Delete a cloud project
|
|
2289
|
+
`);
|
|
2290
|
+
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) => {
|
|
2291
|
+
const spinner = ora7(`Creating project ${projectName}...`).start();
|
|
2292
|
+
const targetDir = path8.join(process.cwd(), projectName);
|
|
2293
|
+
if (fs8.existsSync(targetDir)) {
|
|
2294
|
+
spinner.fail(`Folder ${projectName} already exists.`);
|
|
2295
|
+
process.exit(1);
|
|
2296
|
+
}
|
|
2297
|
+
await fs8.mkdirp(targetDir);
|
|
2298
|
+
await fs8.mkdirp(path8.join(targetDir, "mcp", "example"));
|
|
2299
|
+
const pkg2 = {
|
|
2300
|
+
name: projectName,
|
|
2301
|
+
version: "1.0.0",
|
|
2302
|
+
description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
|
|
2303
|
+
main: "dist/main.js",
|
|
2304
|
+
type: "module",
|
|
2305
|
+
scripts: {
|
|
2306
|
+
dev: "leanmcp dev",
|
|
2307
|
+
build: "leanmcp build",
|
|
2308
|
+
start: "leanmcp start",
|
|
2309
|
+
"start:node": "node dist/main.js",
|
|
2310
|
+
clean: "rm -rf dist"
|
|
2311
|
+
},
|
|
2312
|
+
keywords: [
|
|
2313
|
+
"mcp",
|
|
2314
|
+
"model-context-protocol",
|
|
2315
|
+
"streamable-http",
|
|
2316
|
+
"leanmcp"
|
|
2317
|
+
],
|
|
2318
|
+
author: "",
|
|
2319
|
+
license: "MIT",
|
|
2320
|
+
dependencies: {
|
|
2321
|
+
"@leanmcp/core": "^0.3.9",
|
|
2322
|
+
"@leanmcp/ui": "^0.2.1",
|
|
2323
|
+
"@leanmcp/auth": "^0.3.2",
|
|
2324
|
+
"dotenv": "^16.5.0"
|
|
2325
|
+
},
|
|
2326
|
+
devDependencies: {
|
|
2327
|
+
"@leanmcp/cli": "^0.3.0",
|
|
2328
|
+
"@types/node": "^20.0.0",
|
|
2329
|
+
"tsx": "^4.20.3",
|
|
2330
|
+
"typescript": "^5.6.3"
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
await fs8.writeJSON(path8.join(targetDir, "package.json"), pkg2, {
|
|
2334
|
+
spaces: 2
|
|
2335
|
+
});
|
|
2336
|
+
const tsconfig = {
|
|
2337
|
+
compilerOptions: {
|
|
2338
|
+
module: "ESNext",
|
|
2339
|
+
target: "ES2022",
|
|
2340
|
+
moduleResolution: "Node",
|
|
2341
|
+
esModuleInterop: true,
|
|
2342
|
+
strict: true,
|
|
2343
|
+
skipLibCheck: true,
|
|
2344
|
+
outDir: "dist",
|
|
2345
|
+
experimentalDecorators: true,
|
|
2346
|
+
emitDecoratorMetadata: true
|
|
2347
|
+
},
|
|
2348
|
+
include: [
|
|
2349
|
+
"**/*.ts"
|
|
2350
|
+
],
|
|
2351
|
+
exclude: [
|
|
2352
|
+
"node_modules",
|
|
2353
|
+
"dist"
|
|
2354
|
+
]
|
|
2355
|
+
};
|
|
2356
|
+
await fs8.writeJSON(path8.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
2357
|
+
spaces: 2
|
|
2358
|
+
});
|
|
2359
|
+
const dashboardLine = options.dashboard === false ? `
|
|
2360
|
+
dashboard: false, // Dashboard disabled via --no-dashboard` : "";
|
|
2361
|
+
const mainTs = getMainTsTemplate(projectName, dashboardLine);
|
|
2362
|
+
await fs8.writeFile(path8.join(targetDir, "main.ts"), mainTs);
|
|
2363
|
+
const exampleServiceTs = getExampleServiceTemplate(projectName);
|
|
2364
|
+
await fs8.writeFile(path8.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
|
|
2365
|
+
const gitignore = gitignoreTemplate;
|
|
2366
|
+
const env = `# Server Configuration
|
|
2367
|
+
PORT=3001
|
|
2368
|
+
NODE_ENV=development
|
|
2119
2369
|
|
|
2120
|
-
|
|
2370
|
+
# Add your environment variables here
|
|
2121
2371
|
`;
|
|
2122
|
-
await
|
|
2372
|
+
await fs8.writeFile(path8.join(targetDir, ".gitignore"), gitignore);
|
|
2373
|
+
await fs8.writeFile(path8.join(targetDir, ".env"), env);
|
|
2374
|
+
const readme = getReadmeTemplate(projectName);
|
|
2375
|
+
await fs8.writeFile(path8.join(targetDir, "README.md"), readme);
|
|
2123
2376
|
spinner.succeed(`Project ${projectName} created!`);
|
|
2124
|
-
console.log(
|
|
2125
|
-
console.log(
|
|
2126
|
-
cd ${projectName}
|
|
2377
|
+
console.log(chalk7.green("\nSuccess! Your MCP server is ready.\n"));
|
|
2378
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2379
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2380
|
+
console.log(chalk7.gray(` leanmcp deploy .
|
|
2127
2381
|
`));
|
|
2382
|
+
console.log(chalk7.cyan("Need help? Join our Discord:"));
|
|
2383
|
+
console.log(chalk7.blue(" https://discord.com/invite/DsRcA3GwPy\n"));
|
|
2128
2384
|
const isNonInteractive = options.install !== void 0 || options.allowAll;
|
|
2129
2385
|
if (options.install === false) {
|
|
2130
|
-
console.log(
|
|
2131
|
-
console.log(
|
|
2132
|
-
console.log(
|
|
2133
|
-
console.log(
|
|
2386
|
+
console.log(chalk7.cyan("To get started:"));
|
|
2387
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2388
|
+
console.log(chalk7.gray(` npm install`));
|
|
2389
|
+
console.log(chalk7.gray(` npm run dev`));
|
|
2390
|
+
console.log();
|
|
2391
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2392
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2393
|
+
console.log(chalk7.gray(` leanmcp deploy .`));
|
|
2134
2394
|
return;
|
|
2135
2395
|
}
|
|
2136
2396
|
const shouldInstall = isNonInteractive ? true : await confirm3({
|
|
@@ -2138,10 +2398,10 @@ MIT
|
|
|
2138
2398
|
default: true
|
|
2139
2399
|
});
|
|
2140
2400
|
if (shouldInstall) {
|
|
2141
|
-
const installSpinner =
|
|
2401
|
+
const installSpinner = ora7("Installing dependencies...").start();
|
|
2142
2402
|
try {
|
|
2143
2403
|
await new Promise((resolve, reject) => {
|
|
2144
|
-
const npmInstall =
|
|
2404
|
+
const npmInstall = spawn4("npm", [
|
|
2145
2405
|
"install"
|
|
2146
2406
|
], {
|
|
2147
2407
|
cwd: targetDir,
|
|
@@ -2159,9 +2419,13 @@ MIT
|
|
|
2159
2419
|
});
|
|
2160
2420
|
installSpinner.succeed("Dependencies installed successfully!");
|
|
2161
2421
|
if (options.install === true) {
|
|
2162
|
-
console.log(
|
|
2163
|
-
console.log(
|
|
2164
|
-
console.log(
|
|
2422
|
+
console.log(chalk7.cyan("\nTo start the development server:"));
|
|
2423
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2424
|
+
console.log(chalk7.gray(` npm run dev`));
|
|
2425
|
+
console.log();
|
|
2426
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2427
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2428
|
+
console.log(chalk7.gray(` leanmcp deploy .`));
|
|
2165
2429
|
return;
|
|
2166
2430
|
}
|
|
2167
2431
|
const shouldStartDev = options.allowAll ? true : await confirm3({
|
|
@@ -2169,8 +2433,8 @@ MIT
|
|
|
2169
2433
|
default: true
|
|
2170
2434
|
});
|
|
2171
2435
|
if (shouldStartDev) {
|
|
2172
|
-
console.log(
|
|
2173
|
-
const devServer =
|
|
2436
|
+
console.log(chalk7.cyan("\nStarting development server...\n"));
|
|
2437
|
+
const devServer = spawn4("npm", [
|
|
2174
2438
|
"run",
|
|
2175
2439
|
"dev"
|
|
2176
2440
|
], {
|
|
@@ -2183,106 +2447,57 @@ MIT
|
|
|
2183
2447
|
process.exit(0);
|
|
2184
2448
|
});
|
|
2185
2449
|
} else {
|
|
2186
|
-
console.log(
|
|
2187
|
-
console.log(
|
|
2188
|
-
console.log(
|
|
2450
|
+
console.log(chalk7.cyan("\nTo start the development server later:"));
|
|
2451
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2452
|
+
console.log(chalk7.gray(` npm run dev`));
|
|
2453
|
+
console.log();
|
|
2454
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2455
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2456
|
+
console.log(chalk7.gray(` leanmcp deploy .`));
|
|
2189
2457
|
}
|
|
2190
2458
|
} catch (error) {
|
|
2191
2459
|
installSpinner.fail("Failed to install dependencies");
|
|
2192
|
-
console.error(
|
|
2193
|
-
console.log(
|
|
2194
|
-
console.log(
|
|
2195
|
-
console.log(
|
|
2460
|
+
console.error(chalk7.red(error instanceof Error ? error.message : String(error)));
|
|
2461
|
+
console.log(chalk7.cyan("\nYou can install dependencies manually:"));
|
|
2462
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2463
|
+
console.log(chalk7.gray(` npm install`));
|
|
2196
2464
|
}
|
|
2197
2465
|
} else {
|
|
2198
|
-
console.log(
|
|
2199
|
-
console.log(
|
|
2200
|
-
console.log(
|
|
2201
|
-
console.log(
|
|
2466
|
+
console.log(chalk7.cyan("\nTo get started:"));
|
|
2467
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2468
|
+
console.log(chalk7.gray(` npm install`));
|
|
2469
|
+
console.log(chalk7.gray(` npm run dev`));
|
|
2470
|
+
console.log();
|
|
2471
|
+
console.log(chalk7.cyan("To deploy to LeanMCP cloud:"));
|
|
2472
|
+
console.log(chalk7.gray(` cd ${projectName}`));
|
|
2473
|
+
console.log(chalk7.gray(` leanmcp deploy .`));
|
|
2202
2474
|
}
|
|
2203
2475
|
});
|
|
2204
2476
|
program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
|
|
2205
2477
|
const cwd = process.cwd();
|
|
2206
|
-
const mcpDir =
|
|
2207
|
-
if (!
|
|
2208
|
-
console.error(
|
|
2478
|
+
const mcpDir = path8.join(cwd, "mcp");
|
|
2479
|
+
if (!fs8.existsSync(path8.join(cwd, "main.ts"))) {
|
|
2480
|
+
console.error(chalk7.red("ERROR: Not a LeanMCP project (main.ts missing)."));
|
|
2209
2481
|
process.exit(1);
|
|
2210
2482
|
}
|
|
2211
|
-
const serviceDir =
|
|
2212
|
-
const serviceFile =
|
|
2213
|
-
if (
|
|
2214
|
-
console.error(
|
|
2483
|
+
const serviceDir = path8.join(mcpDir, serviceName);
|
|
2484
|
+
const serviceFile = path8.join(serviceDir, "index.ts");
|
|
2485
|
+
if (fs8.existsSync(serviceDir)) {
|
|
2486
|
+
console.error(chalk7.red(`ERROR: Service ${serviceName} already exists.`));
|
|
2215
2487
|
process.exit(1);
|
|
2216
2488
|
}
|
|
2217
|
-
await
|
|
2218
|
-
const indexTs =
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
name!: string;
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
/**
|
|
2230
|
-
* ${capitalize(serviceName)} Service
|
|
2231
|
-
*
|
|
2232
|
-
* This service demonstrates the three types of MCP primitives:
|
|
2233
|
-
* - Tools: Callable functions (like API endpoints)
|
|
2234
|
-
* - Prompts: Reusable prompt templates
|
|
2235
|
-
* - Resources: Data sources/endpoints
|
|
2236
|
-
*/
|
|
2237
|
-
export class ${capitalize(serviceName)}Service {
|
|
2238
|
-
// TOOL - Callable function
|
|
2239
|
-
// Tool name: "greet" (from function name)
|
|
2240
|
-
@Tool({
|
|
2241
|
-
description: "Greet a user by name",
|
|
2242
|
-
inputClass: GreetInput
|
|
2243
|
-
})
|
|
2244
|
-
greet(args: GreetInput) {
|
|
2245
|
-
return { message: \`Hello, \${args.name}! from ${serviceName}\` };
|
|
2246
|
-
}
|
|
2247
|
-
|
|
2248
|
-
// PROMPT - Prompt template
|
|
2249
|
-
// Prompt name: "welcomePrompt" (from function name)
|
|
2250
|
-
@Prompt({ description: "Welcome message prompt template" })
|
|
2251
|
-
welcomePrompt(args: { userName?: string }) {
|
|
2252
|
-
return {
|
|
2253
|
-
messages: [
|
|
2254
|
-
{
|
|
2255
|
-
role: "user",
|
|
2256
|
-
content: {
|
|
2257
|
-
type: "text",
|
|
2258
|
-
text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
]
|
|
2262
|
-
};
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
// RESOURCE - Data endpoint
|
|
2266
|
-
// Resource URI auto-generated from class and method name
|
|
2267
|
-
@Resource({ description: "${capitalize(serviceName)} service status" })
|
|
2268
|
-
getStatus() {
|
|
2269
|
-
return {
|
|
2270
|
-
service: "${serviceName}",
|
|
2271
|
-
status: "active",
|
|
2272
|
-
timestamp: new Date().toISOString()
|
|
2273
|
-
};
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
`;
|
|
2277
|
-
await fs7.writeFile(serviceFile, indexTs);
|
|
2278
|
-
console.log(chalk6.green(`\\nCreated new service: ${chalk6.bold(serviceName)}`));
|
|
2279
|
-
console.log(chalk6.gray(` File: mcp/${serviceName}/index.ts`));
|
|
2280
|
-
console.log(chalk6.gray(` Tool: greet`));
|
|
2281
|
-
console.log(chalk6.gray(` Prompt: welcomePrompt`));
|
|
2282
|
-
console.log(chalk6.gray(` Resource: getStatus`));
|
|
2283
|
-
console.log(chalk6.green(`\\nService will be automatically discovered on next server start!`));
|
|
2489
|
+
await fs8.mkdirp(serviceDir);
|
|
2490
|
+
const indexTs = getServiceIndexTemplate(serviceName, capitalize(serviceName));
|
|
2491
|
+
await fs8.writeFile(serviceFile, indexTs);
|
|
2492
|
+
console.log(chalk7.green(`\\nCreated new service: ${chalk7.bold(serviceName)}`));
|
|
2493
|
+
console.log(chalk7.gray(` File: mcp/${serviceName}/index.ts`));
|
|
2494
|
+
console.log(chalk7.gray(` Tool: greet`));
|
|
2495
|
+
console.log(chalk7.gray(` Prompt: welcomePrompt`));
|
|
2496
|
+
console.log(chalk7.gray(` Resource: getStatus`));
|
|
2497
|
+
console.log(chalk7.green(`\\nService will be automatically discovered on next server start!`));
|
|
2284
2498
|
});
|
|
2285
2499
|
program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(devCommand);
|
|
2500
|
+
program.command("build").description("Build UI components and compile TypeScript for production").action(buildCommand);
|
|
2286
2501
|
program.command("start").description("Build UI components and start production server").action(startCommand);
|
|
2287
2502
|
program.command("login").description("Authenticate with LeanMCP cloud using an API key").option("--debug", "Enable debug logging").action(async (options) => {
|
|
2288
2503
|
if (options.debug) {
|