@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 +21 -0
- package/README.md +367 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +440 -0
- package/dist/index.mjs +418 -0
- package/package.json +59 -0
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/)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|