@leanmcp/cli 0.2.7 → 0.2.9
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/LICENSE +21 -21
- package/README.md +428 -428
- package/bin/leanmcp.js +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +689 -122
- package/package.json +67 -66
- package/dist/index.d.mts +0 -1
- package/dist/index.mjs +0 -418
package/dist/index.js
CHANGED
|
@@ -1,51 +1,415 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
1
|
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import chalk3 from "chalk";
|
|
7
|
+
import fs5 from "fs-extra";
|
|
8
|
+
import path5 from "path";
|
|
9
|
+
import ora3 from "ora";
|
|
10
|
+
import { createRequire } from "module";
|
|
11
|
+
import { confirm } from "@inquirer/prompts";
|
|
12
|
+
import { spawn as spawn3 } from "child_process";
|
|
13
|
+
|
|
14
|
+
// src/commands/dev.ts
|
|
15
|
+
import { spawn } from "child_process";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import ora from "ora";
|
|
18
|
+
import path3 from "path";
|
|
19
|
+
import fs3 from "fs-extra";
|
|
20
|
+
import chokidar from "chokidar";
|
|
21
|
+
|
|
22
|
+
// src/vite/scanUIApp.ts
|
|
23
|
+
import fs from "fs-extra";
|
|
24
|
+
import path from "path";
|
|
25
|
+
import { glob } from "glob";
|
|
26
|
+
async function scanUIApp(projectDir) {
|
|
27
|
+
const mcpDir = path.join(projectDir, "mcp");
|
|
28
|
+
if (!await fs.pathExists(mcpDir)) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const tsFiles = await glob("**/*.ts", {
|
|
32
|
+
cwd: mcpDir,
|
|
33
|
+
absolute: false,
|
|
34
|
+
ignore: [
|
|
35
|
+
"**/*.d.ts",
|
|
36
|
+
"**/node_modules/**"
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
const results = [];
|
|
40
|
+
for (const relativeFile of tsFiles) {
|
|
41
|
+
const filePath = path.join(mcpDir, relativeFile);
|
|
42
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
43
|
+
if (!content.includes("@UIApp") || !content.includes("@leanmcp/ui")) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const uiApps = parseUIAppDecorators(content, filePath);
|
|
47
|
+
results.push(...uiApps);
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
__name(scanUIApp, "scanUIApp");
|
|
52
|
+
function parseUIAppDecorators(content, filePath) {
|
|
53
|
+
const results = [];
|
|
54
|
+
const classMatch = content.match(/export\s+class\s+(\w+)/);
|
|
55
|
+
const serviceName = classMatch ? classMatch[1] : "Unknown";
|
|
56
|
+
const importMap = parseImports(content, filePath);
|
|
57
|
+
const uiAppRegex = /@UIApp\s*\(\s*\{([^}]+)\}\s*\)\s*(?:async\s+)?(\w+)/g;
|
|
58
|
+
let match;
|
|
59
|
+
while ((match = uiAppRegex.exec(content)) !== null) {
|
|
60
|
+
const decoratorBody = match[1];
|
|
61
|
+
const methodName = match[2];
|
|
62
|
+
const componentMatch = decoratorBody.match(/component\s*:\s*(\w+)/);
|
|
63
|
+
if (!componentMatch) continue;
|
|
64
|
+
const componentName = componentMatch[1];
|
|
65
|
+
const componentPath = importMap[componentName];
|
|
66
|
+
if (!componentPath) {
|
|
67
|
+
console.warn(`[scanUIApp] Could not resolve import for component: ${componentName}`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const servicePrefix = serviceName.replace(/Service$/i, "").toLowerCase();
|
|
71
|
+
const resourceUri = `ui://${servicePrefix}/${methodName}`;
|
|
72
|
+
results.push({
|
|
73
|
+
servicePath: filePath,
|
|
74
|
+
componentPath,
|
|
75
|
+
componentName,
|
|
76
|
+
resourceUri,
|
|
77
|
+
methodName,
|
|
78
|
+
serviceName
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
__name(parseUIAppDecorators, "parseUIAppDecorators");
|
|
84
|
+
function parseImports(content, filePath) {
|
|
85
|
+
const importMap = {};
|
|
86
|
+
const dir = path.dirname(filePath);
|
|
87
|
+
const importRegex = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
90
|
+
const importPath = match[2];
|
|
91
|
+
if (!importPath.startsWith(".")) continue;
|
|
92
|
+
const names = match[1].split(",").map((n) => n.trim().split(/\s+as\s+/).pop().trim());
|
|
93
|
+
let resolvedPath = path.resolve(dir, importPath);
|
|
94
|
+
if (!resolvedPath.endsWith(".tsx") && !resolvedPath.endsWith(".ts")) {
|
|
95
|
+
if (fs.existsSync(resolvedPath + ".tsx")) {
|
|
96
|
+
resolvedPath += ".tsx";
|
|
97
|
+
} else if (fs.existsSync(resolvedPath + ".ts")) {
|
|
98
|
+
resolvedPath += ".ts";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
importMap[name] = resolvedPath;
|
|
103
|
+
}
|
|
15
104
|
}
|
|
16
|
-
return
|
|
105
|
+
return importMap;
|
|
106
|
+
}
|
|
107
|
+
__name(parseImports, "parseImports");
|
|
108
|
+
|
|
109
|
+
// src/vite/buildUI.ts
|
|
110
|
+
import * as vite from "vite";
|
|
111
|
+
import react from "@vitejs/plugin-react";
|
|
112
|
+
import { viteSingleFile } from "vite-plugin-singlefile";
|
|
113
|
+
import fs2 from "fs-extra";
|
|
114
|
+
import path2 from "path";
|
|
115
|
+
async function buildUIComponent(uiApp, projectDir, isDev = false) {
|
|
116
|
+
const { componentPath, componentName, resourceUri } = uiApp;
|
|
117
|
+
const safeFileName = resourceUri.replace("ui://", "").replace(/\//g, "-") + ".html";
|
|
118
|
+
const outDir = path2.join(projectDir, "dist", "ui");
|
|
119
|
+
const htmlPath = path2.join(outDir, safeFileName);
|
|
120
|
+
await fs2.ensureDir(outDir);
|
|
121
|
+
const tempDir = path2.join(projectDir, ".leanmcp-temp");
|
|
122
|
+
await fs2.ensureDir(tempDir);
|
|
123
|
+
const entryHtml = path2.join(tempDir, "index.html");
|
|
124
|
+
const entryJs = path2.join(tempDir, "entry.tsx");
|
|
125
|
+
await fs2.writeFile(entryHtml, `<!DOCTYPE html>
|
|
126
|
+
<html lang="en">
|
|
127
|
+
<head>
|
|
128
|
+
<meta charset="UTF-8">
|
|
129
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
130
|
+
<title>MCP App</title>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<div id="root"></div>
|
|
134
|
+
<script type="module" src="./entry.tsx"></script>
|
|
135
|
+
</body>
|
|
136
|
+
</html>`);
|
|
137
|
+
const relativeComponentPath = path2.relative(tempDir, componentPath).replace(/\\/g, "/");
|
|
138
|
+
await fs2.writeFile(entryJs, `
|
|
139
|
+
import React, { StrictMode } from 'react';
|
|
140
|
+
import { createRoot } from 'react-dom/client';
|
|
141
|
+
import { AppProvider } from '@leanmcp/ui';
|
|
142
|
+
import { ${componentName} } from '${relativeComponentPath.replace(/\.tsx?$/, "")}';
|
|
143
|
+
|
|
144
|
+
const APP_INFO = {
|
|
145
|
+
name: '${componentName}',
|
|
146
|
+
version: '1.0.0'
|
|
17
147
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
148
|
+
|
|
149
|
+
function App() {
|
|
150
|
+
return (
|
|
151
|
+
<AppProvider appInfo={APP_INFO}>
|
|
152
|
+
<${componentName} />
|
|
153
|
+
</AppProvider>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
createRoot(document.getElementById('root')!).render(
|
|
158
|
+
<StrictMode>
|
|
159
|
+
<App />
|
|
160
|
+
</StrictMode>
|
|
161
|
+
);
|
|
162
|
+
`);
|
|
163
|
+
try {
|
|
164
|
+
await vite.build({
|
|
165
|
+
root: tempDir,
|
|
166
|
+
plugins: [
|
|
167
|
+
react(),
|
|
168
|
+
viteSingleFile()
|
|
169
|
+
],
|
|
170
|
+
build: {
|
|
171
|
+
outDir,
|
|
172
|
+
emptyOutDir: false,
|
|
173
|
+
sourcemap: isDev ? "inline" : false,
|
|
174
|
+
minify: !isDev,
|
|
175
|
+
rollupOptions: {
|
|
176
|
+
input: entryHtml,
|
|
177
|
+
output: {
|
|
178
|
+
entryFileNames: `[name].js`
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
logLevel: "warn"
|
|
183
|
+
});
|
|
184
|
+
const builtHtml = path2.join(outDir, "index.html");
|
|
185
|
+
if (await fs2.pathExists(builtHtml)) {
|
|
186
|
+
await fs2.move(builtHtml, htmlPath, {
|
|
187
|
+
overwrite: true
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
await fs2.remove(entryHtml);
|
|
191
|
+
await fs2.remove(entryJs);
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
htmlPath
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
htmlPath: "",
|
|
200
|
+
error: error.message
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
__name(buildUIComponent, "buildUIComponent");
|
|
205
|
+
async function writeUIManifest(manifest, projectDir) {
|
|
206
|
+
const manifestPath = path2.join(projectDir, "dist", "ui-manifest.json");
|
|
207
|
+
await fs2.ensureDir(path2.dirname(manifestPath));
|
|
208
|
+
await fs2.writeJson(manifestPath, manifest, {
|
|
209
|
+
spaces: 2
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
__name(writeUIManifest, "writeUIManifest");
|
|
213
|
+
|
|
214
|
+
// src/commands/dev.ts
|
|
215
|
+
async function devCommand() {
|
|
216
|
+
const cwd = process.cwd();
|
|
217
|
+
if (!await fs3.pathExists(path3.join(cwd, "main.ts"))) {
|
|
218
|
+
console.error(chalk.red("ERROR: Not a LeanMCP project (main.ts not found)."));
|
|
219
|
+
console.error(chalk.gray("Run this command from your project root."));
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
console.log(chalk.cyan("\n\u{1F680} LeanMCP Development Server\n"));
|
|
223
|
+
const scanSpinner = ora("Scanning for @UIApp components...").start();
|
|
224
|
+
const uiApps = await scanUIApp(cwd);
|
|
225
|
+
if (uiApps.length === 0) {
|
|
226
|
+
scanSpinner.succeed("No @UIApp components found");
|
|
227
|
+
} else {
|
|
228
|
+
scanSpinner.succeed(`Found ${uiApps.length} @UIApp component(s)`);
|
|
229
|
+
for (const app of uiApps) {
|
|
230
|
+
console.log(chalk.gray(` \u2022 ${app.componentName} \u2192 ${app.resourceUri}`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const manifest = {};
|
|
234
|
+
if (uiApps.length > 0) {
|
|
235
|
+
const buildSpinner = ora("Building UI components...").start();
|
|
236
|
+
const errors = [];
|
|
237
|
+
for (const app of uiApps) {
|
|
238
|
+
const result = await buildUIComponent(app, cwd, true);
|
|
239
|
+
if (result.success) {
|
|
240
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
241
|
+
} else {
|
|
242
|
+
errors.push(`${app.componentName}: ${result.error}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
await writeUIManifest(manifest, cwd);
|
|
246
|
+
if (errors.length > 0) {
|
|
247
|
+
buildSpinner.warn("Built with warnings");
|
|
248
|
+
for (const error of errors) {
|
|
249
|
+
console.error(chalk.yellow(` \u26A0 ${error}`));
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
buildSpinner.succeed("UI components built");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
console.log(chalk.cyan("\nStarting development server...\n"));
|
|
256
|
+
const devServer = spawn("npx", [
|
|
257
|
+
"tsx",
|
|
258
|
+
"watch",
|
|
259
|
+
"main.ts"
|
|
260
|
+
], {
|
|
261
|
+
cwd,
|
|
262
|
+
stdio: "inherit",
|
|
263
|
+
shell: true
|
|
264
|
+
});
|
|
265
|
+
let watcher = null;
|
|
266
|
+
if (uiApps.length > 0) {
|
|
267
|
+
const componentPaths = uiApps.map((app) => app.componentPath);
|
|
268
|
+
watcher = chokidar.watch(componentPaths, {
|
|
269
|
+
ignoreInitial: true
|
|
270
|
+
});
|
|
271
|
+
watcher.on("change", async (changedPath) => {
|
|
272
|
+
const app = uiApps.find((a) => a.componentPath === changedPath);
|
|
273
|
+
if (!app) return;
|
|
274
|
+
console.log(chalk.cyan(`
|
|
275
|
+
[UI] Rebuilding ${app.componentName}...`));
|
|
276
|
+
const result = await buildUIComponent(app, cwd, true);
|
|
277
|
+
if (result.success) {
|
|
278
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
279
|
+
await writeUIManifest(manifest, cwd);
|
|
280
|
+
console.log(chalk.green(`[UI] ${app.componentName} rebuilt successfully`));
|
|
281
|
+
} else {
|
|
282
|
+
console.log(chalk.yellow(`[UI] ${app.componentName} build failed: ${result.error}`));
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
287
|
+
console.log(chalk.gray("\nShutting down..."));
|
|
288
|
+
if (watcher) watcher.close();
|
|
289
|
+
devServer.kill();
|
|
290
|
+
process.exit(0);
|
|
291
|
+
}, "cleanup");
|
|
292
|
+
process.on("SIGINT", cleanup);
|
|
293
|
+
process.on("SIGTERM", cleanup);
|
|
294
|
+
devServer.on("exit", (code) => {
|
|
295
|
+
if (watcher) watcher.close();
|
|
296
|
+
process.exit(code ?? 0);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
__name(devCommand, "devCommand");
|
|
300
|
+
|
|
301
|
+
// src/commands/start.ts
|
|
302
|
+
import { spawn as spawn2 } from "child_process";
|
|
303
|
+
import chalk2 from "chalk";
|
|
304
|
+
import ora2 from "ora";
|
|
305
|
+
import path4 from "path";
|
|
306
|
+
import fs4 from "fs-extra";
|
|
307
|
+
async function startCommand() {
|
|
308
|
+
const cwd = process.cwd();
|
|
309
|
+
if (!await fs4.pathExists(path4.join(cwd, "main.ts"))) {
|
|
310
|
+
console.error(chalk2.red("ERROR: Not a LeanMCP project (main.ts not found)."));
|
|
311
|
+
console.error(chalk2.gray("Run this command from your project root."));
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
console.log(chalk2.cyan("\n\u{1F680} LeanMCP Production Build\n"));
|
|
315
|
+
const scanSpinner = ora2("Scanning for @UIApp components...").start();
|
|
316
|
+
const uiApps = await scanUIApp(cwd);
|
|
317
|
+
if (uiApps.length === 0) {
|
|
318
|
+
scanSpinner.succeed("No @UIApp components found");
|
|
319
|
+
} else {
|
|
320
|
+
scanSpinner.succeed(`Found ${uiApps.length} @UIApp component(s)`);
|
|
321
|
+
}
|
|
322
|
+
const manifest = {};
|
|
323
|
+
if (uiApps.length > 0) {
|
|
324
|
+
const buildSpinner = ora2("Building UI components...").start();
|
|
325
|
+
const errors = [];
|
|
326
|
+
for (const app of uiApps) {
|
|
327
|
+
const result = await buildUIComponent(app, cwd, false);
|
|
328
|
+
if (result.success) {
|
|
329
|
+
manifest[app.resourceUri] = result.htmlPath;
|
|
330
|
+
} else {
|
|
331
|
+
errors.push(`${app.componentName}: ${result.error}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
await writeUIManifest(manifest, cwd);
|
|
335
|
+
if (errors.length > 0) {
|
|
336
|
+
buildSpinner.fail("Build failed");
|
|
337
|
+
for (const error of errors) {
|
|
338
|
+
console.error(chalk2.red(` \u2717 ${error}`));
|
|
339
|
+
}
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
buildSpinner.succeed("UI components built");
|
|
343
|
+
}
|
|
344
|
+
const tscSpinner = ora2("Compiling TypeScript...").start();
|
|
345
|
+
try {
|
|
346
|
+
await new Promise((resolve, reject) => {
|
|
347
|
+
const tsc = spawn2("npx", [
|
|
348
|
+
"tsc"
|
|
349
|
+
], {
|
|
350
|
+
cwd,
|
|
351
|
+
stdio: "pipe",
|
|
352
|
+
shell: true
|
|
353
|
+
});
|
|
354
|
+
let stderr = "";
|
|
355
|
+
tsc.stderr?.on("data", (data) => {
|
|
356
|
+
stderr += data;
|
|
357
|
+
});
|
|
358
|
+
tsc.on("close", (code) => {
|
|
359
|
+
if (code === 0) resolve();
|
|
360
|
+
else reject(new Error(stderr || `tsc exited with code ${code}`));
|
|
361
|
+
});
|
|
362
|
+
tsc.on("error", reject);
|
|
363
|
+
});
|
|
364
|
+
tscSpinner.succeed("TypeScript compiled");
|
|
365
|
+
} catch (error) {
|
|
366
|
+
tscSpinner.fail("TypeScript compilation failed");
|
|
367
|
+
console.error(chalk2.red(error instanceof Error ? error.message : String(error)));
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
console.log(chalk2.cyan("\nStarting production server...\n"));
|
|
371
|
+
const server = spawn2("node", [
|
|
372
|
+
"dist/main.js"
|
|
373
|
+
], {
|
|
374
|
+
cwd,
|
|
375
|
+
stdio: "inherit",
|
|
376
|
+
shell: true
|
|
377
|
+
});
|
|
378
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
379
|
+
console.log(chalk2.gray("\nShutting down..."));
|
|
380
|
+
server.kill();
|
|
381
|
+
process.exit(0);
|
|
382
|
+
}, "cleanup");
|
|
383
|
+
process.on("SIGINT", cleanup);
|
|
384
|
+
process.on("SIGTERM", cleanup);
|
|
385
|
+
server.on("exit", (code) => {
|
|
386
|
+
process.exit(code ?? 0);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
__name(startCommand, "startCommand");
|
|
26
390
|
|
|
27
391
|
// src/index.ts
|
|
28
|
-
var
|
|
29
|
-
var
|
|
30
|
-
var import_fs_extra = __toESM(require("fs-extra"));
|
|
31
|
-
var import_path = __toESM(require("path"));
|
|
32
|
-
var import_ora = __toESM(require("ora"));
|
|
392
|
+
var require2 = createRequire(import.meta.url);
|
|
393
|
+
var pkg = require2("../package.json");
|
|
33
394
|
function capitalize(str) {
|
|
34
395
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
35
396
|
}
|
|
36
397
|
__name(capitalize, "capitalize");
|
|
37
|
-
var program = new
|
|
38
|
-
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
398
|
+
var program = new Command();
|
|
399
|
+
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
|
|
400
|
+
Examples:
|
|
401
|
+
$ leanmcp create my-app --allow-all # Scaffold without interactive prompts
|
|
402
|
+
`);
|
|
403
|
+
program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").action(async (projectName, options) => {
|
|
404
|
+
const spinner = ora3(`Creating project ${projectName}...`).start();
|
|
405
|
+
const targetDir = path5.join(process.cwd(), projectName);
|
|
406
|
+
if (fs5.existsSync(targetDir)) {
|
|
43
407
|
spinner.fail(`Folder ${projectName} already exists.`);
|
|
44
408
|
process.exit(1);
|
|
45
409
|
}
|
|
46
|
-
await
|
|
47
|
-
await
|
|
48
|
-
const
|
|
410
|
+
await fs5.mkdirp(targetDir);
|
|
411
|
+
await fs5.mkdirp(path5.join(targetDir, "mcp", "example"));
|
|
412
|
+
const pkg2 = {
|
|
49
413
|
name: projectName,
|
|
50
414
|
version: "1.0.0",
|
|
51
415
|
description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
|
|
@@ -66,21 +430,16 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
|
|
|
66
430
|
author: "",
|
|
67
431
|
license: "MIT",
|
|
68
432
|
dependencies: {
|
|
69
|
-
"@leanmcp/core": "^0.
|
|
70
|
-
"
|
|
71
|
-
"cors": "^2.8.5",
|
|
72
|
-
"dotenv": "^16.5.0",
|
|
73
|
-
"express": "^5.1.0"
|
|
433
|
+
"@leanmcp/core": "^0.3.5",
|
|
434
|
+
"dotenv": "^16.5.0"
|
|
74
435
|
},
|
|
75
436
|
devDependencies: {
|
|
76
|
-
"@types/cors": "^2.8.19",
|
|
77
|
-
"@types/express": "^5.0.3",
|
|
78
437
|
"@types/node": "^20.0.0",
|
|
79
438
|
"tsx": "^4.20.3",
|
|
80
439
|
"typescript": "^5.6.3"
|
|
81
440
|
}
|
|
82
441
|
};
|
|
83
|
-
await
|
|
442
|
+
await fs5.writeJSON(path5.join(targetDir, "package.json"), pkg2, {
|
|
84
443
|
spaces: 2
|
|
85
444
|
});
|
|
86
445
|
const tsconfig = {
|
|
@@ -103,12 +462,11 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
|
|
|
103
462
|
"dist"
|
|
104
463
|
]
|
|
105
464
|
};
|
|
106
|
-
await
|
|
465
|
+
await fs5.writeJSON(path5.join(targetDir, "tsconfig.json"), tsconfig, {
|
|
107
466
|
spaces: 2
|
|
108
467
|
});
|
|
109
468
|
const mainTs = `import dotenv from "dotenv";
|
|
110
469
|
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
111
|
-
import { ExampleService } from "./mcp/example.js";
|
|
112
470
|
|
|
113
471
|
// Load environment variables
|
|
114
472
|
dotenv.config();
|
|
@@ -117,27 +475,29 @@ const PORT = Number(process.env.PORT) || 3001;
|
|
|
117
475
|
|
|
118
476
|
/**
|
|
119
477
|
* Create and configure the MCP server
|
|
478
|
+
* Services are automatically discovered from ./mcp directory
|
|
120
479
|
*/
|
|
121
|
-
|
|
480
|
+
const serverFactory = async () => {
|
|
122
481
|
const server = new MCPServer({
|
|
123
482
|
name: "${projectName}",
|
|
124
|
-
version: "1.0.0"
|
|
483
|
+
version: "1.0.0",
|
|
484
|
+
logging: true
|
|
125
485
|
});
|
|
126
486
|
|
|
127
|
-
//
|
|
128
|
-
server.registerService(new ExampleService());
|
|
129
|
-
|
|
487
|
+
// Services are automatically discovered and registered from ./mcp
|
|
130
488
|
return server.getServer();
|
|
131
|
-
}
|
|
489
|
+
};
|
|
132
490
|
|
|
133
491
|
// Start the HTTP server
|
|
134
|
-
await createHTTPServer(
|
|
492
|
+
await createHTTPServer(serverFactory, {
|
|
135
493
|
port: PORT,
|
|
136
494
|
cors: true,
|
|
137
|
-
logging: true
|
|
495
|
+
logging: true // Log HTTP requests
|
|
138
496
|
});
|
|
497
|
+
|
|
498
|
+
console.log(\`\\n${projectName} MCP Server\`);
|
|
139
499
|
`;
|
|
140
|
-
await
|
|
500
|
+
await fs5.writeFile(path5.join(targetDir, "main.ts"), mainTs);
|
|
141
501
|
const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
142
502
|
|
|
143
503
|
/**
|
|
@@ -163,27 +523,38 @@ class CalculateInput {
|
|
|
163
523
|
operation?: string;
|
|
164
524
|
}
|
|
165
525
|
|
|
526
|
+
class EchoInput {
|
|
527
|
+
@SchemaConstraint({
|
|
528
|
+
description: "Message to echo back",
|
|
529
|
+
minLength: 1
|
|
530
|
+
})
|
|
531
|
+
message!: string;
|
|
532
|
+
}
|
|
533
|
+
|
|
166
534
|
export class ExampleService {
|
|
167
535
|
@Tool({
|
|
168
536
|
description: "Perform arithmetic operations with automatic schema validation",
|
|
169
537
|
inputClass: CalculateInput
|
|
170
538
|
})
|
|
171
539
|
async calculate(input: CalculateInput) {
|
|
540
|
+
// Ensure numerical operations by explicitly converting to numbers
|
|
541
|
+
const a = Number(input.a);
|
|
542
|
+
const b = Number(input.b);
|
|
172
543
|
let result: number;
|
|
173
544
|
|
|
174
545
|
switch (input.operation || "add") {
|
|
175
546
|
case "add":
|
|
176
|
-
result =
|
|
547
|
+
result = a + b;
|
|
177
548
|
break;
|
|
178
549
|
case "subtract":
|
|
179
|
-
result =
|
|
550
|
+
result = a - b;
|
|
180
551
|
break;
|
|
181
552
|
case "multiply":
|
|
182
|
-
result =
|
|
553
|
+
result = a * b;
|
|
183
554
|
break;
|
|
184
555
|
case "divide":
|
|
185
|
-
if (
|
|
186
|
-
result =
|
|
556
|
+
if (b === 0) throw new Error("Cannot divide by zero");
|
|
557
|
+
result = a / b;
|
|
187
558
|
break;
|
|
188
559
|
default:
|
|
189
560
|
throw new Error("Invalid operation");
|
|
@@ -201,8 +572,11 @@ export class ExampleService {
|
|
|
201
572
|
};
|
|
202
573
|
}
|
|
203
574
|
|
|
204
|
-
@Tool({
|
|
205
|
-
|
|
575
|
+
@Tool({
|
|
576
|
+
description: "Echo a message back",
|
|
577
|
+
inputClass: EchoInput
|
|
578
|
+
})
|
|
579
|
+
async echo(input: EchoInput) {
|
|
206
580
|
return {
|
|
207
581
|
content: [{
|
|
208
582
|
type: "text" as const,
|
|
@@ -243,12 +617,148 @@ export class ExampleService {
|
|
|
243
617
|
}
|
|
244
618
|
}
|
|
245
619
|
`;
|
|
246
|
-
await
|
|
247
|
-
const gitignore =
|
|
248
|
-
|
|
249
|
-
.env
|
|
250
|
-
.env.local
|
|
620
|
+
await fs5.writeFile(path5.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
|
|
621
|
+
const gitignore = `# Logs
|
|
622
|
+
logs
|
|
251
623
|
*.log
|
|
624
|
+
npm-debug.log*
|
|
625
|
+
yarn-debug.log*
|
|
626
|
+
yarn-error.log*
|
|
627
|
+
lerna-debug.log*
|
|
628
|
+
|
|
629
|
+
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
630
|
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
631
|
+
|
|
632
|
+
# Runtime data
|
|
633
|
+
pids
|
|
634
|
+
*.pid
|
|
635
|
+
*.seed
|
|
636
|
+
*.pid.lock
|
|
637
|
+
|
|
638
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
639
|
+
lib-cov
|
|
640
|
+
|
|
641
|
+
# Coverage directory used by tools like istanbul
|
|
642
|
+
coverage
|
|
643
|
+
*.lcov
|
|
644
|
+
|
|
645
|
+
# nyc test coverage
|
|
646
|
+
.nyc_output
|
|
647
|
+
|
|
648
|
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
649
|
+
.grunt
|
|
650
|
+
|
|
651
|
+
# Bower dependency directory (https://bower.io/)
|
|
652
|
+
bower_components
|
|
653
|
+
|
|
654
|
+
# node-waf configuration
|
|
655
|
+
.lock-wscript
|
|
656
|
+
|
|
657
|
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
658
|
+
build/Release
|
|
659
|
+
|
|
660
|
+
# Dependency directories
|
|
661
|
+
node_modules/
|
|
662
|
+
jspm_packages/
|
|
663
|
+
|
|
664
|
+
# Snowpack dependency directory (https://snowpack.dev/)
|
|
665
|
+
web_modules/
|
|
666
|
+
|
|
667
|
+
# TypeScript cache
|
|
668
|
+
*.tsbuildinfo
|
|
669
|
+
|
|
670
|
+
# Optional npm cache directory
|
|
671
|
+
.npm
|
|
672
|
+
|
|
673
|
+
# Optional eslint cache
|
|
674
|
+
.eslintcache
|
|
675
|
+
|
|
676
|
+
# Optional stylelint cache
|
|
677
|
+
.stylelintcache
|
|
678
|
+
|
|
679
|
+
# Optional REPL history
|
|
680
|
+
.node_repl_history
|
|
681
|
+
|
|
682
|
+
# Output of 'npm pack'
|
|
683
|
+
*.tgz
|
|
684
|
+
|
|
685
|
+
# Yarn Integrity file
|
|
686
|
+
.yarn-integrity
|
|
687
|
+
|
|
688
|
+
# dotenv environment variable files
|
|
689
|
+
.env
|
|
690
|
+
.env.*
|
|
691
|
+
!.env.example
|
|
692
|
+
|
|
693
|
+
# parcel-bundler cache (https://parceljs.org/)
|
|
694
|
+
.cache
|
|
695
|
+
.parcel-cache
|
|
696
|
+
|
|
697
|
+
# Next.js build output
|
|
698
|
+
.next
|
|
699
|
+
out
|
|
700
|
+
|
|
701
|
+
# Nuxt.js build / generate output
|
|
702
|
+
.nuxt
|
|
703
|
+
dist
|
|
704
|
+
.output
|
|
705
|
+
|
|
706
|
+
# Gatsby files
|
|
707
|
+
.cache/
|
|
708
|
+
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
709
|
+
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
710
|
+
# public
|
|
711
|
+
|
|
712
|
+
# vuepress build output
|
|
713
|
+
.vuepress/dist
|
|
714
|
+
|
|
715
|
+
# vuepress v2.x temp and cache directory
|
|
716
|
+
.temp
|
|
717
|
+
.cache
|
|
718
|
+
|
|
719
|
+
# Sveltekit cache directory
|
|
720
|
+
.svelte-kit/
|
|
721
|
+
|
|
722
|
+
# vitepress build output
|
|
723
|
+
**/.vitepress/dist
|
|
724
|
+
|
|
725
|
+
# vitepress cache directory
|
|
726
|
+
**/.vitepress/cache
|
|
727
|
+
|
|
728
|
+
# Docusaurus cache and generated files
|
|
729
|
+
.docusaurus
|
|
730
|
+
|
|
731
|
+
# Serverless directories
|
|
732
|
+
.serverless/
|
|
733
|
+
|
|
734
|
+
# FuseBox cache
|
|
735
|
+
.fusebox/
|
|
736
|
+
|
|
737
|
+
# DynamoDB Local files
|
|
738
|
+
.dynamodb/
|
|
739
|
+
|
|
740
|
+
# Firebase cache directory
|
|
741
|
+
.firebase/
|
|
742
|
+
|
|
743
|
+
# TernJS port file
|
|
744
|
+
.tern-port
|
|
745
|
+
|
|
746
|
+
# Stores VSCode versions used for testing VSCode extensions
|
|
747
|
+
.vscode-test
|
|
748
|
+
|
|
749
|
+
# yarn v3
|
|
750
|
+
.pnp.*
|
|
751
|
+
.yarn/*
|
|
752
|
+
!.yarn/patches
|
|
753
|
+
!.yarn/plugins
|
|
754
|
+
!.yarn/releases
|
|
755
|
+
!.yarn/sdks
|
|
756
|
+
!.yarn/versions
|
|
757
|
+
|
|
758
|
+
# Vite files
|
|
759
|
+
vite.config.js.timestamp-*
|
|
760
|
+
vite.config.ts.timestamp-*
|
|
761
|
+
.vite/
|
|
252
762
|
`;
|
|
253
763
|
const env = `# Server Configuration
|
|
254
764
|
PORT=3001
|
|
@@ -256,8 +766,8 @@ NODE_ENV=development
|
|
|
256
766
|
|
|
257
767
|
# Add your environment variables here
|
|
258
768
|
`;
|
|
259
|
-
await
|
|
260
|
-
await
|
|
769
|
+
await fs5.writeFile(path5.join(targetDir, ".gitignore"), gitignore);
|
|
770
|
+
await fs5.writeFile(path5.join(targetDir, ".env"), env);
|
|
261
771
|
const readme = `# ${projectName}
|
|
262
772
|
|
|
263
773
|
MCP Server with Streamable HTTP Transport built with LeanMCP SDK
|
|
@@ -283,22 +793,36 @@ npm start
|
|
|
283
793
|
\`\`\`
|
|
284
794
|
${projectName}/
|
|
285
795
|
\u251C\u2500\u2500 main.ts # Server entry point
|
|
286
|
-
\u251C\u2500\u2500 mcp/
|
|
287
|
-
\u2502 \u2514\u2500\u2500 example
|
|
796
|
+
\u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
|
|
797
|
+
\u2502 \u2514\u2500\u2500 example/
|
|
798
|
+
\u2502 \u2514\u2500\u2500 index.ts # Example service
|
|
288
799
|
\u251C\u2500\u2500 .env # Environment variables
|
|
289
800
|
\u2514\u2500\u2500 package.json
|
|
290
801
|
\`\`\`
|
|
291
802
|
|
|
292
803
|
## Adding New Services
|
|
293
804
|
|
|
294
|
-
Create a new service
|
|
805
|
+
Create a new service directory in \`mcp/\`:
|
|
295
806
|
|
|
296
807
|
\`\`\`typescript
|
|
297
|
-
|
|
808
|
+
// mcp/myservice/index.ts
|
|
809
|
+
import { Tool, SchemaConstraint } from "@leanmcp/core";
|
|
810
|
+
|
|
811
|
+
// Define input schema
|
|
812
|
+
class MyToolInput {
|
|
813
|
+
@SchemaConstraint({
|
|
814
|
+
description: "Message to process",
|
|
815
|
+
minLength: 1
|
|
816
|
+
})
|
|
817
|
+
message!: string;
|
|
818
|
+
}
|
|
298
819
|
|
|
299
820
|
export class MyService {
|
|
300
|
-
@Tool({
|
|
301
|
-
|
|
821
|
+
@Tool({
|
|
822
|
+
description: "My awesome tool",
|
|
823
|
+
inputClass: MyToolInput
|
|
824
|
+
})
|
|
825
|
+
async myTool(input: MyToolInput) {
|
|
302
826
|
return {
|
|
303
827
|
content: [{
|
|
304
828
|
type: "text",
|
|
@@ -309,12 +833,15 @@ export class MyService {
|
|
|
309
833
|
}
|
|
310
834
|
\`\`\`
|
|
311
835
|
|
|
312
|
-
|
|
836
|
+
Services are automatically discovered and registered - no need to modify \`main.ts\`!
|
|
313
837
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
838
|
+
## Features
|
|
839
|
+
|
|
840
|
+
- **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
|
|
841
|
+
- **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
|
|
842
|
+
- **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
|
|
843
|
+
- **HTTP transport** - Production-ready HTTP server with session management
|
|
844
|
+
- **Hot reload** - Development mode with automatic restart on file changes
|
|
318
845
|
|
|
319
846
|
## Testing with MCP Inspector
|
|
320
847
|
|
|
@@ -326,28 +853,88 @@ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
|
|
|
326
853
|
|
|
327
854
|
MIT
|
|
328
855
|
`;
|
|
329
|
-
await
|
|
856
|
+
await fs5.writeFile(path5.join(targetDir, "README.md"), readme);
|
|
330
857
|
spinner.succeed(`Project ${projectName} created!`);
|
|
331
|
-
console.log(
|
|
332
|
-
console.log(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
858
|
+
console.log(chalk3.green("\nSuccess! Your MCP server is ready.\n"));
|
|
859
|
+
console.log(chalk3.cyan(`Next, navigate to your project:
|
|
860
|
+
cd ${projectName}
|
|
861
|
+
`));
|
|
862
|
+
const shouldInstall = options.allowAll ? true : await confirm({
|
|
863
|
+
message: "Would you like to install dependencies now?",
|
|
864
|
+
default: true
|
|
865
|
+
});
|
|
866
|
+
if (shouldInstall) {
|
|
867
|
+
const installSpinner = ora3("Installing dependencies...").start();
|
|
868
|
+
try {
|
|
869
|
+
await new Promise((resolve, reject) => {
|
|
870
|
+
const npmInstall = spawn3("npm", [
|
|
871
|
+
"install"
|
|
872
|
+
], {
|
|
873
|
+
cwd: targetDir,
|
|
874
|
+
stdio: "pipe",
|
|
875
|
+
shell: true
|
|
876
|
+
});
|
|
877
|
+
npmInstall.on("close", (code) => {
|
|
878
|
+
if (code === 0) {
|
|
879
|
+
resolve();
|
|
880
|
+
} else {
|
|
881
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
npmInstall.on("error", reject);
|
|
885
|
+
});
|
|
886
|
+
installSpinner.succeed("Dependencies installed successfully!");
|
|
887
|
+
const shouldStartDev = options.allowAll ? true : await confirm({
|
|
888
|
+
message: "Would you like to start the development server?",
|
|
889
|
+
default: true
|
|
890
|
+
});
|
|
891
|
+
if (shouldStartDev) {
|
|
892
|
+
console.log(chalk3.cyan("\nStarting development server...\n"));
|
|
893
|
+
const devServer = spawn3("npm", [
|
|
894
|
+
"run",
|
|
895
|
+
"dev"
|
|
896
|
+
], {
|
|
897
|
+
cwd: targetDir,
|
|
898
|
+
stdio: "inherit",
|
|
899
|
+
shell: true
|
|
900
|
+
});
|
|
901
|
+
process.on("SIGINT", () => {
|
|
902
|
+
devServer.kill();
|
|
903
|
+
process.exit(0);
|
|
904
|
+
});
|
|
905
|
+
} else {
|
|
906
|
+
console.log(chalk3.cyan("\nTo start the development server later:"));
|
|
907
|
+
console.log(chalk3.gray(` cd ${projectName}`));
|
|
908
|
+
console.log(chalk3.gray(` npm run dev`));
|
|
909
|
+
}
|
|
910
|
+
} catch (error) {
|
|
911
|
+
installSpinner.fail("Failed to install dependencies");
|
|
912
|
+
console.error(chalk3.red(error instanceof Error ? error.message : String(error)));
|
|
913
|
+
console.log(chalk3.cyan("\nYou can install dependencies manually:"));
|
|
914
|
+
console.log(chalk3.gray(` cd ${projectName}`));
|
|
915
|
+
console.log(chalk3.gray(` npm install`));
|
|
916
|
+
}
|
|
917
|
+
} else {
|
|
918
|
+
console.log(chalk3.cyan("\nTo get started:"));
|
|
919
|
+
console.log(chalk3.gray(` cd ${projectName}`));
|
|
920
|
+
console.log(chalk3.gray(` npm install`));
|
|
921
|
+
console.log(chalk3.gray(` npm run dev`));
|
|
922
|
+
}
|
|
337
923
|
});
|
|
338
924
|
program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
|
|
339
925
|
const cwd = process.cwd();
|
|
340
|
-
const mcpDir =
|
|
341
|
-
if (!
|
|
342
|
-
console.error(
|
|
926
|
+
const mcpDir = path5.join(cwd, "mcp");
|
|
927
|
+
if (!fs5.existsSync(path5.join(cwd, "main.ts"))) {
|
|
928
|
+
console.error(chalk3.red("ERROR: Not a LeanMCP project (main.ts missing)."));
|
|
343
929
|
process.exit(1);
|
|
344
930
|
}
|
|
345
|
-
|
|
346
|
-
const serviceFile =
|
|
347
|
-
if (
|
|
348
|
-
console.error(
|
|
931
|
+
const serviceDir = path5.join(mcpDir, serviceName);
|
|
932
|
+
const serviceFile = path5.join(serviceDir, "index.ts");
|
|
933
|
+
if (fs5.existsSync(serviceDir)) {
|
|
934
|
+
console.error(chalk3.red(`ERROR: Service ${serviceName} already exists.`));
|
|
349
935
|
process.exit(1);
|
|
350
936
|
}
|
|
937
|
+
await fs5.mkdirp(serviceDir);
|
|
351
938
|
const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
|
|
352
939
|
|
|
353
940
|
// Input schema for greeting
|
|
@@ -407,34 +994,14 @@ export class ${capitalize(serviceName)}Service {
|
|
|
407
994
|
}
|
|
408
995
|
}
|
|
409
996
|
`;
|
|
410
|
-
await
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (lastImportMatch) {
|
|
418
|
-
const lastImport = lastImportMatch[lastImportMatch.length - 1];
|
|
419
|
-
const lastImportIndex = mainTsContent.lastIndexOf(lastImport);
|
|
420
|
-
const afterLastImport = lastImportIndex + lastImport.length;
|
|
421
|
-
mainTsContent = mainTsContent.slice(0, afterLastImport) + importStatement + "\n" + mainTsContent.slice(afterLastImport);
|
|
422
|
-
}
|
|
423
|
-
const registerPattern = /server\.registerService\(new \w+\(\)\);/g;
|
|
424
|
-
const matches = [
|
|
425
|
-
...mainTsContent.matchAll(registerPattern)
|
|
426
|
-
];
|
|
427
|
-
if (matches.length > 0) {
|
|
428
|
-
const lastMatch = matches[matches.length - 1];
|
|
429
|
-
const insertPosition = lastMatch.index + lastMatch[0].length;
|
|
430
|
-
mainTsContent = mainTsContent.slice(0, insertPosition) + "\n" + registerStatement + mainTsContent.slice(insertPosition);
|
|
431
|
-
}
|
|
432
|
-
await import_fs_extra.default.writeFile(mainTsPath, mainTsContent);
|
|
433
|
-
console.log(import_chalk.default.green(`\\nCreated new service: ${import_chalk.default.bold(serviceName)}`));
|
|
434
|
-
console.log(import_chalk.default.gray(` File: mcp/${serviceName}.ts`));
|
|
435
|
-
console.log(import_chalk.default.gray(` Tool: greet`));
|
|
436
|
-
console.log(import_chalk.default.gray(` Prompt: welcomePrompt`));
|
|
437
|
-
console.log(import_chalk.default.gray(` Resource: getStatus`));
|
|
438
|
-
console.log(import_chalk.default.green(`\\nService automatically registered in main.ts!`));
|
|
997
|
+
await fs5.writeFile(serviceFile, indexTs);
|
|
998
|
+
console.log(chalk3.green(`\\nCreated new service: ${chalk3.bold(serviceName)}`));
|
|
999
|
+
console.log(chalk3.gray(` File: mcp/${serviceName}/index.ts`));
|
|
1000
|
+
console.log(chalk3.gray(` Tool: greet`));
|
|
1001
|
+
console.log(chalk3.gray(` Prompt: welcomePrompt`));
|
|
1002
|
+
console.log(chalk3.gray(` Resource: getStatus`));
|
|
1003
|
+
console.log(chalk3.green(`\\nService will be automatically discovered on next server start!`));
|
|
439
1004
|
});
|
|
1005
|
+
program.command("dev").description("Start development server with UI hot-reload (builds @UIApp components)").action(devCommand);
|
|
1006
|
+
program.command("start").description("Build UI components and start production server").action(startCommand);
|
|
440
1007
|
program.parse();
|