@leanmcp/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -44,7 +44,7 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
44
44
  process.exit(1);
45
45
  }
46
46
  await import_fs_extra.default.mkdirp(targetDir);
47
- await import_fs_extra.default.mkdirp(import_path.default.join(targetDir, "mcp"));
47
+ await import_fs_extra.default.mkdirp(import_path.default.join(targetDir, "mcp", "example"));
48
48
  const pkg = {
49
49
  name: projectName,
50
50
  version: "1.0.0",
@@ -66,15 +66,10 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
66
66
  author: "",
67
67
  license: "MIT",
68
68
  dependencies: {
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"
69
+ "@leanmcp/core": "^0.2.0",
70
+ "dotenv": "^16.5.0"
74
71
  },
75
72
  devDependencies: {
76
- "@types/cors": "^2.8.19",
77
- "@types/express": "^5.0.3",
78
73
  "@types/node": "^20.0.0",
79
74
  "tsx": "^4.20.3",
80
75
  "typescript": "^5.6.3"
@@ -108,7 +103,6 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
108
103
  });
109
104
  const mainTs = `import dotenv from "dotenv";
110
105
  import { createHTTPServer, MCPServer } from "@leanmcp/core";
111
- import { ExampleService } from "./mcp/example.js";
112
106
 
113
107
  // Load environment variables
114
108
  dotenv.config();
@@ -117,25 +111,29 @@ const PORT = Number(process.env.PORT) || 3001;
117
111
 
118
112
  /**
119
113
  * Create and configure the MCP server
114
+ * Services are automatically discovered from ./mcp directory
120
115
  */
121
- function createMCPServer() {
116
+ const serverFactory = async () => {
122
117
  const server = new MCPServer({
123
118
  name: "${projectName}",
124
- version: "1.0.0"
119
+ version: "1.0.0",
120
+ logging: true
125
121
  });
126
122
 
127
- // Register your services here
128
- server.registerService(new ExampleService());
129
-
123
+ // Services are automatically discovered and registered from ./mcp
130
124
  return server.getServer();
131
- }
125
+ };
132
126
 
133
127
  // Start the HTTP server
134
- await createHTTPServer(createMCPServer, {
128
+ await createHTTPServer(serverFactory, {
135
129
  port: PORT,
136
130
  cors: true,
137
- logging: true
131
+ logging: true // Log HTTP requests
138
132
  });
133
+
134
+ console.log(\`\\n${projectName} MCP Server\`);
135
+ console.log(\`HTTP endpoint: http://localhost:\${PORT}/mcp\`);
136
+ console.log(\`Health check: http://localhost:\${PORT}/health\`);
139
137
  `;
140
138
  await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "main.ts"), mainTs);
141
139
  const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
@@ -243,7 +241,7 @@ export class ExampleService {
243
241
  }
244
242
  }
245
243
  `;
246
- await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "mcp", "example.ts"), exampleServiceTs);
244
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
247
245
  const gitignore = `node_modules
248
246
  dist
249
247
  .env
@@ -283,22 +281,36 @@ npm start
283
281
  \`\`\`
284
282
  ${projectName}/
285
283
  \u251C\u2500\u2500 main.ts # Server entry point
286
- \u251C\u2500\u2500 mcp/
287
- \u2502 \u2514\u2500\u2500 example.ts # Example service
284
+ \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
285
+ \u2502 \u2514\u2500\u2500 example/
286
+ \u2502 \u2514\u2500\u2500 index.ts # Example service
288
287
  \u251C\u2500\u2500 .env # Environment variables
289
288
  \u2514\u2500\u2500 package.json
290
289
  \`\`\`
291
290
 
292
291
  ## Adding New Services
293
292
 
294
- Create a new service file in \`mcp/\`:
293
+ Create a new service directory in \`mcp/\`:
295
294
 
296
295
  \`\`\`typescript
297
- import { Tool } from "@leanmcp/core";
296
+ // mcp/myservice/index.ts
297
+ import { Tool, SchemaConstraint } from "@leanmcp/core";
298
+
299
+ // Define input schema
300
+ class MyToolInput {
301
+ @SchemaConstraint({
302
+ description: "Message to process",
303
+ minLength: 1
304
+ })
305
+ message!: string;
306
+ }
298
307
 
299
308
  export class MyService {
300
- @Tool({ description: "My awesome tool" })
301
- async myTool(input: { message: string }) {
309
+ @Tool({
310
+ description: "My awesome tool",
311
+ inputClass: MyToolInput
312
+ })
313
+ async myTool(input: MyToolInput) {
302
314
  return {
303
315
  content: [{
304
316
  type: "text",
@@ -309,12 +321,15 @@ export class MyService {
309
321
  }
310
322
  \`\`\`
311
323
 
312
- Then register it in \`main.ts\`:
324
+ Services are automatically discovered and registered - no need to modify \`main.ts\`!
313
325
 
314
- \`\`\`typescript
315
- import { MyService } from "./mcp/my-service.js";
316
- server.registerService(new MyService());
317
- \`\`\`
326
+ ## Features
327
+
328
+ - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
329
+ - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
330
+ - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
331
+ - **HTTP transport** - Production-ready HTTP server with session management
332
+ - **Hot reload** - Development mode with automatic restart on file changes
318
333
 
319
334
  ## Testing with MCP Inspector
320
335
 
@@ -342,12 +357,13 @@ program.command("add <serviceName>").description("Add a new MCP service to your
342
357
  console.error(import_chalk.default.red("ERROR: Not a LeanMCP project (main.ts missing)."));
343
358
  process.exit(1);
344
359
  }
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)) {
360
+ const serviceDir = import_path.default.join(mcpDir, serviceName);
361
+ const serviceFile = import_path.default.join(serviceDir, "index.ts");
362
+ if (import_fs_extra.default.existsSync(serviceDir)) {
348
363
  console.error(import_chalk.default.red(`ERROR: Service ${serviceName} already exists.`));
349
364
  process.exit(1);
350
365
  }
366
+ await import_fs_extra.default.mkdirp(serviceDir);
351
367
  const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
352
368
 
353
369
  // Input schema for greeting
@@ -408,33 +424,11 @@ export class ${capitalize(serviceName)}Service {
408
424
  }
409
425
  `;
410
426
  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
427
  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`));
428
+ console.log(import_chalk.default.gray(` File: mcp/${serviceName}/index.ts`));
435
429
  console.log(import_chalk.default.gray(` Tool: greet`));
436
430
  console.log(import_chalk.default.gray(` Prompt: welcomePrompt`));
437
431
  console.log(import_chalk.default.gray(` Resource: getStatus`));
438
- console.log(import_chalk.default.green(`\\nService automatically registered in main.ts!`));
432
+ console.log(import_chalk.default.green(`\\nService will be automatically discovered on next server start!`));
439
433
  });
440
434
  program.parse();
package/dist/index.mjs CHANGED
@@ -22,7 +22,7 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
22
22
  process.exit(1);
23
23
  }
24
24
  await fs.mkdirp(targetDir);
25
- await fs.mkdirp(path.join(targetDir, "mcp"));
25
+ await fs.mkdirp(path.join(targetDir, "mcp", "example"));
26
26
  const pkg = {
27
27
  name: projectName,
28
28
  version: "1.0.0",
@@ -44,15 +44,10 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
44
44
  author: "",
45
45
  license: "MIT",
46
46
  dependencies: {
47
- "@leanmcp/core": "^0.1.0",
48
- "@modelcontextprotocol/sdk": "^1.0.0",
49
- "cors": "^2.8.5",
50
- "dotenv": "^16.5.0",
51
- "express": "^5.1.0"
47
+ "@leanmcp/core": "^0.2.0",
48
+ "dotenv": "^16.5.0"
52
49
  },
53
50
  devDependencies: {
54
- "@types/cors": "^2.8.19",
55
- "@types/express": "^5.0.3",
56
51
  "@types/node": "^20.0.0",
57
52
  "tsx": "^4.20.3",
58
53
  "typescript": "^5.6.3"
@@ -86,7 +81,6 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
86
81
  });
87
82
  const mainTs = `import dotenv from "dotenv";
88
83
  import { createHTTPServer, MCPServer } from "@leanmcp/core";
89
- import { ExampleService } from "./mcp/example.js";
90
84
 
91
85
  // Load environment variables
92
86
  dotenv.config();
@@ -95,25 +89,29 @@ const PORT = Number(process.env.PORT) || 3001;
95
89
 
96
90
  /**
97
91
  * Create and configure the MCP server
92
+ * Services are automatically discovered from ./mcp directory
98
93
  */
99
- function createMCPServer() {
94
+ const serverFactory = async () => {
100
95
  const server = new MCPServer({
101
96
  name: "${projectName}",
102
- version: "1.0.0"
97
+ version: "1.0.0",
98
+ logging: true
103
99
  });
104
100
 
105
- // Register your services here
106
- server.registerService(new ExampleService());
107
-
101
+ // Services are automatically discovered and registered from ./mcp
108
102
  return server.getServer();
109
- }
103
+ };
110
104
 
111
105
  // Start the HTTP server
112
- await createHTTPServer(createMCPServer, {
106
+ await createHTTPServer(serverFactory, {
113
107
  port: PORT,
114
108
  cors: true,
115
- logging: true
109
+ logging: true // Log HTTP requests
116
110
  });
111
+
112
+ console.log(\`\\n${projectName} MCP Server\`);
113
+ console.log(\`HTTP endpoint: http://localhost:\${PORT}/mcp\`);
114
+ console.log(\`Health check: http://localhost:\${PORT}/health\`);
117
115
  `;
118
116
  await fs.writeFile(path.join(targetDir, "main.ts"), mainTs);
119
117
  const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
@@ -221,7 +219,7 @@ export class ExampleService {
221
219
  }
222
220
  }
223
221
  `;
224
- await fs.writeFile(path.join(targetDir, "mcp", "example.ts"), exampleServiceTs);
222
+ await fs.writeFile(path.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
225
223
  const gitignore = `node_modules
226
224
  dist
227
225
  .env
@@ -261,22 +259,36 @@ npm start
261
259
  \`\`\`
262
260
  ${projectName}/
263
261
  \u251C\u2500\u2500 main.ts # Server entry point
264
- \u251C\u2500\u2500 mcp/
265
- \u2502 \u2514\u2500\u2500 example.ts # Example service
262
+ \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
263
+ \u2502 \u2514\u2500\u2500 example/
264
+ \u2502 \u2514\u2500\u2500 index.ts # Example service
266
265
  \u251C\u2500\u2500 .env # Environment variables
267
266
  \u2514\u2500\u2500 package.json
268
267
  \`\`\`
269
268
 
270
269
  ## Adding New Services
271
270
 
272
- Create a new service file in \`mcp/\`:
271
+ Create a new service directory in \`mcp/\`:
273
272
 
274
273
  \`\`\`typescript
275
- import { Tool } from "@leanmcp/core";
274
+ // mcp/myservice/index.ts
275
+ import { Tool, SchemaConstraint } from "@leanmcp/core";
276
+
277
+ // Define input schema
278
+ class MyToolInput {
279
+ @SchemaConstraint({
280
+ description: "Message to process",
281
+ minLength: 1
282
+ })
283
+ message!: string;
284
+ }
276
285
 
277
286
  export class MyService {
278
- @Tool({ description: "My awesome tool" })
279
- async myTool(input: { message: string }) {
287
+ @Tool({
288
+ description: "My awesome tool",
289
+ inputClass: MyToolInput
290
+ })
291
+ async myTool(input: MyToolInput) {
280
292
  return {
281
293
  content: [{
282
294
  type: "text",
@@ -287,12 +299,15 @@ export class MyService {
287
299
  }
288
300
  \`\`\`
289
301
 
290
- Then register it in \`main.ts\`:
302
+ Services are automatically discovered and registered - no need to modify \`main.ts\`!
291
303
 
292
- \`\`\`typescript
293
- import { MyService } from "./mcp/my-service.js";
294
- server.registerService(new MyService());
295
- \`\`\`
304
+ ## Features
305
+
306
+ - **Zero-config auto-discovery** - Services automatically registered from \`./mcp\` directory
307
+ - **Type-safe decorators** - \`@Tool\`, \`@Prompt\`, \`@Resource\` with full TypeScript support
308
+ - **Schema validation** - Automatic input validation with \`@SchemaConstraint\`
309
+ - **HTTP transport** - Production-ready HTTP server with session management
310
+ - **Hot reload** - Development mode with automatic restart on file changes
296
311
 
297
312
  ## Testing with MCP Inspector
298
313
 
@@ -320,12 +335,13 @@ program.command("add <serviceName>").description("Add a new MCP service to your
320
335
  console.error(chalk.red("ERROR: Not a LeanMCP project (main.ts missing)."));
321
336
  process.exit(1);
322
337
  }
323
- await fs.mkdirp(mcpDir);
324
- const serviceFile = path.join(mcpDir, `${serviceName}.ts`);
325
- if (fs.existsSync(serviceFile)) {
338
+ const serviceDir = path.join(mcpDir, serviceName);
339
+ const serviceFile = path.join(serviceDir, "index.ts");
340
+ if (fs.existsSync(serviceDir)) {
326
341
  console.error(chalk.red(`ERROR: Service ${serviceName} already exists.`));
327
342
  process.exit(1);
328
343
  }
344
+ await fs.mkdirp(serviceDir);
329
345
  const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
330
346
 
331
347
  // Input schema for greeting
@@ -386,33 +402,11 @@ export class ${capitalize(serviceName)}Service {
386
402
  }
387
403
  `;
388
404
  await fs.writeFile(serviceFile, indexTs);
389
- const mainTsPath = path.join(cwd, "main.ts");
390
- let mainTsContent = await fs.readFile(mainTsPath, "utf-8");
391
- const serviceClassName = `${capitalize(serviceName)}Service`;
392
- const importStatement = `import { ${serviceClassName} } from "./mcp/${serviceName}.js";`;
393
- const registerStatement = ` server.registerService(new ${serviceClassName}());`;
394
- const lastImportMatch = mainTsContent.match(/import .* from .*;\n/g);
395
- if (lastImportMatch) {
396
- const lastImport = lastImportMatch[lastImportMatch.length - 1];
397
- const lastImportIndex = mainTsContent.lastIndexOf(lastImport);
398
- const afterLastImport = lastImportIndex + lastImport.length;
399
- mainTsContent = mainTsContent.slice(0, afterLastImport) + importStatement + "\n" + mainTsContent.slice(afterLastImport);
400
- }
401
- const registerPattern = /server\.registerService\(new \w+\(\)\);/g;
402
- const matches = [
403
- ...mainTsContent.matchAll(registerPattern)
404
- ];
405
- if (matches.length > 0) {
406
- const lastMatch = matches[matches.length - 1];
407
- const insertPosition = lastMatch.index + lastMatch[0].length;
408
- mainTsContent = mainTsContent.slice(0, insertPosition) + "\n" + registerStatement + mainTsContent.slice(insertPosition);
409
- }
410
- await fs.writeFile(mainTsPath, mainTsContent);
411
405
  console.log(chalk.green(`\\nCreated new service: ${chalk.bold(serviceName)}`));
412
- console.log(chalk.gray(` File: mcp/${serviceName}.ts`));
406
+ console.log(chalk.gray(` File: mcp/${serviceName}/index.ts`));
413
407
  console.log(chalk.gray(` Tool: greet`));
414
408
  console.log(chalk.gray(` Prompt: welcomePrompt`));
415
409
  console.log(chalk.gray(` Resource: getStatus`));
416
- console.log(chalk.green(`\\nService automatically registered in main.ts!`));
410
+ console.log(chalk.green(`\\nService will be automatically discovered on next server start!`));
417
411
  });
418
412
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Command-line interface for scaffolding LeanMCP projects",
5
5
  "bin": {
6
6
  "leanmcp": "dist/index.js"