@leanmcp/cli 0.1.1 → 0.2.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/README.md CHANGED
@@ -4,11 +4,13 @@ Command-line tool for creating LeanMCP projects with production-ready templates.
4
4
 
5
5
  ## Features
6
6
 
7
+ - **Interactive setup** - Guided prompts for dependency installation and dev server
7
8
  - **Quick project scaffolding** - Create new MCP servers in seconds
8
9
  - **Complete setup** - Includes TypeScript, dependencies, and configuration
9
10
  - **Best practices** - Generated projects follow MCP standards
10
11
  - **Ready to run** - Start developing immediately with hot reload
11
12
  - **Example service** - Includes working examples to get started
13
+ - **Pure ESM** - Modern ES modules with full TypeScript support
12
14
 
13
15
  ## Installation
14
16
 
@@ -41,16 +43,25 @@ npx @leanmcp/cli create my-mcp-server
41
43
 
42
44
  ```bash
43
45
  $ leanmcp create my-sentiment-tool
44
- Creating project my-sentiment-tool...
46
+ Project my-sentiment-tool created!
45
47
 
46
- Project created successfully!
48
+ Success! Your MCP server is ready.
47
49
 
48
- Next steps:
50
+ Next, navigate to your project:
49
51
  cd my-sentiment-tool
50
- npm install
51
- npm run dev
52
52
 
53
- Your MCP server will be running on http://localhost:3001
53
+ ? Would you like to install dependencies now? (Y/n) Yes
54
+ ✔ Dependencies installed successfully!
55
+ ? Would you like to start the development server? (Y/n) Yes
56
+
57
+ Starting development server...
58
+
59
+ > my-sentiment-tool@1.0.0 dev
60
+ > tsx watch main.ts
61
+
62
+ [HTTP][INFO] Starting LeanMCP HTTP Server...
63
+ [HTTP][INFO] Server running on http://localhost:3001
64
+ [HTTP][INFO] MCP endpoint: http://localhost:3001/mcp
54
65
  ```
55
66
 
56
67
  ### Add a New Service
@@ -143,22 +154,49 @@ npm run clean # Remove build artifacts
143
154
 
144
155
  ## Development Workflow
145
156
 
146
- After creating a project:
157
+ ### Interactive Setup (Recommended)
158
+
159
+ The CLI provides an interactive setup experience:
147
160
 
148
161
  ```bash
149
- # 1. Install dependencies
162
+ # Create project
163
+ leanmcp create my-mcp-server
164
+
165
+ # The CLI will:
166
+ # 1. Create project structure
167
+ # 2. Ask if you want to install dependencies (Y/n)
168
+ # 3. If yes, ask if you want to start dev server (Y/n)
169
+ # 4. If yes, start server with hot reload
170
+
171
+ # If you choose "No" to installation:
150
172
  cd my-mcp-server
151
173
  npm install
174
+ npm run dev
175
+ ```
176
+
177
+ ### Manual Setup
152
178
 
153
- # 2. Start development server
179
+ If you prefer manual control:
180
+
181
+ ```bash
182
+ # 1. Create project (answer "No" to prompts)
183
+ leanmcp create my-mcp-server
184
+
185
+ # 2. Navigate to project
186
+ cd my-mcp-server
187
+
188
+ # 3. Install dependencies
189
+ npm install
190
+
191
+ # 4. Start development server
154
192
  npm run dev
155
193
 
156
- # 3. Server starts on http://localhost:3001
194
+ # 5. Server starts on http://localhost:3001
157
195
  # - Endpoint: http://localhost:3001/mcp
158
196
  # - Health check: http://localhost:3001/health
159
197
  # - Hot reload enabled
160
198
 
161
- # 4. Edit files in mcp/ directory
199
+ # 6. Edit files in mcp/ directory
162
200
  # Server automatically reloads on changes
163
201
  ```
164
202
 
@@ -342,6 +380,11 @@ Creates a complete MCP server project with:
342
380
  - Package.json with all dependencies
343
381
  - Development and build scripts
344
382
 
383
+ **Interactive Prompts:**
384
+ - Asks if you want to install dependencies
385
+ - If installed, asks if you want to start dev server
386
+ - Runs commands in the project directory automatically
387
+
345
388
  #### `add <service-name>`
346
389
  Adds a new service to an existing project:
347
390
  - Must be run inside a LeanMCP project directory
package/bin/leanmcp.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Always prefer ESM build
4
+ import('../dist/index.js');
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
- #!/usr/bin/env node
1
+
2
+ export { }
package/dist/index.js CHANGED
@@ -1,51 +1,33 @@
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
- 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
- ));
26
3
 
27
4
  // src/index.ts
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"));
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");
33
15
  function capitalize(str) {
34
16
  return str.charAt(0).toUpperCase() + str.slice(1);
35
17
  }
36
18
  __name(capitalize, "capitalize");
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");
19
+ var program = new Command();
20
+ program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(pkg.version);
39
21
  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)) {
22
+ const spinner = ora(`Creating project ${projectName}...`).start();
23
+ const targetDir = path.join(process.cwd(), projectName);
24
+ if (fs.existsSync(targetDir)) {
43
25
  spinner.fail(`Folder ${projectName} already exists.`);
44
26
  process.exit(1);
45
27
  }
46
- await import_fs_extra.default.mkdirp(targetDir);
47
- await import_fs_extra.default.mkdirp(import_path.default.join(targetDir, "mcp", "example"));
48
- const pkg = {
28
+ await fs.mkdirp(targetDir);
29
+ await fs.mkdirp(path.join(targetDir, "mcp", "example"));
30
+ const pkg2 = {
49
31
  name: projectName,
50
32
  version: "1.0.0",
51
33
  description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
@@ -75,7 +57,7 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
75
57
  "typescript": "^5.6.3"
76
58
  }
77
59
  };
78
- await import_fs_extra.default.writeJSON(import_path.default.join(targetDir, "package.json"), pkg, {
60
+ await fs.writeJSON(path.join(targetDir, "package.json"), pkg2, {
79
61
  spaces: 2
80
62
  });
81
63
  const tsconfig = {
@@ -98,7 +80,7 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
98
80
  "dist"
99
81
  ]
100
82
  };
101
- await import_fs_extra.default.writeJSON(import_path.default.join(targetDir, "tsconfig.json"), tsconfig, {
83
+ await fs.writeJSON(path.join(targetDir, "tsconfig.json"), tsconfig, {
102
84
  spaces: 2
103
85
  });
104
86
  const mainTs = `import dotenv from "dotenv";
@@ -135,7 +117,7 @@ console.log(\`\\n${projectName} MCP Server\`);
135
117
  console.log(\`HTTP endpoint: http://localhost:\${PORT}/mcp\`);
136
118
  console.log(\`Health check: http://localhost:\${PORT}/health\`);
137
119
  `;
138
- await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "main.ts"), mainTs);
120
+ await fs.writeFile(path.join(targetDir, "main.ts"), mainTs);
139
121
  const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
140
122
 
141
123
  /**
@@ -161,27 +143,38 @@ class CalculateInput {
161
143
  operation?: string;
162
144
  }
163
145
 
146
+ class EchoInput {
147
+ @SchemaConstraint({
148
+ description: "Message to echo back",
149
+ minLength: 1
150
+ })
151
+ message!: string;
152
+ }
153
+
164
154
  export class ExampleService {
165
155
  @Tool({
166
156
  description: "Perform arithmetic operations with automatic schema validation",
167
157
  inputClass: CalculateInput
168
158
  })
169
159
  async calculate(input: CalculateInput) {
160
+ // Ensure numerical operations by explicitly converting to numbers
161
+ const a = Number(input.a);
162
+ const b = Number(input.b);
170
163
  let result: number;
171
164
 
172
165
  switch (input.operation || "add") {
173
166
  case "add":
174
- result = input.a + input.b;
167
+ result = a + b;
175
168
  break;
176
169
  case "subtract":
177
- result = input.a - input.b;
170
+ result = a - b;
178
171
  break;
179
172
  case "multiply":
180
- result = input.a * input.b;
173
+ result = a * b;
181
174
  break;
182
175
  case "divide":
183
- if (input.b === 0) throw new Error("Cannot divide by zero");
184
- result = input.a / input.b;
176
+ if (b === 0) throw new Error("Cannot divide by zero");
177
+ result = a / b;
185
178
  break;
186
179
  default:
187
180
  throw new Error("Invalid operation");
@@ -199,8 +192,11 @@ export class ExampleService {
199
192
  };
200
193
  }
201
194
 
202
- @Tool({ description: "Echo a message back" })
203
- async echo(input: { message: string }) {
195
+ @Tool({
196
+ description: "Echo a message back",
197
+ inputClass: EchoInput
198
+ })
199
+ async echo(input: EchoInput) {
204
200
  return {
205
201
  content: [{
206
202
  type: "text" as const,
@@ -241,7 +237,7 @@ export class ExampleService {
241
237
  }
242
238
  }
243
239
  `;
244
- await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
240
+ await fs.writeFile(path.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
245
241
  const gitignore = `node_modules
246
242
  dist
247
243
  .env
@@ -254,8 +250,8 @@ NODE_ENV=development
254
250
 
255
251
  # Add your environment variables here
256
252
  `;
257
- await import_fs_extra.default.writeFile(import_path.default.join(targetDir, ".gitignore"), gitignore);
258
- await import_fs_extra.default.writeFile(import_path.default.join(targetDir, ".env"), env);
253
+ await fs.writeFile(path.join(targetDir, ".gitignore"), gitignore);
254
+ await fs.writeFile(path.join(targetDir, ".env"), env);
259
255
  const readme = `# ${projectName}
260
256
 
261
257
  MCP Server with Streamable HTTP Transport built with LeanMCP SDK
@@ -341,29 +337,88 @@ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
341
337
 
342
338
  MIT
343
339
  `;
344
- await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "README.md"), readme);
340
+ await fs.writeFile(path.join(targetDir, "README.md"), readme);
345
341
  spinner.succeed(`Project ${projectName} created!`);
346
- console.log(import_chalk.default.green("\\nSuccess! Your MCP server is ready.\\n"));
347
- console.log(import_chalk.default.cyan("Next steps:"));
348
- console.log(import_chalk.default.gray(` cd ${projectName}`));
349
- console.log(import_chalk.default.gray(` npm install`));
350
- console.log(import_chalk.default.gray(` npm run dev`));
351
- console.log(import_chalk.default.gray(`\\nServer will run on http://localhost:3001`));
342
+ console.log(chalk.green("\nSuccess! Your MCP server is ready.\n"));
343
+ console.log(chalk.cyan(`Next, navigate to your project:
344
+ cd ${projectName}
345
+ `));
346
+ const shouldInstall = await confirm({
347
+ message: "Would you like to install dependencies now?",
348
+ default: true
349
+ });
350
+ if (shouldInstall) {
351
+ const installSpinner = ora("Installing dependencies...").start();
352
+ try {
353
+ await new Promise((resolve, reject) => {
354
+ const npmInstall = spawn("npm", [
355
+ "install"
356
+ ], {
357
+ cwd: targetDir,
358
+ stdio: "pipe",
359
+ shell: true
360
+ });
361
+ npmInstall.on("close", (code) => {
362
+ if (code === 0) {
363
+ resolve();
364
+ } else {
365
+ reject(new Error(`npm install failed with code ${code}`));
366
+ }
367
+ });
368
+ npmInstall.on("error", reject);
369
+ });
370
+ installSpinner.succeed("Dependencies installed successfully!");
371
+ const shouldStartDev = await confirm({
372
+ message: "Would you like to start the development server?",
373
+ default: true
374
+ });
375
+ if (shouldStartDev) {
376
+ console.log(chalk.cyan("\nStarting development server...\n"));
377
+ const devServer = spawn("npm", [
378
+ "run",
379
+ "dev"
380
+ ], {
381
+ cwd: targetDir,
382
+ stdio: "inherit",
383
+ shell: true
384
+ });
385
+ process.on("SIGINT", () => {
386
+ devServer.kill();
387
+ process.exit(0);
388
+ });
389
+ } else {
390
+ console.log(chalk.cyan("\nTo start the development server later:"));
391
+ console.log(chalk.gray(` cd ${projectName}`));
392
+ console.log(chalk.gray(` npm run dev`));
393
+ }
394
+ } catch (error) {
395
+ installSpinner.fail("Failed to install dependencies");
396
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
397
+ console.log(chalk.cyan("\nYou can install dependencies manually:"));
398
+ console.log(chalk.gray(` cd ${projectName}`));
399
+ console.log(chalk.gray(` npm install`));
400
+ }
401
+ } else {
402
+ console.log(chalk.cyan("\nTo get started:"));
403
+ console.log(chalk.gray(` cd ${projectName}`));
404
+ console.log(chalk.gray(` npm install`));
405
+ console.log(chalk.gray(` npm run dev`));
406
+ }
352
407
  });
353
408
  program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
354
409
  const cwd = process.cwd();
355
- const mcpDir = import_path.default.join(cwd, "mcp");
356
- if (!import_fs_extra.default.existsSync(import_path.default.join(cwd, "main.ts"))) {
357
- console.error(import_chalk.default.red("ERROR: Not a LeanMCP project (main.ts missing)."));
410
+ const mcpDir = path.join(cwd, "mcp");
411
+ if (!fs.existsSync(path.join(cwd, "main.ts"))) {
412
+ console.error(chalk.red("ERROR: Not a LeanMCP project (main.ts missing)."));
358
413
  process.exit(1);
359
414
  }
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)) {
363
- console.error(import_chalk.default.red(`ERROR: Service ${serviceName} already exists.`));
415
+ const serviceDir = path.join(mcpDir, serviceName);
416
+ const serviceFile = path.join(serviceDir, "index.ts");
417
+ if (fs.existsSync(serviceDir)) {
418
+ console.error(chalk.red(`ERROR: Service ${serviceName} already exists.`));
364
419
  process.exit(1);
365
420
  }
366
- await import_fs_extra.default.mkdirp(serviceDir);
421
+ await fs.mkdirp(serviceDir);
367
422
  const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
368
423
 
369
424
  // Input schema for greeting
@@ -423,12 +478,12 @@ export class ${capitalize(serviceName)}Service {
423
478
  }
424
479
  }
425
480
  `;
426
- await import_fs_extra.default.writeFile(serviceFile, indexTs);
427
- console.log(import_chalk.default.green(`\\nCreated new service: ${import_chalk.default.bold(serviceName)}`));
428
- console.log(import_chalk.default.gray(` File: mcp/${serviceName}/index.ts`));
429
- console.log(import_chalk.default.gray(` Tool: greet`));
430
- console.log(import_chalk.default.gray(` Prompt: welcomePrompt`));
431
- console.log(import_chalk.default.gray(` Resource: getStatus`));
432
- console.log(import_chalk.default.green(`\\nService will be automatically discovered on next server start!`));
481
+ await fs.writeFile(serviceFile, indexTs);
482
+ console.log(chalk.green(`\\nCreated new service: ${chalk.bold(serviceName)}`));
483
+ console.log(chalk.gray(` File: mcp/${serviceName}/index.ts`));
484
+ console.log(chalk.gray(` Tool: greet`));
485
+ console.log(chalk.gray(` Prompt: welcomePrompt`));
486
+ console.log(chalk.gray(` Resource: getStatus`));
487
+ console.log(chalk.green(`\\nService will be automatically discovered on next server start!`));
433
488
  });
434
489
  program.parse();
package/package.json CHANGED
@@ -1,38 +1,41 @@
1
1
  {
2
2
  "name": "@leanmcp/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Command-line interface for scaffolding LeanMCP projects",
5
5
  "bin": {
6
- "leanmcp": "dist/index.js"
6
+ "leanmcp": "bin/leanmcp.js"
7
7
  },
8
+ "type": "module",
8
9
  "main": "dist/index.js",
9
- "module": "dist/index.mjs",
10
10
  "types": "dist/index.d.ts",
11
11
  "exports": {
12
12
  ".": {
13
13
  "types": "./dist/index.d.ts",
14
- "require": "./dist/index.js",
15
- "import": "./dist/index.mjs"
14
+ "import": "./dist/index.js"
16
15
  }
17
16
  },
18
17
  "files": [
18
+ "bin",
19
19
  "dist",
20
20
  "README.md",
21
21
  "LICENSE"
22
22
  ],
23
23
  "scripts": {
24
- "build": "tsup src/index.ts --format esm,cjs --dts",
25
- "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
26
- "test": "echo \"No tests yet\" && exit 0"
24
+ "build": "tsup src/index.ts --format esm --dts",
25
+ "dev": "tsup src/index.ts --format esm --dts --watch",
26
+ "test": "echo \"No tests yet\" && exit 0",
27
+ "clean": "rimraf dist"
27
28
  },
28
29
  "dependencies": {
30
+ "@inquirer/prompts": "^7.0.1",
29
31
  "chalk": "^5.3.0",
30
32
  "commander": "^12.0.0",
31
33
  "fs-extra": "^11.2.0",
32
34
  "ora": "^8.1.0"
33
35
  },
34
36
  "devDependencies": {
35
- "@types/fs-extra": "^11.0.4"
37
+ "@types/fs-extra": "^11.0.4",
38
+ "rimraf": "^6.1.2"
36
39
  },
37
40
  "repository": {
38
41
  "type": "git",
package/dist/index.d.mts DELETED
@@ -1 +0,0 @@
1
- #!/usr/bin/env node
package/dist/index.mjs DELETED
@@ -1,412 +0,0 @@
1
- #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
-
5
- // src/index.ts
6
- import { Command } from "commander";
7
- import chalk from "chalk";
8
- import fs from "fs-extra";
9
- import path from "path";
10
- import ora from "ora";
11
- function capitalize(str) {
12
- return str.charAt(0).toUpperCase() + str.slice(1);
13
- }
14
- __name(capitalize, "capitalize");
15
- var program = new Command();
16
- program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version("0.1.0");
17
- program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").action(async (projectName) => {
18
- const spinner = ora(`Creating project ${projectName}...`).start();
19
- const targetDir = path.join(process.cwd(), projectName);
20
- if (fs.existsSync(targetDir)) {
21
- spinner.fail(`Folder ${projectName} already exists.`);
22
- process.exit(1);
23
- }
24
- await fs.mkdirp(targetDir);
25
- await fs.mkdirp(path.join(targetDir, "mcp", "example"));
26
- const pkg = {
27
- name: projectName,
28
- version: "1.0.0",
29
- description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
30
- main: "dist/main.js",
31
- type: "module",
32
- scripts: {
33
- dev: "tsx watch main.ts",
34
- build: "tsc",
35
- start: "node dist/main.js",
36
- clean: "rm -rf dist"
37
- },
38
- keywords: [
39
- "mcp",
40
- "model-context-protocol",
41
- "streamable-http",
42
- "leanmcp"
43
- ],
44
- author: "",
45
- license: "MIT",
46
- dependencies: {
47
- "@leanmcp/core": "^0.2.0",
48
- "dotenv": "^16.5.0"
49
- },
50
- devDependencies: {
51
- "@types/node": "^20.0.0",
52
- "tsx": "^4.20.3",
53
- "typescript": "^5.6.3"
54
- }
55
- };
56
- await fs.writeJSON(path.join(targetDir, "package.json"), pkg, {
57
- spaces: 2
58
- });
59
- const tsconfig = {
60
- compilerOptions: {
61
- module: "ESNext",
62
- target: "ES2022",
63
- moduleResolution: "Node",
64
- esModuleInterop: true,
65
- strict: true,
66
- skipLibCheck: true,
67
- outDir: "dist",
68
- experimentalDecorators: true,
69
- emitDecoratorMetadata: true
70
- },
71
- include: [
72
- "**/*.ts"
73
- ],
74
- exclude: [
75
- "node_modules",
76
- "dist"
77
- ]
78
- };
79
- await fs.writeJSON(path.join(targetDir, "tsconfig.json"), tsconfig, {
80
- spaces: 2
81
- });
82
- const mainTs = `import dotenv from "dotenv";
83
- import { createHTTPServer, MCPServer } from "@leanmcp/core";
84
-
85
- // Load environment variables
86
- dotenv.config();
87
-
88
- const PORT = Number(process.env.PORT) || 3001;
89
-
90
- /**
91
- * Create and configure the MCP server
92
- * Services are automatically discovered from ./mcp directory
93
- */
94
- const serverFactory = async () => {
95
- const server = new MCPServer({
96
- name: "${projectName}",
97
- version: "1.0.0",
98
- logging: true
99
- });
100
-
101
- // Services are automatically discovered and registered from ./mcp
102
- return server.getServer();
103
- };
104
-
105
- // Start the HTTP server
106
- await createHTTPServer(serverFactory, {
107
- port: PORT,
108
- cors: true,
109
- logging: true // Log HTTP requests
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\`);
115
- `;
116
- await fs.writeFile(path.join(targetDir, "main.ts"), mainTs);
117
- const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
118
-
119
- /**
120
- * Example service demonstrating LeanMCP SDK decorators
121
- *
122
- * This is a simple example to get you started. Add your own tools, resources, and prompts here!
123
- */
124
-
125
- // Input schema with validation decorators
126
- class CalculateInput {
127
- @SchemaConstraint({ description: "First number" })
128
- a!: number;
129
-
130
- @SchemaConstraint({ description: "Second number" })
131
- b!: number;
132
-
133
- @Optional()
134
- @SchemaConstraint({
135
- description: "Operation to perform",
136
- enum: ["add", "subtract", "multiply", "divide"],
137
- default: "add"
138
- })
139
- operation?: string;
140
- }
141
-
142
- export class ExampleService {
143
- @Tool({
144
- description: "Perform arithmetic operations with automatic schema validation",
145
- inputClass: CalculateInput
146
- })
147
- async calculate(input: CalculateInput) {
148
- let result: number;
149
-
150
- switch (input.operation || "add") {
151
- case "add":
152
- result = input.a + input.b;
153
- break;
154
- case "subtract":
155
- result = input.a - input.b;
156
- break;
157
- case "multiply":
158
- result = input.a * input.b;
159
- break;
160
- case "divide":
161
- if (input.b === 0) throw new Error("Cannot divide by zero");
162
- result = input.a / input.b;
163
- break;
164
- default:
165
- throw new Error("Invalid operation");
166
- }
167
-
168
- return {
169
- content: [{
170
- type: "text" as const,
171
- text: JSON.stringify({
172
- operation: input.operation || "add",
173
- operands: { a: input.a, b: input.b },
174
- result
175
- }, null, 2)
176
- }]
177
- };
178
- }
179
-
180
- @Tool({ description: "Echo a message back" })
181
- async echo(input: { message: string }) {
182
- return {
183
- content: [{
184
- type: "text" as const,
185
- text: JSON.stringify({
186
- echoed: input.message,
187
- timestamp: new Date().toISOString()
188
- }, null, 2)
189
- }]
190
- };
191
- }
192
-
193
- @Resource({ description: "Get server information" })
194
- async serverInfo() {
195
- return {
196
- contents: [{
197
- uri: "server://info",
198
- mimeType: "application/json",
199
- text: JSON.stringify({
200
- name: "${projectName}",
201
- version: "1.0.0",
202
- uptime: process.uptime()
203
- }, null, 2)
204
- }]
205
- };
206
- }
207
-
208
- @Prompt({ description: "Generate a greeting prompt" })
209
- async greeting(args: { name?: string }) {
210
- return {
211
- messages: [{
212
- role: "user" as const,
213
- content: {
214
- type: "text" as const,
215
- text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
216
- }
217
- }]
218
- };
219
- }
220
- }
221
- `;
222
- await fs.writeFile(path.join(targetDir, "mcp", "example", "index.ts"), exampleServiceTs);
223
- const gitignore = `node_modules
224
- dist
225
- .env
226
- .env.local
227
- *.log
228
- `;
229
- const env = `# Server Configuration
230
- PORT=3001
231
- NODE_ENV=development
232
-
233
- # Add your environment variables here
234
- `;
235
- await fs.writeFile(path.join(targetDir, ".gitignore"), gitignore);
236
- await fs.writeFile(path.join(targetDir, ".env"), env);
237
- const readme = `# ${projectName}
238
-
239
- MCP Server with Streamable HTTP Transport built with LeanMCP SDK
240
-
241
- ## Quick Start
242
-
243
- \`\`\`bash
244
- # Install dependencies
245
- npm install
246
-
247
- # Start development server (hot reload)
248
- npm run dev
249
-
250
- # Build for production
251
- npm run build
252
-
253
- # Run production server
254
- npm start
255
- \`\`\`
256
-
257
- ## Project Structure
258
-
259
- \`\`\`
260
- ${projectName}/
261
- \u251C\u2500\u2500 main.ts # Server entry point
262
- \u251C\u2500\u2500 mcp/ # Services directory (auto-discovered)
263
- \u2502 \u2514\u2500\u2500 example/
264
- \u2502 \u2514\u2500\u2500 index.ts # Example service
265
- \u251C\u2500\u2500 .env # Environment variables
266
- \u2514\u2500\u2500 package.json
267
- \`\`\`
268
-
269
- ## Adding New Services
270
-
271
- Create a new service directory in \`mcp/\`:
272
-
273
- \`\`\`typescript
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
- }
285
-
286
- export class MyService {
287
- @Tool({
288
- description: "My awesome tool",
289
- inputClass: MyToolInput
290
- })
291
- async myTool(input: MyToolInput) {
292
- return {
293
- content: [{
294
- type: "text",
295
- text: \`You said: \${input.message}\`
296
- }]
297
- };
298
- }
299
- }
300
- \`\`\`
301
-
302
- Services are automatically discovered and registered - no need to modify \`main.ts\`!
303
-
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
311
-
312
- ## Testing with MCP Inspector
313
-
314
- \`\`\`bash
315
- npx @modelcontextprotocol/inspector http://localhost:3001/mcp
316
- \`\`\`
317
-
318
- ## License
319
-
320
- MIT
321
- `;
322
- await fs.writeFile(path.join(targetDir, "README.md"), readme);
323
- spinner.succeed(`Project ${projectName} created!`);
324
- console.log(chalk.green("\\nSuccess! Your MCP server is ready.\\n"));
325
- console.log(chalk.cyan("Next steps:"));
326
- console.log(chalk.gray(` cd ${projectName}`));
327
- console.log(chalk.gray(` npm install`));
328
- console.log(chalk.gray(` npm run dev`));
329
- console.log(chalk.gray(`\\nServer will run on http://localhost:3001`));
330
- });
331
- program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
332
- const cwd = process.cwd();
333
- const mcpDir = path.join(cwd, "mcp");
334
- if (!fs.existsSync(path.join(cwd, "main.ts"))) {
335
- console.error(chalk.red("ERROR: Not a LeanMCP project (main.ts missing)."));
336
- process.exit(1);
337
- }
338
- const serviceDir = path.join(mcpDir, serviceName);
339
- const serviceFile = path.join(serviceDir, "index.ts");
340
- if (fs.existsSync(serviceDir)) {
341
- console.error(chalk.red(`ERROR: Service ${serviceName} already exists.`));
342
- process.exit(1);
343
- }
344
- await fs.mkdirp(serviceDir);
345
- const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
346
-
347
- // Input schema for greeting
348
- class GreetInput {
349
- @SchemaConstraint({
350
- description: "Name to greet",
351
- minLength: 1
352
- })
353
- name!: string;
354
- }
355
-
356
- /**
357
- * ${capitalize(serviceName)} Service
358
- *
359
- * This service demonstrates the three types of MCP primitives:
360
- * - Tools: Callable functions (like API endpoints)
361
- * - Prompts: Reusable prompt templates
362
- * - Resources: Data sources/endpoints
363
- */
364
- export class ${capitalize(serviceName)}Service {
365
- // TOOL - Callable function
366
- // Tool name: "greet" (from function name)
367
- @Tool({
368
- description: "Greet a user by name",
369
- inputClass: GreetInput
370
- })
371
- greet(args: GreetInput) {
372
- return { message: \`Hello, \${args.name}! from ${serviceName}\` };
373
- }
374
-
375
- // PROMPT - Prompt template
376
- // Prompt name: "welcomePrompt" (from function name)
377
- @Prompt({ description: "Welcome message prompt template" })
378
- welcomePrompt(args: { userName?: string }) {
379
- return {
380
- messages: [
381
- {
382
- role: "user",
383
- content: {
384
- type: "text",
385
- text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
386
- }
387
- }
388
- ]
389
- };
390
- }
391
-
392
- // RESOURCE - Data endpoint
393
- // Resource URI auto-generated from class and method name
394
- @Resource({ description: "${capitalize(serviceName)} service status" })
395
- getStatus() {
396
- return {
397
- service: "${serviceName}",
398
- status: "active",
399
- timestamp: new Date().toISOString()
400
- };
401
- }
402
- }
403
- `;
404
- await fs.writeFile(serviceFile, indexTs);
405
- console.log(chalk.green(`\\nCreated new service: ${chalk.bold(serviceName)}`));
406
- console.log(chalk.gray(` File: mcp/${serviceName}/index.ts`));
407
- console.log(chalk.gray(` Tool: greet`));
408
- console.log(chalk.gray(` Prompt: welcomePrompt`));
409
- console.log(chalk.gray(` Resource: getStatus`));
410
- console.log(chalk.green(`\\nService will be automatically discovered on next server start!`));
411
- });
412
- program.parse();