@leanmcp/cli 0.2.4 → 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,33 +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);
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");
21
39
  program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").action(async (projectName) => {
22
- const spinner = ora(`Creating project ${projectName}...`).start();
23
- const targetDir = path.join(process.cwd(), projectName);
24
- if (fs.existsSync(targetDir)) {
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)) {
25
43
  spinner.fail(`Folder ${projectName} already exists.`);
26
44
  process.exit(1);
27
45
  }
28
- await fs.mkdirp(targetDir);
29
- await fs.mkdirp(path.join(targetDir, "mcp", "example"));
30
- 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 = {
31
49
  name: projectName,
32
50
  version: "1.0.0",
33
51
  description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
@@ -48,16 +66,21 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
48
66
  author: "",
49
67
  license: "MIT",
50
68
  dependencies: {
51
- "@leanmcp/core": "^0.3.2",
52
- "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"
53
74
  },
54
75
  devDependencies: {
76
+ "@types/cors": "^2.8.19",
77
+ "@types/express": "^5.0.3",
55
78
  "@types/node": "^20.0.0",
56
79
  "tsx": "^4.20.3",
57
80
  "typescript": "^5.6.3"
58
81
  }
59
82
  };
60
- await fs.writeJSON(path.join(targetDir, "package.json"), pkg2, {
83
+ await import_fs_extra.default.writeJSON(import_path.default.join(targetDir, "package.json"), pkg, {
61
84
  spaces: 2
62
85
  });
63
86
  const tsconfig = {
@@ -80,11 +103,12 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
80
103
  "dist"
81
104
  ]
82
105
  };
83
- await fs.writeJSON(path.join(targetDir, "tsconfig.json"), tsconfig, {
106
+ await import_fs_extra.default.writeJSON(import_path.default.join(targetDir, "tsconfig.json"), tsconfig, {
84
107
  spaces: 2
85
108
  });
86
109
  const mainTs = `import dotenv from "dotenv";
87
110
  import { createHTTPServer, MCPServer } from "@leanmcp/core";
111
+ import { ExampleService } from "./mcp/example.js";
88
112
 
89
113
  // Load environment variables
90
114
  dotenv.config();
@@ -93,29 +117,27 @@ const PORT = Number(process.env.PORT) || 3001;
93
117
 
94
118
  /**
95
119
  * Create and configure the MCP server
96
- * Services are automatically discovered from ./mcp directory
97
120
  */
98
- const serverFactory = async () => {
121
+ function createMCPServer() {
99
122
  const server = new MCPServer({
100
123
  name: "${projectName}",
101
- version: "1.0.0",
102
- logging: true
124
+ version: "1.0.0"
103
125
  });
104
126
 
105
- // Services are automatically discovered and registered from ./mcp
127
+ // Register your services here
128
+ server.registerService(new ExampleService());
129
+
106
130
  return server.getServer();
107
- };
131
+ }
108
132
 
109
133
  // Start the HTTP server
110
- await createHTTPServer(serverFactory, {
134
+ await createHTTPServer(createMCPServer, {
111
135
  port: PORT,
112
136
  cors: true,
113
- logging: true // Log HTTP requests
137
+ logging: true
114
138
  });
115
-
116
- console.log(\`\\n${projectName} MCP Server\`);
117
139
  `;
118
- await fs.writeFile(path.join(targetDir, "main.ts"), mainTs);
140
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "main.ts"), mainTs);
119
141
  const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
120
142
 
121
143
  /**
@@ -141,38 +163,27 @@ class CalculateInput {
141
163
  operation?: string;
142
164
  }
143
165
 
144
- class EchoInput {
145
- @SchemaConstraint({
146
- description: "Message to echo back",
147
- minLength: 1
148
- })
149
- message!: string;
150
- }
151
-
152
166
  export class ExampleService {
153
167
  @Tool({
154
168
  description: "Perform arithmetic operations with automatic schema validation",
155
169
  inputClass: CalculateInput
156
170
  })
157
171
  async calculate(input: CalculateInput) {
158
- // Ensure numerical operations by explicitly converting to numbers
159
- const a = Number(input.a);
160
- const b = Number(input.b);
161
172
  let result: number;
162
173
 
163
174
  switch (input.operation || "add") {
164
175
  case "add":
165
- result = a + b;
176
+ result = input.a + input.b;
166
177
  break;
167
178
  case "subtract":
168
- result = a - b;
179
+ result = input.a - input.b;
169
180
  break;
170
181
  case "multiply":
171
- result = a * b;
182
+ result = input.a * input.b;
172
183
  break;
173
184
  case "divide":
174
- if (b === 0) throw new Error("Cannot divide by zero");
175
- result = a / b;
185
+ if (input.b === 0) throw new Error("Cannot divide by zero");
186
+ result = input.a / input.b;
176
187
  break;
177
188
  default:
178
189
  throw new Error("Invalid operation");
@@ -190,11 +201,8 @@ export class ExampleService {
190
201
  };
191
202
  }
192
203
 
193
- @Tool({
194
- description: "Echo a message back",
195
- inputClass: EchoInput
196
- })
197
- async echo(input: EchoInput) {
204
+ @Tool({ description: "Echo a message back" })
205
+ async echo(input: { message: string }) {
198
206
  return {
199
207
  content: [{
200
208
  type: "text" as const,
@@ -235,7 +243,7 @@ export class ExampleService {
235
243
  }
236
244
  }
237
245
  `;
238
- 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);
239
247
  const gitignore = `node_modules
240
248
  dist
241
249
  .env
@@ -248,8 +256,8 @@ NODE_ENV=development
248
256
 
249
257
  # Add your environment variables here
250
258
  `;
251
- await fs.writeFile(path.join(targetDir, ".gitignore"), gitignore);
252
- 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);
253
261
  const readme = `# ${projectName}
254
262
 
255
263
  MCP Server with Streamable HTTP Transport built with LeanMCP SDK
@@ -275,36 +283,22 @@ npm start
275
283
  \`\`\`
276
284
  ${projectName}/
277
285
  \u251C\u2500\u2500 main.ts # Server entry point
278
- \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
279
- \u2502 \u2514\u2500\u2500 example/
280
- \u2502 \u2514\u2500\u2500 index.ts # Example service
286
+ \u251C\u2500\u2500 mcp/
287
+ \u2502 \u2514\u2500\u2500 example.ts # Example service
281
288
  \u251C\u2500\u2500 .env # Environment variables
282
289
  \u2514\u2500\u2500 package.json
283
290
  \`\`\`
284
291
 
285
292
  ## Adding New Services
286
293
 
287
- Create a new service directory in \`mcp/\`:
294
+ Create a new service file in \`mcp/\`:
288
295
 
289
296
  \`\`\`typescript
290
- // mcp/myservice/index.ts
291
- import { Tool, SchemaConstraint } from "@leanmcp/core";
292
-
293
- // Define input schema
294
- class MyToolInput {
295
- @SchemaConstraint({
296
- description: "Message to process",
297
- minLength: 1
298
- })
299
- message!: string;
300
- }
297
+ import { Tool } from "@leanmcp/core";
301
298
 
302
299
  export class MyService {
303
- @Tool({
304
- description: "My awesome tool",
305
- inputClass: MyToolInput
306
- })
307
- async myTool(input: MyToolInput) {
300
+ @Tool({ description: "My awesome tool" })
301
+ async myTool(input: { message: string }) {
308
302
  return {
309
303
  content: [{
310
304
  type: "text",
@@ -315,15 +309,12 @@ export class MyService {
315
309
  }
316
310
  \`\`\`
317
311
 
318
- Services are automatically discovered and registered - no need to modify \`main.ts\`!
312
+ Then register it in \`main.ts\`:
319
313
 
320
- ## Features
321
-
322
- - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
323
- - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
324
- - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
325
- - **HTTP transport** - Production-ready HTTP server with session management
326
- - **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
+ \`\`\`
327
318
 
328
319
  ## Testing with MCP Inspector
329
320
 
@@ -335,88 +326,28 @@ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
335
326
 
336
327
  MIT
337
328
  `;
338
- await fs.writeFile(path.join(targetDir, "README.md"), readme);
329
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "README.md"), readme);
339
330
  spinner.succeed(`Project ${projectName} created!`);
340
- console.log(chalk.green("\nSuccess! Your MCP server is ready.\n"));
341
- console.log(chalk.cyan(`Next, navigate to your project:
342
- cd ${projectName}
343
- `));
344
- const shouldInstall = await confirm({
345
- message: "Would you like to install dependencies now?",
346
- default: true
347
- });
348
- if (shouldInstall) {
349
- const installSpinner = ora("Installing dependencies...").start();
350
- try {
351
- await new Promise((resolve, reject) => {
352
- const npmInstall = spawn("npm", [
353
- "install"
354
- ], {
355
- cwd: targetDir,
356
- stdio: "pipe",
357
- shell: true
358
- });
359
- npmInstall.on("close", (code) => {
360
- if (code === 0) {
361
- resolve();
362
- } else {
363
- reject(new Error(`npm install failed with code ${code}`));
364
- }
365
- });
366
- npmInstall.on("error", reject);
367
- });
368
- installSpinner.succeed("Dependencies installed successfully!");
369
- const shouldStartDev = await confirm({
370
- message: "Would you like to start the development server?",
371
- default: true
372
- });
373
- if (shouldStartDev) {
374
- console.log(chalk.cyan("\nStarting development server...\n"));
375
- const devServer = spawn("npm", [
376
- "run",
377
- "dev"
378
- ], {
379
- cwd: targetDir,
380
- stdio: "inherit",
381
- shell: true
382
- });
383
- process.on("SIGINT", () => {
384
- devServer.kill();
385
- process.exit(0);
386
- });
387
- } else {
388
- console.log(chalk.cyan("\nTo start the development server later:"));
389
- console.log(chalk.gray(` cd ${projectName}`));
390
- console.log(chalk.gray(` npm run dev`));
391
- }
392
- } catch (error) {
393
- installSpinner.fail("Failed to install dependencies");
394
- console.error(chalk.red(error instanceof Error ? error.message : String(error)));
395
- console.log(chalk.cyan("\nYou can install dependencies manually:"));
396
- console.log(chalk.gray(` cd ${projectName}`));
397
- console.log(chalk.gray(` npm install`));
398
- }
399
- } else {
400
- console.log(chalk.cyan("\nTo get started:"));
401
- console.log(chalk.gray(` cd ${projectName}`));
402
- console.log(chalk.gray(` npm install`));
403
- console.log(chalk.gray(` npm run dev`));
404
- }
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`));
405
337
  });
406
338
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
407
339
  const cwd = process.cwd();
408
- const mcpDir = path.join(cwd, "mcp");
409
- if (!fs.existsSync(path.join(cwd, "main.ts"))) {
410
- 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)."));
411
343
  process.exit(1);
412
344
  }
413
- const serviceDir = path.join(mcpDir, serviceName);
414
- const serviceFile = path.join(serviceDir, "index.ts");
415
- if (fs.existsSync(serviceDir)) {
416
- 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.`));
417
349
  process.exit(1);
418
350
  }
419
- await fs.mkdirp(serviceDir);
420
351
  const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
421
352
 
422
353
  // Input schema for greeting
@@ -476,12 +407,34 @@ export class ${capitalize(serviceName)}Service {
476
407
  }
477
408
  }
478
409
  `;
479
- await fs.writeFile(serviceFile, indexTs);
480
- console.log(chalk.green(`\\nCreated new service: ${chalk.bold(serviceName)}`));
481
- console.log(chalk.gray(` File: mcp/${serviceName}/index.ts`));
482
- console.log(chalk.gray(` Tool: greet`));
483
- console.log(chalk.gray(` Prompt: welcomePrompt`));
484
- console.log(chalk.gray(` Resource: getStatus`));
485
- 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!`));
486
439
  });
487
440
  program.parse();