@leanmcp/cli 0.2.5 → 0.2.7

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.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 CHANGED
@@ -1,62 +1,67 @@
1
- {
2
- "name": "@leanmcp/cli",
3
- "version": "0.2.5",
4
- "description": "Command-line interface for scaffolding LeanMCP projects",
5
- "bin": {
6
- "leanmcp": "bin/leanmcp.js"
7
- },
8
- "type": "module",
9
- "main": "dist/index.js",
10
- "types": "dist/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.js"
15
- }
16
- },
17
- "files": [
18
- "bin",
19
- "dist",
20
- "README.md",
21
- "LICENSE"
22
- ],
23
- "scripts": {
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"
28
- },
29
- "dependencies": {
30
- "@inquirer/prompts": "^7.0.1",
31
- "chalk": "^5.3.0",
32
- "commander": "^12.0.0",
33
- "fs-extra": "^11.2.0",
34
- "ora": "^8.1.0"
35
- },
36
- "devDependencies": {
37
- "@types/fs-extra": "^11.0.4",
38
- "rimraf": "^6.1.2"
39
- },
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/LeanMCP/leanmcp-sdk.git",
43
- "directory": "packages/cli"
44
- },
45
- "homepage": "https://github.com/LeanMCP/leanmcp-sdk#readme",
46
- "bugs": {
47
- "url": "https://github.com/LeanMCP/leanmcp-sdk/issues"
48
- },
49
- "keywords": [
50
- "mcp",
51
- "model-context-protocol",
52
- "cli",
53
- "scaffolding",
54
- "generator",
55
- "typescript"
56
- ],
57
- "author": "LeanMCP <admin@leanmcp.com>",
58
- "license": "MIT",
59
- "publishConfig": {
60
- "access": "public"
61
- }
62
- }
1
+ {
2
+ "name": "@leanmcp/cli",
3
+ "version": "0.2.7",
4
+ "description": "Command-line interface for scaffolding LeanMCP projects",
5
+ "bin": {
6
+ "leanmcp": "bin/leanmcp.js"
7
+ },
8
+ "type": "module",
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "bin",
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
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"
28
+ },
29
+ "dependencies": {
30
+ "@inquirer/prompts": "^7.0.1",
31
+ "chalk": "^5.3.0",
32
+ "commander": "^12.0.0",
33
+ "fs-extra": "^11.2.0",
34
+ "ora": "^8.1.0",
35
+ "vite": "^5.4.0",
36
+ "vite-plugin-singlefile": "^2.3.0",
37
+ "@vitejs/plugin-react": "^4.3.0",
38
+ "chokidar": "^4.0.0",
39
+ "glob": "^11.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/fs-extra": "^11.0.4",
43
+ "rimraf": "^6.1.2"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/LeanMCP/leanmcp-sdk.git",
48
+ "directory": "packages/cli"
49
+ },
50
+ "homepage": "https://github.com/LeanMCP/leanmcp-sdk#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/LeanMCP/leanmcp-sdk/issues"
53
+ },
54
+ "keywords": [
55
+ "mcp",
56
+ "model-context-protocol",
57
+ "cli",
58
+ "scaffolding",
59
+ "generator",
60
+ "typescript"
61
+ ],
62
+ "author": "LeanMCP <admin@leanmcp.com>",
63
+ "license": "MIT",
64
+ "publishConfig": {
65
+ "access": "public"
66
+ }
67
+ }