@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 +54 -11
- package/bin/leanmcp.js +4 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +126 -71
- package/package.json +12 -9
- package/dist/index.d.mts +0 -1
- package/dist/index.mjs +0 -412
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
|
-
✔
|
|
46
|
+
✔ Project my-sentiment-tool created!
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
Success! Your MCP server is ready.
|
|
47
49
|
|
|
48
|
-
Next
|
|
50
|
+
Next, navigate to your project:
|
|
49
51
|
cd my-sentiment-tool
|
|
50
|
-
npm install
|
|
51
|
-
npm run dev
|
|
52
52
|
|
|
53
|
-
|
|
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
|
-
|
|
157
|
+
### Interactive Setup (Recommended)
|
|
158
|
+
|
|
159
|
+
The CLI provides an interactive setup experience:
|
|
147
160
|
|
|
148
161
|
```bash
|
|
149
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
38
|
-
program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready MCP servers with Streamable HTTP").version(
|
|
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 = (
|
|
41
|
-
const targetDir =
|
|
42
|
-
if (
|
|
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
|
|
47
|
-
await
|
|
48
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
167
|
+
result = a + b;
|
|
175
168
|
break;
|
|
176
169
|
case "subtract":
|
|
177
|
-
result =
|
|
170
|
+
result = a - b;
|
|
178
171
|
break;
|
|
179
172
|
case "multiply":
|
|
180
|
-
result =
|
|
173
|
+
result = a * b;
|
|
181
174
|
break;
|
|
182
175
|
case "divide":
|
|
183
|
-
if (
|
|
184
|
-
result =
|
|
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({
|
|
203
|
-
|
|
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
|
|
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
|
|
258
|
-
await
|
|
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
|
|
340
|
+
await fs.writeFile(path.join(targetDir, "README.md"), readme);
|
|
345
341
|
spinner.succeed(`Project ${projectName} created!`);
|
|
346
|
-
console.log(
|
|
347
|
-
console.log(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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 =
|
|
356
|
-
if (!
|
|
357
|
-
console.error(
|
|
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 =
|
|
361
|
-
const serviceFile =
|
|
362
|
-
if (
|
|
363
|
-
console.error(
|
|
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
|
|
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
|
|
427
|
-
console.log(
|
|
428
|
-
console.log(
|
|
429
|
-
console.log(
|
|
430
|
-
console.log(
|
|
431
|
-
console.log(
|
|
432
|
-
console.log(
|
|
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.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Command-line interface for scaffolding LeanMCP projects",
|
|
5
5
|
"bin": {
|
|
6
|
-
"leanmcp": "
|
|
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
|
-
"
|
|
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
|
|
25
|
-
"dev": "tsup src/index.ts --format esm
|
|
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();
|