@leanmcp/cli 0.1.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LeanMCP Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,367 @@
1
+ # @leanmcp/cli
2
+
3
+ Command-line tool for creating LeanMCP projects with production-ready templates.
4
+
5
+ ## Features
6
+
7
+ - **Quick project scaffolding** - Create new MCP servers in seconds
8
+ - **Complete setup** - Includes TypeScript, dependencies, and configuration
9
+ - **Best practices** - Generated projects follow MCP standards
10
+ - **Ready to run** - Start developing immediately with hot reload
11
+ - **Example service** - Includes working examples to get started
12
+
13
+ ## Installation
14
+
15
+ ### Global Installation (Recommended)
16
+
17
+ ```bash
18
+ npm install -g @leanmcp/cli
19
+ ```
20
+
21
+ ### Run Without Installing
22
+
23
+ ```bash
24
+ npx @leanmcp/cli create my-mcp-server
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Create a New Project
30
+
31
+ ```bash
32
+ leanmcp create <project-name>
33
+ ```
34
+
35
+ Or with npx:
36
+ ```bash
37
+ npx @leanmcp/cli create my-mcp-server
38
+ ```
39
+
40
+ ### Example
41
+
42
+ ```bash
43
+ $ leanmcp create my-sentiment-tool
44
+ ✔ Creating project my-sentiment-tool...
45
+
46
+ Project created successfully!
47
+
48
+ Next steps:
49
+ cd my-sentiment-tool
50
+ npm install
51
+ npm run dev
52
+
53
+ Your MCP server will be running on http://localhost:3001
54
+ ```
55
+
56
+ ### Add a New Service
57
+
58
+ After creating a project, you can quickly add new services:
59
+
60
+ ```bash
61
+ leanmcp add <service-name>
62
+ ```
63
+
64
+ This command:
65
+ - Creates a new service file in `mcp/<service-name>.ts`
66
+ - Includes example Tool, Prompt, and Resource decorators
67
+ - Automatically registers the service in `main.ts`
68
+ - Includes schema validation examples
69
+
70
+ **Example:**
71
+
72
+ ```bash
73
+ $ leanmcp add weather
74
+ ✔ Created new service: weather
75
+ File: mcp/weather.ts
76
+ Tool: greet
77
+ Prompt: welcomePrompt
78
+ Resource: getStatus
79
+
80
+ Service automatically registered in main.ts!
81
+ ```
82
+
83
+ The generated service includes:
84
+ - **Tool** - `greet()`: A callable function with schema validation
85
+ - **Prompt** - `welcomePrompt()`: A reusable prompt template
86
+ - **Resource** - `getStatus()`: A data endpoint
87
+
88
+ You can then customize these to fit your needs.
89
+
90
+ ## Generated Project Structure
91
+
92
+ ```
93
+ my-mcp-server/
94
+ ├── main.ts # Entry point with HTTP server
95
+ ├── package.json # Dependencies and scripts
96
+ ├── tsconfig.json # TypeScript configuration
97
+ └── mcp/ # Services directory
98
+ └── example.ts # Example service with tools
99
+ ```
100
+
101
+ ## Generated Files
102
+
103
+ ### main.ts
104
+ Entry point that:
105
+ - Loads environment variables
106
+ - Creates MCP server instance
107
+ - Registers services
108
+ - Starts HTTP server with session management
109
+
110
+ ### mcp/example.ts
111
+ Example service demonstrating:
112
+ - `@Tool` decorator for callable functions
113
+ - `@Resource` decorator for data sources
114
+ - `@Prompt` decorator for prompt templates
115
+ - Class-based schema validation with `@SchemaConstraint`
116
+ - Input/output type safety
117
+
118
+ ### package.json
119
+ Includes:
120
+ - `@leanmcp/core` - Core MCP functionality
121
+ - `@modelcontextprotocol/sdk` - Official MCP SDK
122
+ - `express` - HTTP server
123
+ - `tsx` - TypeScript execution with hot reload
124
+ - All type definitions
125
+
126
+ ### tsconfig.json
127
+ Configured with:
128
+ - ESNext modules
129
+ - Decorator support
130
+ - Strict type checking
131
+ - Source maps
132
+
133
+ ## NPM Scripts
134
+
135
+ Generated projects include:
136
+
137
+ ```bash
138
+ npm run dev # Start with hot reload (tsx watch)
139
+ npm run build # Build for production
140
+ npm run start # Run production build
141
+ npm run clean # Remove build artifacts
142
+ ```
143
+
144
+ ## Development Workflow
145
+
146
+ After creating a project:
147
+
148
+ ```bash
149
+ # 1. Install dependencies
150
+ cd my-mcp-server
151
+ npm install
152
+
153
+ # 2. Start development server
154
+ npm run dev
155
+
156
+ # 3. Server starts on http://localhost:3001
157
+ # - Endpoint: http://localhost:3001/mcp
158
+ # - Health check: http://localhost:3001/health
159
+ # - Hot reload enabled
160
+
161
+ # 4. Edit files in mcp/ directory
162
+ # Server automatically reloads on changes
163
+ ```
164
+
165
+ ## Testing Your Server
166
+
167
+ Test with curl:
168
+ ```bash
169
+ # List available tools
170
+ curl http://localhost:3001/mcp \
171
+ -X POST \
172
+ -H "Content-Type: application/json" \
173
+ -d '{
174
+ "jsonrpc": "2.0",
175
+ "id": 1,
176
+ "method": "tools/list"
177
+ }'
178
+
179
+ # Call a tool
180
+ curl http://localhost:3001/mcp \
181
+ -X POST \
182
+ -H "Content-Type: application/json" \
183
+ -d '{
184
+ "jsonrpc": "2.0",
185
+ "id": 1,
186
+ "method": "tools/call",
187
+ "params": {
188
+ "name": "calculate",
189
+ "arguments": {
190
+ "a": 10,
191
+ "b": 5,
192
+ "operation": "add"
193
+ }
194
+ }
195
+ }'
196
+ ```
197
+
198
+ ## Customizing Generated Projects
199
+
200
+ ### Add New Services
201
+
202
+ **Quick Way (Recommended):**
203
+
204
+ Use the `add` command to automatically generate and register a new service:
205
+
206
+ ```bash
207
+ leanmcp add weather
208
+ ```
209
+
210
+ This creates `mcp/weather.ts` with example Tool, Prompt, and Resource decorators, and automatically registers it in `main.ts`.
211
+
212
+ **Manual Way:**
213
+
214
+ Create a new file in `mcp/`:
215
+
216
+ ```typescript
217
+ // mcp/weather.ts
218
+ import { Tool } from "@leanmcp/core";
219
+
220
+ export class WeatherService {
221
+ @Tool({ description: 'Get weather for a city' })
222
+ async getWeather(input: { city: string }) {
223
+ // Your implementation
224
+ return { temperature: 72, condition: 'sunny' };
225
+ }
226
+ }
227
+ ```
228
+
229
+ Register in `main.ts`:
230
+ ```typescript
231
+ import { WeatherService } from "./mcp/weather.js";
232
+
233
+ server.registerService(new WeatherService());
234
+ ```
235
+
236
+ ### Add Authentication
237
+
238
+ Install auth package:
239
+ ```bash
240
+ npm install @leanmcp/auth
241
+ ```
242
+
243
+ See [@leanmcp/auth](../auth) documentation for details.
244
+
245
+ ### Configure Port
246
+
247
+ Set in environment variable:
248
+ ```bash
249
+ PORT=4000 npm run dev
250
+ ```
251
+
252
+ Or in `.env` file:
253
+ ```bash
254
+ PORT=4000
255
+ ```
256
+
257
+ ## Advanced Options
258
+
259
+ ### Custom Project Location
260
+
261
+ ```bash
262
+ leanmcp create my-project
263
+ cd my-project
264
+ ```
265
+
266
+ Project is created in current directory with the specified name.
267
+
268
+ ### Modify Template
269
+
270
+ The generated project is fully customizable:
271
+ - Edit `main.ts` for server configuration
272
+ - Add/remove services in `mcp/` directory
273
+ - Modify `package.json` for additional dependencies
274
+ - Update `tsconfig.json` for compiler options
275
+
276
+ ## Troubleshooting
277
+
278
+ ### Port Already in Use
279
+
280
+ Change the port in `.env`:
281
+ ```bash
282
+ PORT=3002
283
+ ```
284
+
285
+ ### Module Not Found Errors
286
+
287
+ Ensure you've installed dependencies:
288
+ ```bash
289
+ npm install
290
+ ```
291
+
292
+ ### TypeScript Errors
293
+
294
+ Check your `tsconfig.json` and ensure:
295
+ - `experimentalDecorators: true`
296
+ - `emitDecoratorMetadata: true`
297
+
298
+ ### Hot Reload Not Working
299
+
300
+ Try restarting the dev server:
301
+ ```bash
302
+ npm run dev
303
+ ```
304
+
305
+ ## Project Types
306
+
307
+ Currently supports:
308
+ - **MCP Server** - Standard MCP server with HTTP transport
309
+
310
+ Coming soon:
311
+ - MCP Server with Auth
312
+ - MCP Server with Database
313
+ - MCP Server with File Storage
314
+
315
+ ## Examples
316
+
317
+ See the [examples](../../examples) directory for complete working examples:
318
+ - [basic-sentiment-tool](../../examples/basic-sentiment-tool) - Simple sentiment analysis
319
+ - [slack-with-auth](../../examples/slack-with-auth) - Slack integration with Cognito auth
320
+
321
+ ## Requirements
322
+
323
+ - Node.js >= 18.0.0
324
+ - npm >= 9.0.0
325
+
326
+ ## CLI Commands
327
+
328
+ ```bash
329
+ leanmcp create <name> # Create new project
330
+ leanmcp add <service> # Add new service to existing project
331
+ leanmcp --version # Show version
332
+ leanmcp --help # Show help
333
+ ```
334
+
335
+ ### Command Details
336
+
337
+ #### `create <project-name>`
338
+ Creates a complete MCP server project with:
339
+ - Entry point (`main.ts`)
340
+ - Example service with Tool, Resource, and Prompt decorators
341
+ - TypeScript configuration
342
+ - Package.json with all dependencies
343
+ - Development and build scripts
344
+
345
+ #### `add <service-name>`
346
+ Adds a new service to an existing project:
347
+ - Must be run inside a LeanMCP project directory
348
+ - Creates `mcp/<service-name>.ts` with template code
349
+ - Automatically imports and registers in `main.ts`
350
+ - Includes example Tool, Prompt, and Resource implementations
351
+ - Uses schema validation with `@SchemaConstraint` decorators
352
+
353
+ ## License
354
+
355
+ MIT
356
+
357
+ ## Related Packages
358
+
359
+ - [@leanmcp/core](../core) - Core MCP server functionality
360
+ - [@leanmcp/auth](../auth) - Authentication decorators
361
+ - [@leanmcp/utils](../utils) - Utility functions
362
+
363
+ ## Links
364
+
365
+ - [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
366
+ - [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)
367
+ - [MCP Specification](https://spec.modelcontextprotocol.io/)
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,440 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
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;
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
+ ));
26
+
27
+ // 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"));
33
+ function capitalize(str) {
34
+ return str.charAt(0).toUpperCase() + str.slice(1);
35
+ }
36
+ __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");
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)) {
43
+ spinner.fail(`Folder ${projectName} already exists.`);
44
+ process.exit(1);
45
+ }
46
+ await import_fs_extra.default.mkdirp(targetDir);
47
+ await import_fs_extra.default.mkdirp(import_path.default.join(targetDir, "mcp"));
48
+ const pkg = {
49
+ name: projectName,
50
+ version: "1.0.0",
51
+ description: "MCP Server with Streamable HTTP Transport and LeanMCP SDK",
52
+ main: "dist/main.js",
53
+ type: "module",
54
+ scripts: {
55
+ dev: "tsx watch main.ts",
56
+ build: "tsc",
57
+ start: "node dist/main.js",
58
+ clean: "rm -rf dist"
59
+ },
60
+ keywords: [
61
+ "mcp",
62
+ "model-context-protocol",
63
+ "streamable-http",
64
+ "leanmcp"
65
+ ],
66
+ author: "",
67
+ license: "MIT",
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"
74
+ },
75
+ devDependencies: {
76
+ "@types/cors": "^2.8.19",
77
+ "@types/express": "^5.0.3",
78
+ "@types/node": "^20.0.0",
79
+ "tsx": "^4.20.3",
80
+ "typescript": "^5.6.3"
81
+ }
82
+ };
83
+ await import_fs_extra.default.writeJSON(import_path.default.join(targetDir, "package.json"), pkg, {
84
+ spaces: 2
85
+ });
86
+ const tsconfig = {
87
+ compilerOptions: {
88
+ module: "ESNext",
89
+ target: "ES2022",
90
+ moduleResolution: "Node",
91
+ esModuleInterop: true,
92
+ strict: true,
93
+ skipLibCheck: true,
94
+ outDir: "dist",
95
+ experimentalDecorators: true,
96
+ emitDecoratorMetadata: true
97
+ },
98
+ include: [
99
+ "**/*.ts"
100
+ ],
101
+ exclude: [
102
+ "node_modules",
103
+ "dist"
104
+ ]
105
+ };
106
+ await import_fs_extra.default.writeJSON(import_path.default.join(targetDir, "tsconfig.json"), tsconfig, {
107
+ spaces: 2
108
+ });
109
+ const mainTs = `import dotenv from "dotenv";
110
+ import { createHTTPServer, MCPServer } from "@leanmcp/core";
111
+ import { ExampleService } from "./mcp/example.js";
112
+
113
+ // Load environment variables
114
+ dotenv.config();
115
+
116
+ const PORT = Number(process.env.PORT) || 3001;
117
+
118
+ /**
119
+ * Create and configure the MCP server
120
+ */
121
+ function createMCPServer() {
122
+ const server = new MCPServer({
123
+ name: "${projectName}",
124
+ version: "1.0.0"
125
+ });
126
+
127
+ // Register your services here
128
+ server.registerService(new ExampleService());
129
+
130
+ return server.getServer();
131
+ }
132
+
133
+ // Start the HTTP server
134
+ await createHTTPServer(createMCPServer, {
135
+ port: PORT,
136
+ cors: true,
137
+ logging: true
138
+ });
139
+ `;
140
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "main.ts"), mainTs);
141
+ const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
142
+
143
+ /**
144
+ * Example service demonstrating LeanMCP SDK decorators
145
+ *
146
+ * This is a simple example to get you started. Add your own tools, resources, and prompts here!
147
+ */
148
+
149
+ // Input schema with validation decorators
150
+ class CalculateInput {
151
+ @SchemaConstraint({ description: "First number" })
152
+ a!: number;
153
+
154
+ @SchemaConstraint({ description: "Second number" })
155
+ b!: number;
156
+
157
+ @Optional()
158
+ @SchemaConstraint({
159
+ description: "Operation to perform",
160
+ enum: ["add", "subtract", "multiply", "divide"],
161
+ default: "add"
162
+ })
163
+ operation?: string;
164
+ }
165
+
166
+ export class ExampleService {
167
+ @Tool({
168
+ description: "Perform arithmetic operations with automatic schema validation",
169
+ inputClass: CalculateInput
170
+ })
171
+ async calculate(input: CalculateInput) {
172
+ let result: number;
173
+
174
+ switch (input.operation || "add") {
175
+ case "add":
176
+ result = input.a + input.b;
177
+ break;
178
+ case "subtract":
179
+ result = input.a - input.b;
180
+ break;
181
+ case "multiply":
182
+ result = input.a * input.b;
183
+ break;
184
+ case "divide":
185
+ if (input.b === 0) throw new Error("Cannot divide by zero");
186
+ result = input.a / input.b;
187
+ break;
188
+ default:
189
+ throw new Error("Invalid operation");
190
+ }
191
+
192
+ return {
193
+ content: [{
194
+ type: "text" as const,
195
+ text: JSON.stringify({
196
+ operation: input.operation || "add",
197
+ operands: { a: input.a, b: input.b },
198
+ result
199
+ }, null, 2)
200
+ }]
201
+ };
202
+ }
203
+
204
+ @Tool({ description: "Echo a message back" })
205
+ async echo(input: { message: string }) {
206
+ return {
207
+ content: [{
208
+ type: "text" as const,
209
+ text: JSON.stringify({
210
+ echoed: input.message,
211
+ timestamp: new Date().toISOString()
212
+ }, null, 2)
213
+ }]
214
+ };
215
+ }
216
+
217
+ @Resource({ description: "Get server information" })
218
+ async serverInfo() {
219
+ return {
220
+ contents: [{
221
+ uri: "server://info",
222
+ mimeType: "application/json",
223
+ text: JSON.stringify({
224
+ name: "${projectName}",
225
+ version: "1.0.0",
226
+ uptime: process.uptime()
227
+ }, null, 2)
228
+ }]
229
+ };
230
+ }
231
+
232
+ @Prompt({ description: "Generate a greeting prompt" })
233
+ async greeting(args: { name?: string }) {
234
+ return {
235
+ messages: [{
236
+ role: "user" as const,
237
+ content: {
238
+ type: "text" as const,
239
+ text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
240
+ }
241
+ }]
242
+ };
243
+ }
244
+ }
245
+ `;
246
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "mcp", "example.ts"), exampleServiceTs);
247
+ const gitignore = `node_modules
248
+ dist
249
+ .env
250
+ .env.local
251
+ *.log
252
+ `;
253
+ const env = `# Server Configuration
254
+ PORT=3001
255
+ NODE_ENV=development
256
+
257
+ # Add your environment variables here
258
+ `;
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);
261
+ const readme = `# ${projectName}
262
+
263
+ MCP Server with Streamable HTTP Transport built with LeanMCP SDK
264
+
265
+ ## Quick Start
266
+
267
+ \`\`\`bash
268
+ # Install dependencies
269
+ npm install
270
+
271
+ # Start development server (hot reload)
272
+ npm run dev
273
+
274
+ # Build for production
275
+ npm run build
276
+
277
+ # Run production server
278
+ npm start
279
+ \`\`\`
280
+
281
+ ## Project Structure
282
+
283
+ \`\`\`
284
+ ${projectName}/
285
+ \u251C\u2500\u2500 main.ts # Server entry point
286
+ \u251C\u2500\u2500 mcp/
287
+ \u2502 \u2514\u2500\u2500 example.ts # Example service
288
+ \u251C\u2500\u2500 .env # Environment variables
289
+ \u2514\u2500\u2500 package.json
290
+ \`\`\`
291
+
292
+ ## Adding New Services
293
+
294
+ Create a new service file in \`mcp/\`:
295
+
296
+ \`\`\`typescript
297
+ import { Tool } from "@leanmcp/core";
298
+
299
+ export class MyService {
300
+ @Tool({ description: "My awesome tool" })
301
+ async myTool(input: { message: string }) {
302
+ return {
303
+ content: [{
304
+ type: "text",
305
+ text: \`You said: \${input.message}\`
306
+ }]
307
+ };
308
+ }
309
+ }
310
+ \`\`\`
311
+
312
+ Then register it in \`main.ts\`:
313
+
314
+ \`\`\`typescript
315
+ import { MyService } from "./mcp/my-service.js";
316
+ server.registerService(new MyService());
317
+ \`\`\`
318
+
319
+ ## Testing with MCP Inspector
320
+
321
+ \`\`\`bash
322
+ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
323
+ \`\`\`
324
+
325
+ ## License
326
+
327
+ MIT
328
+ `;
329
+ await import_fs_extra.default.writeFile(import_path.default.join(targetDir, "README.md"), readme);
330
+ spinner.succeed(`Project ${projectName} created!`);
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`));
337
+ });
338
+ program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
339
+ const cwd = process.cwd();
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)."));
343
+ process.exit(1);
344
+ }
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.`));
349
+ process.exit(1);
350
+ }
351
+ const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
352
+
353
+ // Input schema for greeting
354
+ class GreetInput {
355
+ @SchemaConstraint({
356
+ description: "Name to greet",
357
+ minLength: 1
358
+ })
359
+ name!: string;
360
+ }
361
+
362
+ /**
363
+ * ${capitalize(serviceName)} Service
364
+ *
365
+ * This service demonstrates the three types of MCP primitives:
366
+ * - Tools: Callable functions (like API endpoints)
367
+ * - Prompts: Reusable prompt templates
368
+ * - Resources: Data sources/endpoints
369
+ */
370
+ export class ${capitalize(serviceName)}Service {
371
+ // TOOL - Callable function
372
+ // Tool name: "greet" (from function name)
373
+ @Tool({
374
+ description: "Greet a user by name",
375
+ inputClass: GreetInput
376
+ })
377
+ greet(args: GreetInput) {
378
+ return { message: \`Hello, \${args.name}! from ${serviceName}\` };
379
+ }
380
+
381
+ // PROMPT - Prompt template
382
+ // Prompt name: "welcomePrompt" (from function name)
383
+ @Prompt({ description: "Welcome message prompt template" })
384
+ welcomePrompt(args: { userName?: string }) {
385
+ return {
386
+ messages: [
387
+ {
388
+ role: "user",
389
+ content: {
390
+ type: "text",
391
+ text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
392
+ }
393
+ }
394
+ ]
395
+ };
396
+ }
397
+
398
+ // RESOURCE - Data endpoint
399
+ // Resource URI auto-generated from class and method name
400
+ @Resource({ description: "${capitalize(serviceName)} service status" })
401
+ getStatus() {
402
+ return {
403
+ service: "${serviceName}",
404
+ status: "active",
405
+ timestamp: new Date().toISOString()
406
+ };
407
+ }
408
+ }
409
+ `;
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!`));
439
+ });
440
+ program.parse();
package/dist/index.mjs ADDED
@@ -0,0 +1,418 @@
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"));
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.1.0",
48
+ "@modelcontextprotocol/sdk": "^1.0.0",
49
+ "cors": "^2.8.5",
50
+ "dotenv": "^16.5.0",
51
+ "express": "^5.1.0"
52
+ },
53
+ devDependencies: {
54
+ "@types/cors": "^2.8.19",
55
+ "@types/express": "^5.0.3",
56
+ "@types/node": "^20.0.0",
57
+ "tsx": "^4.20.3",
58
+ "typescript": "^5.6.3"
59
+ }
60
+ };
61
+ await fs.writeJSON(path.join(targetDir, "package.json"), pkg, {
62
+ spaces: 2
63
+ });
64
+ const tsconfig = {
65
+ compilerOptions: {
66
+ module: "ESNext",
67
+ target: "ES2022",
68
+ moduleResolution: "Node",
69
+ esModuleInterop: true,
70
+ strict: true,
71
+ skipLibCheck: true,
72
+ outDir: "dist",
73
+ experimentalDecorators: true,
74
+ emitDecoratorMetadata: true
75
+ },
76
+ include: [
77
+ "**/*.ts"
78
+ ],
79
+ exclude: [
80
+ "node_modules",
81
+ "dist"
82
+ ]
83
+ };
84
+ await fs.writeJSON(path.join(targetDir, "tsconfig.json"), tsconfig, {
85
+ spaces: 2
86
+ });
87
+ const mainTs = `import dotenv from "dotenv";
88
+ import { createHTTPServer, MCPServer } from "@leanmcp/core";
89
+ import { ExampleService } from "./mcp/example.js";
90
+
91
+ // Load environment variables
92
+ dotenv.config();
93
+
94
+ const PORT = Number(process.env.PORT) || 3001;
95
+
96
+ /**
97
+ * Create and configure the MCP server
98
+ */
99
+ function createMCPServer() {
100
+ const server = new MCPServer({
101
+ name: "${projectName}",
102
+ version: "1.0.0"
103
+ });
104
+
105
+ // Register your services here
106
+ server.registerService(new ExampleService());
107
+
108
+ return server.getServer();
109
+ }
110
+
111
+ // Start the HTTP server
112
+ await createHTTPServer(createMCPServer, {
113
+ port: PORT,
114
+ cors: true,
115
+ logging: true
116
+ });
117
+ `;
118
+ await fs.writeFile(path.join(targetDir, "main.ts"), mainTs);
119
+ const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
120
+
121
+ /**
122
+ * Example service demonstrating LeanMCP SDK decorators
123
+ *
124
+ * This is a simple example to get you started. Add your own tools, resources, and prompts here!
125
+ */
126
+
127
+ // Input schema with validation decorators
128
+ class CalculateInput {
129
+ @SchemaConstraint({ description: "First number" })
130
+ a!: number;
131
+
132
+ @SchemaConstraint({ description: "Second number" })
133
+ b!: number;
134
+
135
+ @Optional()
136
+ @SchemaConstraint({
137
+ description: "Operation to perform",
138
+ enum: ["add", "subtract", "multiply", "divide"],
139
+ default: "add"
140
+ })
141
+ operation?: string;
142
+ }
143
+
144
+ export class ExampleService {
145
+ @Tool({
146
+ description: "Perform arithmetic operations with automatic schema validation",
147
+ inputClass: CalculateInput
148
+ })
149
+ async calculate(input: CalculateInput) {
150
+ let result: number;
151
+
152
+ switch (input.operation || "add") {
153
+ case "add":
154
+ result = input.a + input.b;
155
+ break;
156
+ case "subtract":
157
+ result = input.a - input.b;
158
+ break;
159
+ case "multiply":
160
+ result = input.a * input.b;
161
+ break;
162
+ case "divide":
163
+ if (input.b === 0) throw new Error("Cannot divide by zero");
164
+ result = input.a / input.b;
165
+ break;
166
+ default:
167
+ throw new Error("Invalid operation");
168
+ }
169
+
170
+ return {
171
+ content: [{
172
+ type: "text" as const,
173
+ text: JSON.stringify({
174
+ operation: input.operation || "add",
175
+ operands: { a: input.a, b: input.b },
176
+ result
177
+ }, null, 2)
178
+ }]
179
+ };
180
+ }
181
+
182
+ @Tool({ description: "Echo a message back" })
183
+ async echo(input: { message: string }) {
184
+ return {
185
+ content: [{
186
+ type: "text" as const,
187
+ text: JSON.stringify({
188
+ echoed: input.message,
189
+ timestamp: new Date().toISOString()
190
+ }, null, 2)
191
+ }]
192
+ };
193
+ }
194
+
195
+ @Resource({ description: "Get server information" })
196
+ async serverInfo() {
197
+ return {
198
+ contents: [{
199
+ uri: "server://info",
200
+ mimeType: "application/json",
201
+ text: JSON.stringify({
202
+ name: "${projectName}",
203
+ version: "1.0.0",
204
+ uptime: process.uptime()
205
+ }, null, 2)
206
+ }]
207
+ };
208
+ }
209
+
210
+ @Prompt({ description: "Generate a greeting prompt" })
211
+ async greeting(args: { name?: string }) {
212
+ return {
213
+ messages: [{
214
+ role: "user" as const,
215
+ content: {
216
+ type: "text" as const,
217
+ text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
218
+ }
219
+ }]
220
+ };
221
+ }
222
+ }
223
+ `;
224
+ await fs.writeFile(path.join(targetDir, "mcp", "example.ts"), exampleServiceTs);
225
+ const gitignore = `node_modules
226
+ dist
227
+ .env
228
+ .env.local
229
+ *.log
230
+ `;
231
+ const env = `# Server Configuration
232
+ PORT=3001
233
+ NODE_ENV=development
234
+
235
+ # Add your environment variables here
236
+ `;
237
+ await fs.writeFile(path.join(targetDir, ".gitignore"), gitignore);
238
+ await fs.writeFile(path.join(targetDir, ".env"), env);
239
+ const readme = `# ${projectName}
240
+
241
+ MCP Server with Streamable HTTP Transport built with LeanMCP SDK
242
+
243
+ ## Quick Start
244
+
245
+ \`\`\`bash
246
+ # Install dependencies
247
+ npm install
248
+
249
+ # Start development server (hot reload)
250
+ npm run dev
251
+
252
+ # Build for production
253
+ npm run build
254
+
255
+ # Run production server
256
+ npm start
257
+ \`\`\`
258
+
259
+ ## Project Structure
260
+
261
+ \`\`\`
262
+ ${projectName}/
263
+ \u251C\u2500\u2500 main.ts # Server entry point
264
+ \u251C\u2500\u2500 mcp/
265
+ \u2502 \u2514\u2500\u2500 example.ts # Example service
266
+ \u251C\u2500\u2500 .env # Environment variables
267
+ \u2514\u2500\u2500 package.json
268
+ \`\`\`
269
+
270
+ ## Adding New Services
271
+
272
+ Create a new service file in \`mcp/\`:
273
+
274
+ \`\`\`typescript
275
+ import { Tool } from "@leanmcp/core";
276
+
277
+ export class MyService {
278
+ @Tool({ description: "My awesome tool" })
279
+ async myTool(input: { message: string }) {
280
+ return {
281
+ content: [{
282
+ type: "text",
283
+ text: \`You said: \${input.message}\`
284
+ }]
285
+ };
286
+ }
287
+ }
288
+ \`\`\`
289
+
290
+ Then register it in \`main.ts\`:
291
+
292
+ \`\`\`typescript
293
+ import { MyService } from "./mcp/my-service.js";
294
+ server.registerService(new MyService());
295
+ \`\`\`
296
+
297
+ ## Testing with MCP Inspector
298
+
299
+ \`\`\`bash
300
+ npx @modelcontextprotocol/inspector http://localhost:3001/mcp
301
+ \`\`\`
302
+
303
+ ## License
304
+
305
+ MIT
306
+ `;
307
+ await fs.writeFile(path.join(targetDir, "README.md"), readme);
308
+ spinner.succeed(`Project ${projectName} created!`);
309
+ console.log(chalk.green("\\nSuccess! Your MCP server is ready.\\n"));
310
+ console.log(chalk.cyan("Next steps:"));
311
+ console.log(chalk.gray(` cd ${projectName}`));
312
+ console.log(chalk.gray(` npm install`));
313
+ console.log(chalk.gray(` npm run dev`));
314
+ console.log(chalk.gray(`\\nServer will run on http://localhost:3001`));
315
+ });
316
+ program.command("add <serviceName>").description("Add a new MCP service to your project").action(async (serviceName) => {
317
+ const cwd = process.cwd();
318
+ const mcpDir = path.join(cwd, "mcp");
319
+ if (!fs.existsSync(path.join(cwd, "main.ts"))) {
320
+ console.error(chalk.red("ERROR: Not a LeanMCP project (main.ts missing)."));
321
+ process.exit(1);
322
+ }
323
+ await fs.mkdirp(mcpDir);
324
+ const serviceFile = path.join(mcpDir, `${serviceName}.ts`);
325
+ if (fs.existsSync(serviceFile)) {
326
+ console.error(chalk.red(`ERROR: Service ${serviceName} already exists.`));
327
+ process.exit(1);
328
+ }
329
+ const indexTs = `import { Tool, Resource, Prompt, Optional, SchemaConstraint } from "@leanmcp/core";
330
+
331
+ // Input schema for greeting
332
+ class GreetInput {
333
+ @SchemaConstraint({
334
+ description: "Name to greet",
335
+ minLength: 1
336
+ })
337
+ name!: string;
338
+ }
339
+
340
+ /**
341
+ * ${capitalize(serviceName)} Service
342
+ *
343
+ * This service demonstrates the three types of MCP primitives:
344
+ * - Tools: Callable functions (like API endpoints)
345
+ * - Prompts: Reusable prompt templates
346
+ * - Resources: Data sources/endpoints
347
+ */
348
+ export class ${capitalize(serviceName)}Service {
349
+ // TOOL - Callable function
350
+ // Tool name: "greet" (from function name)
351
+ @Tool({
352
+ description: "Greet a user by name",
353
+ inputClass: GreetInput
354
+ })
355
+ greet(args: GreetInput) {
356
+ return { message: \`Hello, \${args.name}! from ${serviceName}\` };
357
+ }
358
+
359
+ // PROMPT - Prompt template
360
+ // Prompt name: "welcomePrompt" (from function name)
361
+ @Prompt({ description: "Welcome message prompt template" })
362
+ welcomePrompt(args: { userName?: string }) {
363
+ return {
364
+ messages: [
365
+ {
366
+ role: "user",
367
+ content: {
368
+ type: "text",
369
+ text: \`Welcome \${args.userName || 'user'}! How can I help you with ${serviceName}?\`
370
+ }
371
+ }
372
+ ]
373
+ };
374
+ }
375
+
376
+ // RESOURCE - Data endpoint
377
+ // Resource URI auto-generated from class and method name
378
+ @Resource({ description: "${capitalize(serviceName)} service status" })
379
+ getStatus() {
380
+ return {
381
+ service: "${serviceName}",
382
+ status: "active",
383
+ timestamp: new Date().toISOString()
384
+ };
385
+ }
386
+ }
387
+ `;
388
+ 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
+ console.log(chalk.green(`\\nCreated new service: ${chalk.bold(serviceName)}`));
412
+ console.log(chalk.gray(` File: mcp/${serviceName}.ts`));
413
+ console.log(chalk.gray(` Tool: greet`));
414
+ console.log(chalk.gray(` Prompt: welcomePrompt`));
415
+ console.log(chalk.gray(` Resource: getStatus`));
416
+ console.log(chalk.green(`\\nService automatically registered in main.ts!`));
417
+ });
418
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@leanmcp/cli",
3
+ "version": "0.1.0",
4
+ "description": "Command-line interface for scaffolding LeanMCP projects",
5
+ "bin": {
6
+ "leanmcp": "dist/index.js"
7
+ },
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "require": "./dist/index.js",
15
+ "import": "./dist/index.mjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
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"
27
+ },
28
+ "dependencies": {
29
+ "chalk": "^5.3.0",
30
+ "commander": "^12.0.0",
31
+ "fs-extra": "^11.2.0",
32
+ "ora": "^8.1.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/fs-extra": "^11.0.4"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/LeanMCP/leanmcp-sdk.git",
40
+ "directory": "packages/cli"
41
+ },
42
+ "homepage": "https://github.com/LeanMCP/leanmcp-sdk#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/LeanMCP/leanmcp-sdk/issues"
45
+ },
46
+ "keywords": [
47
+ "mcp",
48
+ "model-context-protocol",
49
+ "cli",
50
+ "scaffolding",
51
+ "generator",
52
+ "typescript"
53
+ ],
54
+ "author": "LeanMCP <admin@leanmcp.com>",
55
+ "license": "MIT",
56
+ "publishConfig": {
57
+ "access": "public"
58
+ }
59
+ }