@leanmcp/cli 0.2.5 → 0.2.6

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/bin/leanmcp.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
-
3
- // Always prefer ESM build
4
- import('../dist/index.js');
2
+
3
+ // Always prefer ESM build
4
+ import('../dist/index.js');
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.d.ts CHANGED
@@ -1,2 +1 @@
1
-
2
- export { }
1
+ #!/usr/bin/env node
package/dist/index.js CHANGED
@@ -1,36 +1,51 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
1
4
  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;
2
9
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
3
26
 
4
27
  // src/index.ts
5
- import { Command } from "commander";
6
- import chalk from "chalk";
7
- import fs from "fs-extra";
8
- import path from "path";
9
- import ora from "ora";
10
- import { createRequire } from "module";
11
- import { confirm } from "@inquirer/prompts";
12
- import { spawn } from "child_process";
13
- var require2 = createRequire(import.meta.url);
14
- var pkg = require2("../package.json");
28
+ var import_commander = require("commander");
29
+ var import_chalk = __toESM(require("chalk"));
30
+ var import_fs_extra = __toESM(require("fs-extra"));
31
+ var import_path = __toESM(require("path"));
32
+ var import_ora = __toESM(require("ora"));
15
33
  function capitalize(str) {
16
34
  return str.charAt(0).toUpperCase() + str.slice(1);
17
35
  }
18
36
  __name(capitalize, "capitalize");
19
- var program = new Command();
20
- program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version).addHelpText("after", `
21
- Examples:
22
- $ leanmcp create my-app --allow-all # Scaffold without interactive prompts
23
- `);
24
- 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) => {
25
- const spinner = ora(`Creating project ${projectName}...`).start();
26
- const targetDir = path.join(process.cwd(), projectName);
27
- if (fs.existsSync(targetDir)) {
37
+ var program = new import_commander.Command();
38
+ program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version("0.1.0");
39
+ program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").action(async (projectName) => {
40
+ const spinner = (0, import_ora.default)(`Creating project ${projectName}...`).start();
41
+ const targetDir = import_path.default.join(process.cwd(), projectName);
42
+ if (import_fs_extra.default.existsSync(targetDir)) {
28
43
  spinner.fail(`Folder ${projectName} already exists.`);
29
44
  process.exit(1);
30
45
  }
31
- await fs.mkdirp(targetDir);
32
- await fs.mkdirp(path.join(targetDir, "mcp", "example"));
33
- const pkg2 = {
46
+ await import_fs_extra.default.mkdirp(targetDir);
47
+ await import_fs_extra.default.mkdirp(import_path.default.join(targetDir, "mcp"));
48
+ const pkg = {
34
49
  name: projectName,
35
50
  version: "1.0.0",
36
51
  description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
@@ -51,16 +66,21 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
51
66
  author: "",
52
67
  license: "MIT",
53
68
  dependencies: {
54
- "@leanmcp/core": "^0.3.2",
55
- "dotenv": "^16.5.0"
69
+ "@leanmcp/core": "^0.1.0",
70
+ "@modelcontextprotocol/sdk": "^1.0.0",
71
+ "cors": "^2.8.5",
72
+ "dotenv": "^16.5.0",
73
+ "express": "^5.1.0"
56
74
  },
57
75
  devDependencies: {
76
+ "@types/cors": "^2.8.19",
77
+ "@types/express": "^5.0.3",
58
78
  "@types/node": "^20.0.0",
59
79
  "tsx": "^4.20.3",
60
80
  "typescript": "^5.6.3"
61
81
  }
62
82
  };
63
- await fs.writeJSON(path.join(targetDir, "package.json"), pkg2, {
83
+ await import_fs_extra.default.writeJSON(import_path.default.join(targetDir, "package.json"), pkg, {
64
84
  spaces: 2
65
85
  });
66
86
  const tsconfig = {
@@ -83,11 +103,12 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
83
103
  "dist"
84
104
  ]
85
105
  };
86
- await fs.writeJSON(path.join(targetDir, "tsconfig.json"), tsconfig, {
106
+ await import_fs_extra.default.writeJSON(import_path.default.join(targetDir, "tsconfig.json"), tsconfig, {
87
107
  spaces: 2
88
108
  });
89
109
  const mainTs = `import dotenv from "dotenv";
90
110
  import { createHTTPServer, MCPServer } from "@leanmcp/core";
111
+ import { ExampleService } from "./mcp/example.js";
91
112
 
92
113
  // Load environment variables
93
114
  dotenv.config();
@@ -96,29 +117,27 @@ const PORT = Number(process.env.PORT) || 3001;
96
117
 
97
118
  /**
98
119
  * Create and configure the MCP server
99
- * Services are automatically discovered from ./mcp directory
100
120
  */
101
- const serverFactory = async () => {
121
+ function createMCPServer() {
102
122
  const server = new MCPServer({
103
123
  name: "${projectName}",
104
- version: "1.0.0",
105
- logging: true
124
+ version: "1.0.0"
106
125
  });
107
126
 
108
- // Services are automatically discovered and registered from ./mcp
127
+ // Register your services here
128
+ server.registerService(new ExampleService());
129
+
109
130
  return server.getServer();
110
- };
131
+ }
111
132
 
112
133
  // Start the HTTP server
113
- await createHTTPServer(serverFactory, {
134
+ await createHTTPServer(createMCPServer, {
114
135
  port: PORT,
115
136
  cors: true,
116
- logging: true // Log HTTP requests
137
+ logging: true
117
138
  });
118
-
119
- console.log(\`\\n${projectName} MCP Server\`);
120
139
  `;
121
- await fs.writeFile(path.join(targetDir, "main.ts"), mainTs);
140
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "main.ts"), mainTs);
122
141
  const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
123
142
 
124
143
  /**
@@ -144,38 +163,27 @@ class CalculateInput {
144
163
  operation?: string;
145
164
  }
146
165
 
147
- class EchoInput {
148
- @SchemaConstraint({
149
- description: "Message to echo back",
150
- minLength: 1
151
- })
152
- message!: string;
153
- }
154
-
155
166
  export class ExampleService {
156
167
  @Tool({
157
168
  description: "Perform arithmetic operations with automatic schema validation",
158
169
  inputClass: CalculateInput
159
170
  })
160
171
  async calculate(input: CalculateInput) {
161
- // Ensure numerical operations by explicitly converting to numbers
162
- const a = Number(input.a);
163
- const b = Number(input.b);
164
172
  let result: number;
165
173
 
166
174
  switch (input.operation || "add") {
167
175
  case "add":
168
- result = a + b;
176
+ result = input.a + input.b;
169
177
  break;
170
178
  case "subtract":
171
- result = a - b;
179
+ result = input.a - input.b;
172
180
  break;
173
181
  case "multiply":
174
- result = a * b;
182
+ result = input.a * input.b;
175
183
  break;
176
184
  case "divide":
177
- if (b === 0) throw new Error("Cannot divide by zero");
178
- result = a / b;
185
+ if (input.b === 0) throw new Error("Cannot divide by zero");
186
+ result = input.a / input.b;
179
187
  break;
180
188
  default:
181
189
  throw new Error("Invalid operation");
@@ -193,11 +201,8 @@ export class ExampleService {
193
201
  };
194
202
  }
195
203
 
196
- @Tool({
197
- description: "Echo a message back",
198
- inputClass: EchoInput
199
- })
200
- async echo(input: EchoInput) {
204
+ @Tool({ description: "Echo a message back" })
205
+ async echo(input: { message: string }) {
201
206
  return {
202
207
  content: [{
203
208
  type: "text" as const,
@@ -238,7 +243,7 @@ export class ExampleService {
238
243
  }
239
244
  }
240
245
  `;
241
- await fs.writeFile(path.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
246
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "mcp", "example.ts"), exampleServiceTs);
242
247
  const gitignore = `node_modules
243
248
  dist
244
249
  .env
@@ -251,8 +256,8 @@ NODE_ENV=development
251
256
 
252
257
  # Add your environment variables here
253
258
  `;
254
- await fs.writeFile(path.join(targetDir, ".gitignore"), gitignore);
255
- await fs.writeFile(path.join(targetDir, ".env"), env);
259
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, ".gitignore"), gitignore);
260
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, ".env"), env);
256
261
  const readme = `# ${projectName}
257
262
 
258
263
  MCP Server with Streamable HTTP Transport built with LeanMCP SDK
@@ -278,36 +283,22 @@ npm start
278
283
  \`\`\`
279
284
  ${projectName}/
280
285
  \u251C\u2500\u2500 main.ts # Server entry point
281
- \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
282
- \u2502 \u2514\u2500\u2500 example/
283
- \u2502 \u2514\u2500\u2500 index.ts # Example service
286
+ \u251C\u2500\u2500 mcp/
287
+ \u2502 \u2514\u2500\u2500 example.ts # Example service
284
288
  \u251C\u2500\u2500 .env # Environment variables
285
289
  \u2514\u2500\u2500 package.json
286
290
  \`\`\`
287
291
 
288
292
  ## Adding New Services
289
293
 
290
- Create a new service directory in \`mcp/\`:
294
+ Create a new service file in \`mcp/\`:
291
295
 
292
296
  \`\`\`typescript
293
- // mcp/myservice/index.ts
294
- import { Tool, SchemaConstraint } from "@leanmcp/core";
295
-
296
- // Define input schema
297
- class MyToolInput {
298
- @SchemaConstraint({
299
- description: "Message to process",
300
- minLength: 1
301
- })
302
- message!: string;
303
- }
297
+ import { Tool } from "@leanmcp/core";
304
298
 
305
299
  export class MyService {
306
- @Tool({
307
- description: "My awesome tool",
308
- inputClass: MyToolInput
309
- })
310
- async myTool(input: MyToolInput) {
300
+ @Tool({ description: "My awesome tool" })
301
+ async myTool(input: { message: string }) {
311
302
  return {
312
303
  content: [{
313
304
  type: "text",
@@ -318,15 +309,12 @@ export class MyService {
318
309
  }
319
310
  \`\`\`
320
311
 
321
- Services are automatically discovered and registered - no need to modify \`main.ts\`!
312
+ Then register it in \`main.ts\`:
322
313
 
323
- ## Features
324
-
325
- - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
326
- - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
327
- - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
328
- - **HTTP transport** - Production-ready HTTP server with session management
329
- - **Hot reload** - Development mode with automatic restart on file changes
314
+ \`\`\`typescript
315
+ import { MyService } from "./mcp/my-service.js";
316
+ server.registerService(new MyService());
317
+ \`\`\`
330
318
 
331
319
  ## Testing with MCP Inspector
332
320
 
@@ -338,88 +326,28 @@ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
338
326
 
339
327
  MIT
340
328
  `;
341
- await fs.writeFile(path.join(targetDir, "README.md"), readme);
329
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "README.md"), readme);
342
330
  spinner.succeed(`Project ${projectName} created!`);
343
- console.log(chalk.green("\nSuccess! Your MCP server is ready.\n"));
344
- console.log(chalk.cyan(`Next, navigate to your project:
345
- cd ${projectName}
346
- `));
347
- const shouldInstall = options.allowAll ? true : await confirm({
348
- message: "Would you like to install dependencies now?",
349
- default: true
350
- });
351
- if (shouldInstall) {
352
- const installSpinner = ora("Installing dependencies...").start();
353
- try {
354
- await new Promise((resolve, reject) => {
355
- const npmInstall = spawn("npm", [
356
- "install"
357
- ], {
358
- cwd: targetDir,
359
- stdio: "pipe",
360
- shell: true
361
- });
362
- npmInstall.on("close", (code) => {
363
- if (code === 0) {
364
- resolve();
365
- } else {
366
- reject(new Error(`npm install failed with code ${code}`));
367
- }
368
- });
369
- npmInstall.on("error", reject);
370
- });
371
- installSpinner.succeed("Dependencies installed successfully!");
372
- const shouldStartDev = options.allowAll ? true : await confirm({
373
- message: "Would you like to start the development server?",
374
- default: true
375
- });
376
- if (shouldStartDev) {
377
- console.log(chalk.cyan("\nStarting development server...\n"));
378
- const devServer = spawn("npm", [
379
- "run",
380
- "dev"
381
- ], {
382
- cwd: targetDir,
383
- stdio: "inherit",
384
- shell: true
385
- });
386
- process.on("SIGINT", () => {
387
- devServer.kill();
388
- process.exit(0);
389
- });
390
- } else {
391
- console.log(chalk.cyan("\nTo start the development server later:"));
392
- console.log(chalk.gray(` cd ${projectName}`));
393
- console.log(chalk.gray(` npm run dev`));
394
- }
395
- } catch (error) {
396
- installSpinner.fail("Failed to install dependencies");
397
- console.error(chalk.red(error instanceof Error ? error.message : String(error)));
398
- console.log(chalk.cyan("\nYou can install dependencies manually:"));
399
- console.log(chalk.gray(` cd ${projectName}`));
400
- console.log(chalk.gray(` npm install`));
401
- }
402
- } else {
403
- console.log(chalk.cyan("\nTo get started:"));
404
- console.log(chalk.gray(` cd ${projectName}`));
405
- console.log(chalk.gray(` npm install`));
406
- console.log(chalk.gray(` npm run dev`));
407
- }
331
+ console.log(import_chalk.default.green("\\nSuccess! Your MCP server is ready.\\n"));
332
+ console.log(import_chalk.default.cyan("Next steps:"));
333
+ console.log(import_chalk.default.gray(` cd ${projectName}`));
334
+ console.log(import_chalk.default.gray(` npm install`));
335
+ console.log(import_chalk.default.gray(` npm run dev`));
336
+ console.log(import_chalk.default.gray(`\\nServer will run on http://localhost:3001`));
408
337
  });
409
338
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
410
339
  const cwd = process.cwd();
411
- const mcpDir = path.join(cwd, "mcp");
412
- if (!fs.existsSync(path.join(cwd, "main.ts"))) {
413
- console.error(chalk.red("ERROR: Not a LeanMCP project (main.ts missing)."));
340
+ const mcpDir = import_path.default.join(cwd, "mcp");
341
+ if (!import_fs_extra.default.existsSync(import_path.default.join(cwd, "main.ts"))) {
342
+ console.error(import_chalk.default.red("ERROR: Not a LeanMCP project (main.ts missing)."));
414
343
  process.exit(1);
415
344
  }
416
- const serviceDir = path.join(mcpDir, serviceName);
417
- const serviceFile = path.join(serviceDir, "index.ts");
418
- if (fs.existsSync(serviceDir)) {
419
- console.error(chalk.red(`ERROR: Service ${serviceName} already exists.`));
345
+ await import_fs_extra.default.mkdirp(mcpDir);
346
+ const serviceFile = import_path.default.join(mcpDir, `${serviceName}.ts`);
347
+ if (import_fs_extra.default.existsSync(serviceFile)) {
348
+ console.error(import_chalk.default.red(`ERROR: Service ${serviceName} already exists.`));
420
349
  process.exit(1);
421
350
  }
422
- await fs.mkdirp(serviceDir);
423
351
  const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
424
352
 
425
353
  // Input schema for greeting
@@ -479,12 +407,34 @@ export class ${capitalize(serviceName)}Service {
479
407
  }
480
408
  }
481
409
  `;
482
- await fs.writeFile(serviceFile, indexTs);
483
- console.log(chalk.green(`\\nCreated new service: ${chalk.bold(serviceName)}`));
484
- console.log(chalk.gray(` File: mcp/${serviceName}/index.ts`));
485
- console.log(chalk.gray(` Tool: greet`));
486
- console.log(chalk.gray(` Prompt: welcomePrompt`));
487
- console.log(chalk.gray(` Resource: getStatus`));
488
- console.log(chalk.green(`\\nService will be automatically discovered on next server start!`));
410
+ await import_fs_extra.default.writeFile(serviceFile, indexTs);
411
+ const mainTsPath = import_path.default.join(cwd, "main.ts");
412
+ let mainTsContent = await import_fs_extra.default.readFile(mainTsPath, "utf-8");
413
+ const serviceClassName = `${capitalize(serviceName)}Service`;
414
+ const importStatement = `import { ${serviceClassName} } from "./mcp/${serviceName}.js";`;
415
+ const registerStatement = ` server.registerService(new ${serviceClassName}());`;
416
+ const lastImportMatch = mainTsContent.match(/import .* from .*;\n/g);
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!`));
489
439
  });
490
440
  program.parse();