@leanmcp/core 0.3.10 → 0.3.11
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 +188 -306
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +31 -1
- package/dist/index.mjs +31 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img
|
|
3
|
+
src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/assets/logo.svg"
|
|
4
|
+
alt="LeanMCP Logo"
|
|
5
|
+
width="400"
|
|
6
|
+
/>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<strong>@leanmcp/core</strong><br/>
|
|
11
|
+
Core library for building MCP servers with TypeScript decorators and declarative schema definition.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://www.npmjs.com/package/@leanmcp/core">
|
|
16
|
+
<img src="https://img.shields.io/npm/v/@leanmcp/core" alt="npm version" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://www.npmjs.com/package/@leanmcp/core">
|
|
19
|
+
<img src="https://img.shields.io/npm/dm/@leanmcp/core" alt="npm downloads" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://docs.leanmcp.com/sdk/core">
|
|
22
|
+
<img src="https://img.shields.io/badge/Docs-leanmcp-0A66C2?" />
|
|
23
|
+
</a>
|
|
24
|
+
<a href="https://discord.com/invite/DsRcA3GwPy">
|
|
25
|
+
<img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" />
|
|
26
|
+
</a>
|
|
27
|
+
<a href="https://x.com/LeanMcp">
|
|
28
|
+
<img src="https://img.shields.io/badge/@LeanMCP-f5f5f5?logo=x&logoColor=000000" />
|
|
29
|
+
</a>
|
|
30
|
+
</p>
|
|
4
31
|
|
|
5
32
|
## Features
|
|
6
33
|
|
|
7
|
-
- **Type-
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **MCP
|
|
34
|
+
- **Type-Safe Decorators** — `@Tool`, `@Prompt`, `@Resource` with full TypeScript support
|
|
35
|
+
- **Auto-Discovery** — Zero-config service discovery from `./mcp` directory
|
|
36
|
+
- **Schema Generation** — Declarative JSON Schema with `@SchemaConstraint` decorators
|
|
37
|
+
- **HTTP Transport** — Production-ready HTTP server with session management
|
|
38
|
+
- **Input Validation** — Built-in AJV validation for all inputs
|
|
39
|
+
- **MCP Compliant** — Built on official `@modelcontextprotocol/sdk`
|
|
13
40
|
|
|
14
41
|
## Installation
|
|
15
42
|
|
|
@@ -17,8 +44,6 @@ Core library for building Model Context Protocol (MCP) servers with TypeScript d
|
|
|
17
44
|
npm install @leanmcp/core
|
|
18
45
|
```
|
|
19
46
|
|
|
20
|
-
### Peer Dependencies
|
|
21
|
-
|
|
22
47
|
For HTTP server support:
|
|
23
48
|
```bash
|
|
24
49
|
npm install express cors
|
|
@@ -26,12 +51,42 @@ npm install express cors
|
|
|
26
51
|
|
|
27
52
|
## Quick Start
|
|
28
53
|
|
|
29
|
-
###
|
|
54
|
+
### Zero-Config (Recommended)
|
|
55
|
+
|
|
56
|
+
The simplest way to create an MCP server with auto-discovery:
|
|
30
57
|
|
|
31
58
|
```typescript
|
|
59
|
+
import { createHTTPServer } from "@leanmcp/core";
|
|
60
|
+
|
|
61
|
+
await createHTTPServer({
|
|
62
|
+
name: "my-mcp-server",
|
|
63
|
+
version: "1.0.0",
|
|
64
|
+
port: 3001,
|
|
65
|
+
cors: true,
|
|
66
|
+
logging: true
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Services are automatically discovered from ./mcp directory
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Directory Structure:**
|
|
73
|
+
```
|
|
74
|
+
your-project/
|
|
75
|
+
├── main.ts
|
|
76
|
+
└── mcp/
|
|
77
|
+
├── sentiment/
|
|
78
|
+
│ └── index.ts # export class SentimentService
|
|
79
|
+
├── weather/
|
|
80
|
+
│ └── index.ts # export class WeatherService
|
|
81
|
+
└── config.ts # Optional: shared dependencies
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Define a Service
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// mcp/sentiment/index.ts
|
|
32
88
|
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
33
89
|
|
|
34
|
-
// Define input schema as a class
|
|
35
90
|
class AnalyzeSentimentInput {
|
|
36
91
|
@SchemaConstraint({
|
|
37
92
|
description: 'Text to analyze',
|
|
@@ -48,109 +103,27 @@ class AnalyzeSentimentInput {
|
|
|
48
103
|
language?: string;
|
|
49
104
|
}
|
|
50
105
|
|
|
51
|
-
// Define output schema
|
|
52
|
-
class AnalyzeSentimentOutput {
|
|
53
|
-
@SchemaConstraint({ enum: ['positive', 'negative', 'neutral'] })
|
|
54
|
-
sentiment!: string;
|
|
55
|
-
|
|
56
|
-
@SchemaConstraint({ minimum: -1, maximum: 1 })
|
|
57
|
-
score!: number;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
106
|
export class SentimentService {
|
|
61
107
|
@Tool({
|
|
62
108
|
description: 'Analyze sentiment of text',
|
|
63
109
|
inputClass: AnalyzeSentimentInput
|
|
64
110
|
})
|
|
65
|
-
async analyzeSentiment(input: AnalyzeSentimentInput)
|
|
66
|
-
// Your implementation
|
|
111
|
+
async analyzeSentiment(input: AnalyzeSentimentInput) {
|
|
67
112
|
return {
|
|
68
113
|
sentiment: 'positive',
|
|
69
|
-
score: 0.8
|
|
70
|
-
confidence: 0.95
|
|
114
|
+
score: 0.8
|
|
71
115
|
};
|
|
72
116
|
}
|
|
73
117
|
}
|
|
74
118
|
```
|
|
75
119
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#### Option A: Zero-Config Auto-Discovery (Recommended)
|
|
79
|
-
|
|
80
|
-
The simplest way to create an HTTP server with auto-discovery:
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
84
|
-
|
|
85
|
-
// Create and start HTTP server with auto-discovery
|
|
86
|
-
await createHTTPServer({
|
|
87
|
-
name: "my-mcp-server",
|
|
88
|
-
version: "1.0.0",
|
|
89
|
-
port: 3000,
|
|
90
|
-
cors: true,
|
|
91
|
-
logging: true
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
console.log('\nMCP Server running');
|
|
95
|
-
console.log('HTTP endpoint: http://localhost:3000/mcp');
|
|
96
|
-
console.log('Health check: http://localhost:3000/health');
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**What happens automatically:**
|
|
100
|
-
- Services are discovered from `./mcp` directory
|
|
101
|
-
- HTTP server is created and started
|
|
102
|
-
- Session management is configured
|
|
103
|
-
- CORS is enabled (if specified)
|
|
104
|
-
|
|
105
|
-
**Directory Structure:**
|
|
106
|
-
```
|
|
107
|
-
your-project/
|
|
108
|
-
├── main.ts
|
|
109
|
-
└── mcp/
|
|
110
|
-
├── sentiment/
|
|
111
|
-
│ └── index.ts # export class SentimentService
|
|
112
|
-
├── weather/
|
|
113
|
-
│ └── index.ts # export class WeatherService
|
|
114
|
-
└── database/
|
|
115
|
-
└── index.ts # export class DatabaseService
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
#### Option B: Factory Pattern (Advanced)
|
|
119
|
-
|
|
120
|
-
For advanced use cases requiring manual service registration or custom configuration:
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
124
|
-
import { SentimentService } from "./services/sentiment";
|
|
125
|
-
|
|
126
|
-
// Create MCP server with factory function
|
|
127
|
-
const serverFactory = async () => {
|
|
128
|
-
const server = new MCPServer({
|
|
129
|
-
name: "my-mcp-server",
|
|
130
|
-
version: "1.0.0",
|
|
131
|
-
logging: true,
|
|
132
|
-
autoDiscover: false // Disable auto-discovery for manual registration
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Register services manually
|
|
136
|
-
server.registerService(new SentimentService());
|
|
137
|
-
|
|
138
|
-
return server.getServer();
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Start HTTP server with factory
|
|
142
|
-
await createHTTPServer(serverFactory, {
|
|
143
|
-
port: 3000,
|
|
144
|
-
cors: true,
|
|
145
|
-
logging: true
|
|
146
|
-
});
|
|
147
|
-
```
|
|
120
|
+
---
|
|
148
121
|
|
|
149
122
|
## Decorators
|
|
150
123
|
|
|
151
124
|
### @Tool
|
|
152
125
|
|
|
153
|
-
Marks a method as
|
|
126
|
+
Marks a method as a callable MCP tool.
|
|
154
127
|
|
|
155
128
|
```typescript
|
|
156
129
|
class CalculateInput {
|
|
@@ -170,9 +143,16 @@ async calculate(input: CalculateInput) {
|
|
|
170
143
|
}
|
|
171
144
|
```
|
|
172
145
|
|
|
146
|
+
**Options:**
|
|
147
|
+
|
|
148
|
+
| Option | Type | Description |
|
|
149
|
+
|--------|------|-------------|
|
|
150
|
+
| `description` | `string` | Tool description for the AI |
|
|
151
|
+
| `inputClass` | `Class` | Class defining input schema |
|
|
152
|
+
|
|
173
153
|
### @Prompt
|
|
174
154
|
|
|
175
|
-
Marks a method as
|
|
155
|
+
Marks a method as a reusable prompt template.
|
|
176
156
|
|
|
177
157
|
```typescript
|
|
178
158
|
class CodeReviewInput {
|
|
@@ -202,7 +182,10 @@ codeReview(input: CodeReviewInput) {
|
|
|
202
182
|
Marks a method as an MCP resource (data source).
|
|
203
183
|
|
|
204
184
|
```typescript
|
|
205
|
-
@Resource({
|
|
185
|
+
@Resource({
|
|
186
|
+
description: 'Get system configuration',
|
|
187
|
+
mimeType: 'application/json'
|
|
188
|
+
})
|
|
206
189
|
async getConfig() {
|
|
207
190
|
return {
|
|
208
191
|
version: "1.0.0",
|
|
@@ -213,7 +196,7 @@ async getConfig() {
|
|
|
213
196
|
|
|
214
197
|
### @SchemaConstraint
|
|
215
198
|
|
|
216
|
-
Add validation constraints to class properties
|
|
199
|
+
Add validation constraints to class properties.
|
|
217
200
|
|
|
218
201
|
```typescript
|
|
219
202
|
class UserInput {
|
|
@@ -242,6 +225,14 @@ class UserInput {
|
|
|
242
225
|
}
|
|
243
226
|
```
|
|
244
227
|
|
|
228
|
+
**Common constraints:**
|
|
229
|
+
- `description`, `default` — Documentation
|
|
230
|
+
- `minLength`, `maxLength` — String length
|
|
231
|
+
- `minimum`, `maximum` — Number range
|
|
232
|
+
- `enum` — Allowed values
|
|
233
|
+
- `format` — String format (`email`, `uri`, `date`, etc.)
|
|
234
|
+
- `pattern` — Regex pattern
|
|
235
|
+
|
|
245
236
|
### @Optional
|
|
246
237
|
|
|
247
238
|
Marks a property as optional in the schema.
|
|
@@ -257,8 +248,50 @@ class SearchInput {
|
|
|
257
248
|
}
|
|
258
249
|
```
|
|
259
250
|
|
|
251
|
+
---
|
|
252
|
+
|
|
260
253
|
## API Reference
|
|
261
254
|
|
|
255
|
+
### createHTTPServer
|
|
256
|
+
|
|
257
|
+
Create and start an HTTP server with auto-discovery.
|
|
258
|
+
|
|
259
|
+
**Simplified API (Recommended):**
|
|
260
|
+
```typescript
|
|
261
|
+
await createHTTPServer({
|
|
262
|
+
name: string; // Server name (required)
|
|
263
|
+
version: string; // Server version (required)
|
|
264
|
+
port?: number; // Port (default: 3001)
|
|
265
|
+
cors?: boolean | object; // Enable CORS (default: false)
|
|
266
|
+
logging?: boolean; // Enable logging (default: false)
|
|
267
|
+
debug?: boolean; // Verbose debug logs (default: false)
|
|
268
|
+
autoDiscover?: boolean; // Auto-discover services (default: true)
|
|
269
|
+
mcpDir?: string; // Custom mcp directory path
|
|
270
|
+
sessionTimeout?: number; // Session timeout in ms
|
|
271
|
+
stateless?: boolean; // Stateless mode for Lambda/serverless (default: true)
|
|
272
|
+
dashboard?: boolean; // Serve dashboard UI at / (default: true)
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Factory Pattern (Advanced):**
|
|
277
|
+
```typescript
|
|
278
|
+
const serverFactory = async () => {
|
|
279
|
+
const server = new MCPServer({
|
|
280
|
+
name: "my-server",
|
|
281
|
+
version: "1.0.0",
|
|
282
|
+
autoDiscover: false // Disable for manual registration
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
server.registerService(new MyService());
|
|
286
|
+
return server.getServer();
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
await createHTTPServer(serverFactory, {
|
|
290
|
+
port: 3001,
|
|
291
|
+
cors: true
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
262
295
|
### MCPServer
|
|
263
296
|
|
|
264
297
|
Main server class for registering services.
|
|
@@ -268,69 +301,39 @@ const server = new MCPServer({
|
|
|
268
301
|
name: string; // Server name
|
|
269
302
|
version: string; // Server version
|
|
270
303
|
logging?: boolean; // Enable logging (default: false)
|
|
271
|
-
debug?: boolean; //
|
|
272
|
-
autoDiscover?: boolean; //
|
|
273
|
-
mcpDir?: string; // Custom mcp directory path
|
|
304
|
+
debug?: boolean; // Verbose debug logs (default: false)
|
|
305
|
+
autoDiscover?: boolean; // Auto-discover from ./mcp (default: true)
|
|
306
|
+
mcpDir?: string; // Custom mcp directory path
|
|
274
307
|
});
|
|
275
308
|
|
|
276
|
-
// Manual registration
|
|
277
|
-
server.
|
|
278
|
-
|
|
279
|
-
// Get underlying MCP SDK server
|
|
280
|
-
server.getServer(): Server;
|
|
309
|
+
server.registerService(instance); // Manual registration
|
|
310
|
+
server.getServer(); // Get underlying MCP SDK server
|
|
281
311
|
```
|
|
282
312
|
|
|
283
|
-
|
|
313
|
+
---
|
|
284
314
|
|
|
285
|
-
-
|
|
286
|
-
- **`debug`**: Enable verbose debug logs showing detailed service registration (requires `logging: true`)
|
|
287
|
-
- **`autoDiscover`**: Automatically discover and register services from `./mcp` directory (default: `true`)
|
|
288
|
-
- **`mcpDir`**: Custom path to the mcp directory (default: auto-detected `./mcp`)
|
|
315
|
+
## Auto-Discovery
|
|
289
316
|
|
|
290
|
-
|
|
317
|
+
Services are automatically discovered from the `./mcp` directory:
|
|
291
318
|
|
|
292
|
-
|
|
319
|
+
1. Recursively scans for `index.ts` or `index.js` files
|
|
320
|
+
2. Dynamically imports each file
|
|
321
|
+
3. Looks for exported classes
|
|
322
|
+
4. Instantiates with no-args constructors
|
|
323
|
+
5. Registers all decorated methods
|
|
293
324
|
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
325
|
+
### Shared Dependencies
|
|
297
326
|
|
|
298
|
-
|
|
299
|
-
name: "my-server",
|
|
300
|
-
version: "1.0.0",
|
|
301
|
-
port: 3000,
|
|
302
|
-
logging: true // Enable logging
|
|
303
|
-
});
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
**With Debug Logging:**
|
|
307
|
-
```typescript
|
|
308
|
-
await createHTTPServer({
|
|
309
|
-
name: "my-server",
|
|
310
|
-
version: "1.0.0",
|
|
311
|
-
port: 3000,
|
|
312
|
-
logging: true,
|
|
313
|
-
debug: true // Show detailed service registration logs
|
|
314
|
-
});
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
**With Shared Dependencies:**
|
|
318
|
-
|
|
319
|
-
For services that need shared dependencies, create a `config.ts` (example) file in your `mcp` directory:
|
|
327
|
+
For services needing shared configuration (auth, database, etc.), create a `config.ts`:
|
|
320
328
|
|
|
321
329
|
```typescript
|
|
322
330
|
// mcp/config.ts
|
|
323
331
|
import { AuthProvider } from "@leanmcp/auth";
|
|
324
332
|
|
|
325
|
-
if (!process.env.COGNITO_USER_POOL_ID || !process.env.COGNITO_CLIENT_ID) {
|
|
326
|
-
throw new Error('Missing required Cognito configuration');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
333
|
export const authProvider = new AuthProvider('cognito', {
|
|
330
|
-
region: process.env.AWS_REGION
|
|
334
|
+
region: process.env.AWS_REGION,
|
|
331
335
|
userPoolId: process.env.COGNITO_USER_POOL_ID,
|
|
332
|
-
clientId: process.env.COGNITO_CLIENT_ID
|
|
333
|
-
clientSecret: process.env.COGNITO_CLIENT_SECRET
|
|
336
|
+
clientId: process.env.COGNITO_CLIENT_ID
|
|
334
337
|
});
|
|
335
338
|
|
|
336
339
|
await authProvider.init();
|
|
@@ -346,147 +349,29 @@ import { authProvider } from "../config.js";
|
|
|
346
349
|
|
|
347
350
|
@Authenticated(authProvider)
|
|
348
351
|
export class SlackService {
|
|
349
|
-
constructor() {
|
|
350
|
-
// No parameters needed - use environment or imported config
|
|
351
|
-
}
|
|
352
|
-
|
|
353
352
|
@Tool({ description: 'Send a message' })
|
|
354
|
-
async sendMessage(args:
|
|
353
|
+
async sendMessage(args: { channel: string; message: string }) {
|
|
355
354
|
// Implementation
|
|
356
355
|
}
|
|
357
356
|
}
|
|
358
357
|
```
|
|
359
358
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
```typescript
|
|
363
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
364
|
-
|
|
365
|
-
await createHTTPServer({
|
|
366
|
-
name: "my-server",
|
|
367
|
-
version: "1.0.0",
|
|
368
|
-
port: 3000,
|
|
369
|
-
logging: true
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
// Services are automatically discovered and registered
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
**How It Works:**
|
|
376
|
-
- Automatically discovers and registers services from the `./mcp` directory during server initialization
|
|
377
|
-
- Recursively scans for `index.ts` or `index.js` files
|
|
378
|
-
- Dynamically imports each file and looks for exported classes
|
|
379
|
-
- Instantiates services with no-args constructors
|
|
380
|
-
- Registers all discovered services with their decorated methods
|
|
381
|
-
|
|
382
|
-
**Directory Structure:**
|
|
383
|
-
```
|
|
384
|
-
your-project/
|
|
385
|
-
├── main.ts
|
|
386
|
-
└── mcp/
|
|
387
|
-
├── config.ts # Optional: shared dependencies
|
|
388
|
-
├── slack/
|
|
389
|
-
│ └── index.ts # export class SlackService
|
|
390
|
-
├── database/
|
|
391
|
-
│ └── index.ts # export class DatabaseService
|
|
392
|
-
└── auth/
|
|
393
|
-
└── index.ts # export class AuthService
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### createHTTPServer
|
|
397
|
-
|
|
398
|
-
Create and start an HTTP server with streamable transport.
|
|
399
|
-
|
|
400
|
-
**Simplified API (Recommended):**
|
|
401
|
-
```typescript
|
|
402
|
-
await createHTTPServer({
|
|
403
|
-
name: string; // Server name (required)
|
|
404
|
-
version: string; // Server version (required)
|
|
405
|
-
port?: number; // Port number (default: 3001)
|
|
406
|
-
cors?: boolean | object; // Enable CORS (default: false)
|
|
407
|
-
logging?: boolean; // Enable logging (default: false)
|
|
408
|
-
debug?: boolean; // Enable debug logs (default: false)
|
|
409
|
-
autoDiscover?: boolean; // Auto-discover services (default: true)
|
|
410
|
-
mcpDir?: string; // Custom mcp directory path (optional)
|
|
411
|
-
sessionTimeout?: number; // Session timeout in ms (optional)
|
|
412
|
-
});
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
**Factory Pattern (Advanced):**
|
|
416
|
-
```typescript
|
|
417
|
-
await createHTTPServer(
|
|
418
|
-
serverFactory: () => Server | Promise<Server>,
|
|
419
|
-
options: {
|
|
420
|
-
port?: number; // Port number (default: 3001)
|
|
421
|
-
cors?: boolean | object; // Enable CORS (default: false)
|
|
422
|
-
logging?: boolean; // Enable HTTP request logging (default: false)
|
|
423
|
-
sessionTimeout?: number; // Session timeout in ms (optional)
|
|
424
|
-
}
|
|
425
|
-
);
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
**CORS Configuration:**
|
|
429
|
-
```typescript
|
|
430
|
-
// Simple CORS (allow all origins - not recommended for production)
|
|
431
|
-
await createHTTPServer({
|
|
432
|
-
name: "my-server",
|
|
433
|
-
version: "1.0.0",
|
|
434
|
-
cors: true
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
// Advanced CORS configuration
|
|
438
|
-
await createHTTPServer({
|
|
439
|
-
name: "my-server",
|
|
440
|
-
version: "1.0.0",
|
|
441
|
-
cors: {
|
|
442
|
-
origin: 'https://example.com', // Specific origin
|
|
443
|
-
credentials: true // Allow credentials
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
### Schema Generation
|
|
449
|
-
|
|
450
|
-
Generate JSON Schema from TypeScript classes:
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
import { classToJsonSchemaWithConstraints } from "@leanmcp/core";
|
|
454
|
-
|
|
455
|
-
const schema = classToJsonSchemaWithConstraints(MyInputClass);
|
|
456
|
-
```
|
|
359
|
+
---
|
|
457
360
|
|
|
458
361
|
## HTTP Endpoints
|
|
459
362
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
## Environment Variables
|
|
467
|
-
|
|
468
|
-
```bash
|
|
469
|
-
PORT=3000 # Server port (optional)
|
|
470
|
-
NODE_ENV=production # Environment (optional)
|
|
471
|
-
```
|
|
363
|
+
| Endpoint | Method | Description |
|
|
364
|
+
|----------|--------|-------------|
|
|
365
|
+
| `/mcp` | POST | MCP protocol endpoint (JSON-RPC 2.0) |
|
|
366
|
+
| `/health` | GET | Health check |
|
|
367
|
+
| `/` | GET | Welcome message |
|
|
472
368
|
|
|
473
369
|
## Error Handling
|
|
474
370
|
|
|
475
|
-
|
|
371
|
+
Errors are automatically caught and returned in MCP format:
|
|
476
372
|
|
|
477
373
|
```typescript
|
|
478
|
-
|
|
479
|
-
@SchemaConstraint({ description: 'Numerator' })
|
|
480
|
-
a!: number;
|
|
481
|
-
|
|
482
|
-
@SchemaConstraint({ description: 'Denominator' })
|
|
483
|
-
b!: number;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
@Tool({
|
|
487
|
-
description: 'Divide numbers',
|
|
488
|
-
inputClass: DivideInput
|
|
489
|
-
})
|
|
374
|
+
@Tool({ description: 'Divide numbers', inputClass: DivideInput })
|
|
490
375
|
async divide(input: DivideInput) {
|
|
491
376
|
if (input.b === 0) {
|
|
492
377
|
throw new Error("Division by zero");
|
|
@@ -495,7 +380,7 @@ async divide(input: DivideInput) {
|
|
|
495
380
|
}
|
|
496
381
|
```
|
|
497
382
|
|
|
498
|
-
|
|
383
|
+
Returns:
|
|
499
384
|
```json
|
|
500
385
|
{
|
|
501
386
|
"content": [{"type": "text", "text": "Error: Division by zero"}],
|
|
@@ -503,9 +388,20 @@ Errors are returned as:
|
|
|
503
388
|
}
|
|
504
389
|
```
|
|
505
390
|
|
|
391
|
+
## Environment Variables
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
PORT=3001 # Server port
|
|
395
|
+
NODE_ENV=production # Environment
|
|
396
|
+
```
|
|
397
|
+
|
|
506
398
|
## TypeScript Support
|
|
507
399
|
|
|
508
|
-
|
|
400
|
+
**Key Points:**
|
|
401
|
+
- Input schema is defined via `inputClass` in the decorator
|
|
402
|
+
- Output type is inferred from the return type
|
|
403
|
+
- For tools with no input, omit `inputClass`
|
|
404
|
+
- Use `@SchemaConstraint` for validation and documentation
|
|
509
405
|
|
|
510
406
|
```typescript
|
|
511
407
|
class MyInput {
|
|
@@ -513,43 +409,29 @@ class MyInput {
|
|
|
513
409
|
field!: string;
|
|
514
410
|
}
|
|
515
411
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Input schema defined via inputClass, output type inferred from return type
|
|
521
|
-
@Tool({
|
|
522
|
-
description: 'My tool',
|
|
523
|
-
inputClass: MyInput
|
|
524
|
-
})
|
|
525
|
-
async myTool(input: MyInput): Promise<MyOutput> {
|
|
526
|
-
// TypeScript knows the exact types
|
|
527
|
-
const result: MyOutput = {
|
|
528
|
-
result: input.field.toUpperCase()
|
|
529
|
-
// Full autocomplete and type checking
|
|
530
|
-
};
|
|
531
|
-
return result;
|
|
412
|
+
@Tool({ description: 'My tool', inputClass: MyInput })
|
|
413
|
+
async myTool(input: MyInput): Promise<{ result: string }> {
|
|
414
|
+
return { result: input.field.toUpperCase() };
|
|
532
415
|
}
|
|
533
416
|
```
|
|
534
417
|
|
|
535
|
-
|
|
536
|
-
- Input schema is defined using `inputClass` in the `@Tool` decorator
|
|
537
|
-
- Output schema is inferred from the return type
|
|
538
|
-
- For tools with no input parameters, omit the `inputClass` option
|
|
539
|
-
- Use `@SchemaConstraint` decorators to add validation and documentation to your input classes
|
|
418
|
+
## Documentation
|
|
540
419
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
MIT
|
|
420
|
+
- [Full Documentation](https://docs.leanmcp.com/sdk/core)
|
|
544
421
|
|
|
545
422
|
## Related Packages
|
|
546
423
|
|
|
547
|
-
- [@leanmcp/cli](
|
|
548
|
-
- [@leanmcp/auth](
|
|
549
|
-
- [@leanmcp/
|
|
424
|
+
- [@leanmcp/cli](https://www.npmjs.com/package/@leanmcp/cli) — CLI tool for project creation
|
|
425
|
+
- [@leanmcp/auth](https://www.npmjs.com/package/@leanmcp/auth) — Authentication decorators
|
|
426
|
+
- [@leanmcp/ui](https://www.npmjs.com/package/@leanmcp/ui) — MCP App UI components
|
|
427
|
+
- [@leanmcp/elicitation](https://www.npmjs.com/package/@leanmcp/elicitation) — Structured user input
|
|
550
428
|
|
|
551
429
|
## Links
|
|
552
430
|
|
|
553
431
|
- [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
|
|
432
|
+
- [NPM Package](https://www.npmjs.com/package/@leanmcp/core)
|
|
554
433
|
- [MCP Specification](https://spec.modelcontextprotocol.io/)
|
|
555
|
-
|
|
434
|
+
|
|
435
|
+
## License
|
|
436
|
+
|
|
437
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -465,6 +465,10 @@ declare class MCPServer {
|
|
|
465
465
|
registerService(instance: any): void;
|
|
466
466
|
/**
|
|
467
467
|
* Watch UI manifest for changes and reload resources dynamically
|
|
468
|
+
*
|
|
469
|
+
* CRITICAL: Only for stateful mode. In stateless mode, each request
|
|
470
|
+
* creates a fresh server that reads the manifest directly, making
|
|
471
|
+
* watchers both unnecessary and a memory leak source.
|
|
468
472
|
*/
|
|
469
473
|
private watchUIManifest;
|
|
470
474
|
/**
|
|
@@ -520,6 +524,11 @@ declare class MCPServer {
|
|
|
520
524
|
} | undefined;
|
|
521
525
|
} | undefined;
|
|
522
526
|
}>;
|
|
527
|
+
/**
|
|
528
|
+
* Clean up all registered services, watchers, and resources
|
|
529
|
+
* CRITICAL for stateless mode to prevent memory leaks
|
|
530
|
+
*/
|
|
531
|
+
close(): void;
|
|
523
532
|
/**
|
|
524
533
|
* Cleanup resources (call on server shutdown)
|
|
525
534
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -465,6 +465,10 @@ declare class MCPServer {
|
|
|
465
465
|
registerService(instance: any): void;
|
|
466
466
|
/**
|
|
467
467
|
* Watch UI manifest for changes and reload resources dynamically
|
|
468
|
+
*
|
|
469
|
+
* CRITICAL: Only for stateful mode. In stateless mode, each request
|
|
470
|
+
* creates a fresh server that reads the manifest directly, making
|
|
471
|
+
* watchers both unnecessary and a memory leak source.
|
|
468
472
|
*/
|
|
469
473
|
private watchUIManifest;
|
|
470
474
|
/**
|
|
@@ -520,6 +524,11 @@ declare class MCPServer {
|
|
|
520
524
|
} | undefined;
|
|
521
525
|
} | undefined;
|
|
522
526
|
}>;
|
|
527
|
+
/**
|
|
528
|
+
* Clean up all registered services, watchers, and resources
|
|
529
|
+
* CRITICAL for stateless mode to prevent memory leaks
|
|
530
|
+
*/
|
|
531
|
+
close(): void;
|
|
523
532
|
/**
|
|
524
533
|
* Cleanup resources (call on server shutdown)
|
|
525
534
|
*/
|
package/dist/index.js
CHANGED
|
@@ -796,7 +796,11 @@ async function createHTTPServer(serverInput, options) {
|
|
|
796
796
|
await transport.handleRequest(req, res, req.body);
|
|
797
797
|
res.on("close", () => {
|
|
798
798
|
transport.close();
|
|
799
|
-
freshServer.close
|
|
799
|
+
if ("close" in freshServer && typeof freshServer.close === "function") {
|
|
800
|
+
freshServer.close();
|
|
801
|
+
} else {
|
|
802
|
+
freshServer.close();
|
|
803
|
+
}
|
|
800
804
|
});
|
|
801
805
|
} catch (error) {
|
|
802
806
|
logger.error("Error handling MCP request:", error);
|
|
@@ -1439,8 +1443,15 @@ var init_index = __esm({
|
|
|
1439
1443
|
}
|
|
1440
1444
|
/**
|
|
1441
1445
|
* Watch UI manifest for changes and reload resources dynamically
|
|
1446
|
+
*
|
|
1447
|
+
* CRITICAL: Only for stateful mode. In stateless mode, each request
|
|
1448
|
+
* creates a fresh server that reads the manifest directly, making
|
|
1449
|
+
* watchers both unnecessary and a memory leak source.
|
|
1442
1450
|
*/
|
|
1443
1451
|
watchUIManifest() {
|
|
1452
|
+
if (this.options.stateless) {
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1444
1455
|
try {
|
|
1445
1456
|
const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
|
|
1446
1457
|
if (!import_fs.default.existsSync(manifestPath)) {
|
|
@@ -1624,6 +1635,25 @@ var init_index = __esm({
|
|
|
1624
1635
|
return this.server;
|
|
1625
1636
|
}
|
|
1626
1637
|
/**
|
|
1638
|
+
* Clean up all registered services, watchers, and resources
|
|
1639
|
+
* CRITICAL for stateless mode to prevent memory leaks
|
|
1640
|
+
*/
|
|
1641
|
+
close() {
|
|
1642
|
+
if (this.manifestWatcher) {
|
|
1643
|
+
try {
|
|
1644
|
+
this.manifestWatcher.close();
|
|
1645
|
+
} catch (e) {
|
|
1646
|
+
}
|
|
1647
|
+
this.manifestWatcher = null;
|
|
1648
|
+
}
|
|
1649
|
+
this.tools.clear();
|
|
1650
|
+
this.prompts.clear();
|
|
1651
|
+
this.resources.clear();
|
|
1652
|
+
if (this.server && typeof this.server.close === "function") {
|
|
1653
|
+
this.server.close();
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1627
1657
|
* Cleanup resources (call on server shutdown)
|
|
1628
1658
|
*/
|
|
1629
1659
|
async cleanup() {
|
package/dist/index.mjs
CHANGED
|
@@ -758,7 +758,11 @@ async function createHTTPServer(serverInput, options) {
|
|
|
758
758
|
await transport.handleRequest(req, res, req.body);
|
|
759
759
|
res.on("close", () => {
|
|
760
760
|
transport.close();
|
|
761
|
-
freshServer.close
|
|
761
|
+
if ("close" in freshServer && typeof freshServer.close === "function") {
|
|
762
|
+
freshServer.close();
|
|
763
|
+
} else {
|
|
764
|
+
freshServer.close();
|
|
765
|
+
}
|
|
762
766
|
});
|
|
763
767
|
} catch (error) {
|
|
764
768
|
logger.error("Error handling MCP request:", error);
|
|
@@ -1336,8 +1340,15 @@ var MCPServer = class {
|
|
|
1336
1340
|
}
|
|
1337
1341
|
/**
|
|
1338
1342
|
* Watch UI manifest for changes and reload resources dynamically
|
|
1343
|
+
*
|
|
1344
|
+
* CRITICAL: Only for stateful mode. In stateless mode, each request
|
|
1345
|
+
* creates a fresh server that reads the manifest directly, making
|
|
1346
|
+
* watchers both unnecessary and a memory leak source.
|
|
1339
1347
|
*/
|
|
1340
1348
|
watchUIManifest() {
|
|
1349
|
+
if (this.options.stateless) {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1341
1352
|
try {
|
|
1342
1353
|
const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
|
|
1343
1354
|
if (!fs.existsSync(manifestPath)) {
|
|
@@ -1521,6 +1532,25 @@ var MCPServer = class {
|
|
|
1521
1532
|
return this.server;
|
|
1522
1533
|
}
|
|
1523
1534
|
/**
|
|
1535
|
+
* Clean up all registered services, watchers, and resources
|
|
1536
|
+
* CRITICAL for stateless mode to prevent memory leaks
|
|
1537
|
+
*/
|
|
1538
|
+
close() {
|
|
1539
|
+
if (this.manifestWatcher) {
|
|
1540
|
+
try {
|
|
1541
|
+
this.manifestWatcher.close();
|
|
1542
|
+
} catch (e) {
|
|
1543
|
+
}
|
|
1544
|
+
this.manifestWatcher = null;
|
|
1545
|
+
}
|
|
1546
|
+
this.tools.clear();
|
|
1547
|
+
this.prompts.clear();
|
|
1548
|
+
this.resources.clear();
|
|
1549
|
+
if (this.server && typeof this.server.close === "function") {
|
|
1550
|
+
this.server.close();
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1524
1554
|
* Cleanup resources (call on server shutdown)
|
|
1525
1555
|
*/
|
|
1526
1556
|
async cleanup() {
|