@leanmcp/core 0.3.10 → 0.3.12
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 +211 -292
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +56 -3
- package/dist/index.mjs +56 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
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
|
-
- **
|
|
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
|
+
- **Structured Content** — Automatic `structuredContent` for ChatGPT Apps SDK compatibility
|
|
40
|
+
- **MCP Compliant** — Built on official `@modelcontextprotocol/sdk`
|
|
13
41
|
|
|
14
42
|
## Installation
|
|
15
43
|
|
|
@@ -17,8 +45,6 @@ Core library for building Model Context Protocol (MCP) servers with TypeScript d
|
|
|
17
45
|
npm install @leanmcp/core
|
|
18
46
|
```
|
|
19
47
|
|
|
20
|
-
### Peer Dependencies
|
|
21
|
-
|
|
22
48
|
For HTTP server support:
|
|
23
49
|
```bash
|
|
24
50
|
npm install express cors
|
|
@@ -26,12 +52,42 @@ npm install express cors
|
|
|
26
52
|
|
|
27
53
|
## Quick Start
|
|
28
54
|
|
|
29
|
-
###
|
|
55
|
+
### Zero-Config (Recommended)
|
|
56
|
+
|
|
57
|
+
The simplest way to create an MCP server with auto-discovery:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { createHTTPServer } from "@leanmcp/core";
|
|
61
|
+
|
|
62
|
+
await createHTTPServer({
|
|
63
|
+
name: "my-mcp-server",
|
|
64
|
+
version: "1.0.0",
|
|
65
|
+
port: 3001,
|
|
66
|
+
cors: true,
|
|
67
|
+
logging: true
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Services are automatically discovered from ./mcp directory
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Directory Structure:**
|
|
74
|
+
```
|
|
75
|
+
your-project/
|
|
76
|
+
├── main.ts
|
|
77
|
+
└── mcp/
|
|
78
|
+
├── sentiment/
|
|
79
|
+
│ └── index.ts # export class SentimentService
|
|
80
|
+
├── weather/
|
|
81
|
+
│ └── index.ts # export class WeatherService
|
|
82
|
+
└── config.ts # Optional: shared dependencies
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Define a Service
|
|
30
86
|
|
|
31
87
|
```typescript
|
|
88
|
+
// mcp/sentiment/index.ts
|
|
32
89
|
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";
|
|
33
90
|
|
|
34
|
-
// Define input schema as a class
|
|
35
91
|
class AnalyzeSentimentInput {
|
|
36
92
|
@SchemaConstraint({
|
|
37
93
|
description: 'Text to analyze',
|
|
@@ -48,109 +104,27 @@ class AnalyzeSentimentInput {
|
|
|
48
104
|
language?: string;
|
|
49
105
|
}
|
|
50
106
|
|
|
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
107
|
export class SentimentService {
|
|
61
108
|
@Tool({
|
|
62
109
|
description: 'Analyze sentiment of text',
|
|
63
110
|
inputClass: AnalyzeSentimentInput
|
|
64
111
|
})
|
|
65
|
-
async analyzeSentiment(input: AnalyzeSentimentInput)
|
|
66
|
-
// Your implementation
|
|
112
|
+
async analyzeSentiment(input: AnalyzeSentimentInput) {
|
|
67
113
|
return {
|
|
68
114
|
sentiment: 'positive',
|
|
69
|
-
score: 0.8
|
|
70
|
-
confidence: 0.95
|
|
115
|
+
score: 0.8
|
|
71
116
|
};
|
|
72
117
|
}
|
|
73
118
|
}
|
|
74
119
|
```
|
|
75
120
|
|
|
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
|
-
```
|
|
121
|
+
---
|
|
148
122
|
|
|
149
123
|
## Decorators
|
|
150
124
|
|
|
151
125
|
### @Tool
|
|
152
126
|
|
|
153
|
-
Marks a method as
|
|
127
|
+
Marks a method as a callable MCP tool.
|
|
154
128
|
|
|
155
129
|
```typescript
|
|
156
130
|
class CalculateInput {
|
|
@@ -170,9 +144,16 @@ async calculate(input: CalculateInput) {
|
|
|
170
144
|
}
|
|
171
145
|
```
|
|
172
146
|
|
|
147
|
+
**Options:**
|
|
148
|
+
|
|
149
|
+
| Option | Type | Description |
|
|
150
|
+
|--------|------|-------------|
|
|
151
|
+
| `description` | `string` | Tool description for the AI |
|
|
152
|
+
| `inputClass` | `Class` | Class defining input schema |
|
|
153
|
+
|
|
173
154
|
### @Prompt
|
|
174
155
|
|
|
175
|
-
Marks a method as
|
|
156
|
+
Marks a method as a reusable prompt template.
|
|
176
157
|
|
|
177
158
|
```typescript
|
|
178
159
|
class CodeReviewInput {
|
|
@@ -202,7 +183,10 @@ codeReview(input: CodeReviewInput) {
|
|
|
202
183
|
Marks a method as an MCP resource (data source).
|
|
203
184
|
|
|
204
185
|
```typescript
|
|
205
|
-
@Resource({
|
|
186
|
+
@Resource({
|
|
187
|
+
description: 'Get system configuration',
|
|
188
|
+
mimeType: 'application/json'
|
|
189
|
+
})
|
|
206
190
|
async getConfig() {
|
|
207
191
|
return {
|
|
208
192
|
version: "1.0.0",
|
|
@@ -213,7 +197,7 @@ async getConfig() {
|
|
|
213
197
|
|
|
214
198
|
### @SchemaConstraint
|
|
215
199
|
|
|
216
|
-
Add validation constraints to class properties
|
|
200
|
+
Add validation constraints to class properties.
|
|
217
201
|
|
|
218
202
|
```typescript
|
|
219
203
|
class UserInput {
|
|
@@ -242,6 +226,14 @@ class UserInput {
|
|
|
242
226
|
}
|
|
243
227
|
```
|
|
244
228
|
|
|
229
|
+
**Common constraints:**
|
|
230
|
+
- `description`, `default` — Documentation
|
|
231
|
+
- `minLength`, `maxLength` — String length
|
|
232
|
+
- `minimum`, `maximum` — Number range
|
|
233
|
+
- `enum` — Allowed values
|
|
234
|
+
- `format` — String format (`email`, `uri`, `date`, etc.)
|
|
235
|
+
- `pattern` — Regex pattern
|
|
236
|
+
|
|
245
237
|
### @Optional
|
|
246
238
|
|
|
247
239
|
Marks a property as optional in the schema.
|
|
@@ -257,8 +249,50 @@ class SearchInput {
|
|
|
257
249
|
}
|
|
258
250
|
```
|
|
259
251
|
|
|
252
|
+
---
|
|
253
|
+
|
|
260
254
|
## API Reference
|
|
261
255
|
|
|
256
|
+
### createHTTPServer
|
|
257
|
+
|
|
258
|
+
Create and start an HTTP server with auto-discovery.
|
|
259
|
+
|
|
260
|
+
**Simplified API (Recommended):**
|
|
261
|
+
```typescript
|
|
262
|
+
await createHTTPServer({
|
|
263
|
+
name: string; // Server name (required)
|
|
264
|
+
version: string; // Server version (required)
|
|
265
|
+
port?: number; // Port (default: 3001)
|
|
266
|
+
cors?: boolean | object; // Enable CORS (default: false)
|
|
267
|
+
logging?: boolean; // Enable logging (default: false)
|
|
268
|
+
debug?: boolean; // Verbose debug logs (default: false)
|
|
269
|
+
autoDiscover?: boolean; // Auto-discover services (default: true)
|
|
270
|
+
mcpDir?: string; // Custom mcp directory path
|
|
271
|
+
sessionTimeout?: number; // Session timeout in ms
|
|
272
|
+
stateless?: boolean; // Stateless mode for Lambda/serverless (default: true)
|
|
273
|
+
dashboard?: boolean; // Serve dashboard UI at / (default: true)
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Factory Pattern (Advanced):**
|
|
278
|
+
```typescript
|
|
279
|
+
const serverFactory = async () => {
|
|
280
|
+
const server = new MCPServer({
|
|
281
|
+
name: "my-server",
|
|
282
|
+
version: "1.0.0",
|
|
283
|
+
autoDiscover: false // Disable for manual registration
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
server.registerService(new MyService());
|
|
287
|
+
return server.getServer();
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
await createHTTPServer(serverFactory, {
|
|
291
|
+
port: 3001,
|
|
292
|
+
cors: true
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
262
296
|
### MCPServer
|
|
263
297
|
|
|
264
298
|
Main server class for registering services.
|
|
@@ -268,69 +302,39 @@ const server = new MCPServer({
|
|
|
268
302
|
name: string; // Server name
|
|
269
303
|
version: string; // Server version
|
|
270
304
|
logging?: boolean; // Enable logging (default: false)
|
|
271
|
-
debug?: boolean; //
|
|
272
|
-
autoDiscover?: boolean; //
|
|
273
|
-
mcpDir?: string; // Custom mcp directory path
|
|
305
|
+
debug?: boolean; // Verbose debug logs (default: false)
|
|
306
|
+
autoDiscover?: boolean; // Auto-discover from ./mcp (default: true)
|
|
307
|
+
mcpDir?: string; // Custom mcp directory path
|
|
274
308
|
});
|
|
275
309
|
|
|
276
|
-
// Manual registration
|
|
277
|
-
server.
|
|
278
|
-
|
|
279
|
-
// Get underlying MCP SDK server
|
|
280
|
-
server.getServer(): Server;
|
|
310
|
+
server.registerService(instance); // Manual registration
|
|
311
|
+
server.getServer(); // Get underlying MCP SDK server
|
|
281
312
|
```
|
|
282
313
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
- **`logging`**: Enable basic logging for server operations
|
|
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`)
|
|
289
|
-
|
|
290
|
-
#### Zero-Config Auto-Discovery
|
|
314
|
+
---
|
|
291
315
|
|
|
292
|
-
|
|
316
|
+
## Auto-Discovery
|
|
293
317
|
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
297
|
-
|
|
298
|
-
await createHTTPServer({
|
|
299
|
-
name: "my-server",
|
|
300
|
-
version: "1.0.0",
|
|
301
|
-
port: 3000,
|
|
302
|
-
logging: true // Enable logging
|
|
303
|
-
});
|
|
304
|
-
```
|
|
318
|
+
Services are automatically discovered from the `./mcp` directory:
|
|
305
319
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
port: 3000,
|
|
312
|
-
logging: true,
|
|
313
|
-
debug: true // Show detailed service registration logs
|
|
314
|
-
});
|
|
315
|
-
```
|
|
320
|
+
1. Recursively scans for `index.ts` or `index.js` files
|
|
321
|
+
2. Dynamically imports each file
|
|
322
|
+
3. Looks for exported classes
|
|
323
|
+
4. Instantiates with no-args constructors
|
|
324
|
+
5. Registers all decorated methods
|
|
316
325
|
|
|
317
|
-
|
|
326
|
+
### Shared Dependencies
|
|
318
327
|
|
|
319
|
-
For services
|
|
328
|
+
For services needing shared configuration (auth, database, etc.), create a `config.ts`:
|
|
320
329
|
|
|
321
330
|
```typescript
|
|
322
331
|
// mcp/config.ts
|
|
323
332
|
import { AuthProvider } from "@leanmcp/auth";
|
|
324
333
|
|
|
325
|
-
if (!process.env.COGNITO_USER_POOL_ID || !process.env.COGNITO_CLIENT_ID) {
|
|
326
|
-
throw new Error('Missing required Cognito configuration');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
334
|
export const authProvider = new AuthProvider('cognito', {
|
|
330
|
-
region: process.env.AWS_REGION
|
|
335
|
+
region: process.env.AWS_REGION,
|
|
331
336
|
userPoolId: process.env.COGNITO_USER_POOL_ID,
|
|
332
|
-
clientId: process.env.COGNITO_CLIENT_ID
|
|
333
|
-
clientSecret: process.env.COGNITO_CLIENT_SECRET
|
|
337
|
+
clientId: process.env.COGNITO_CLIENT_ID
|
|
334
338
|
});
|
|
335
339
|
|
|
336
340
|
await authProvider.init();
|
|
@@ -346,147 +350,65 @@ import { authProvider } from "../config.js";
|
|
|
346
350
|
|
|
347
351
|
@Authenticated(authProvider)
|
|
348
352
|
export class SlackService {
|
|
349
|
-
constructor() {
|
|
350
|
-
// No parameters needed - use environment or imported config
|
|
351
|
-
}
|
|
352
|
-
|
|
353
353
|
@Tool({ description: 'Send a message' })
|
|
354
|
-
async sendMessage(args:
|
|
354
|
+
async sendMessage(args: { channel: string; message: string }) {
|
|
355
355
|
// Implementation
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
```
|
|
359
359
|
|
|
360
|
-
|
|
360
|
+
---
|
|
361
361
|
|
|
362
|
-
|
|
363
|
-
import { createHTTPServer } from "@leanmcp/core";
|
|
362
|
+
## Structured Content
|
|
364
363
|
|
|
365
|
-
|
|
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
|
-
```
|
|
364
|
+
Tool return values are automatically exposed as `structuredContent` in the MCP response, enabling ChatGPT Apps SDK compatibility.
|
|
395
365
|
|
|
396
|
-
|
|
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
|
-
```
|
|
366
|
+
**Automatic Handling:**
|
|
414
367
|
|
|
415
|
-
**Factory Pattern (Advanced):**
|
|
416
368
|
```typescript
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
logging?: boolean; // Enable HTTP request logging (default: false)
|
|
423
|
-
sessionTimeout?: number; // Session timeout in ms (optional)
|
|
424
|
-
}
|
|
425
|
-
);
|
|
369
|
+
@Tool({ description: 'List channels' })
|
|
370
|
+
async listChannels() {
|
|
371
|
+
// Return a plain object - it becomes structuredContent automatically
|
|
372
|
+
return { channels: [...] };
|
|
373
|
+
}
|
|
426
374
|
```
|
|
427
375
|
|
|
428
|
-
|
|
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
|
-
});
|
|
376
|
+
The response includes both `content` (text) and `structuredContent` (object):
|
|
436
377
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
origin: 'https://example.com', // Specific origin
|
|
443
|
-
credentials: true // Allow credentials
|
|
444
|
-
}
|
|
445
|
-
});
|
|
378
|
+
```json
|
|
379
|
+
{
|
|
380
|
+
"content": [{ "type": "text", "text": "{\"channels\": [...]}" }],
|
|
381
|
+
"structuredContent": { "channels": [...] }
|
|
382
|
+
}
|
|
446
383
|
```
|
|
447
384
|
|
|
448
|
-
|
|
385
|
+
**Manual MCP Response:**
|
|
449
386
|
|
|
450
|
-
|
|
387
|
+
If your tool returns a manual MCP response (with `content` array), the SDK extracts data from `content[0].text`:
|
|
451
388
|
|
|
452
389
|
```typescript
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
390
|
+
return {
|
|
391
|
+
content: [{ type: 'text', text: JSON.stringify({ channels }) }]
|
|
392
|
+
};
|
|
393
|
+
// structuredContent will be { channels: [...] }
|
|
456
394
|
```
|
|
457
395
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
When using `createHTTPServer`, the following endpoints are available:
|
|
396
|
+
---
|
|
461
397
|
|
|
462
|
-
|
|
463
|
-
- `GET /health` - Health check endpoint
|
|
464
|
-
- `GET /` - Welcome message
|
|
465
|
-
|
|
466
|
-
## Environment Variables
|
|
398
|
+
## HTTP Endpoints
|
|
467
399
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
400
|
+
| Endpoint | Method | Description |
|
|
401
|
+
|----------|--------|-------------|
|
|
402
|
+
| `/mcp` | POST | MCP protocol endpoint (JSON-RPC 2.0) |
|
|
403
|
+
| `/health` | GET | Health check |
|
|
404
|
+
| `/` | GET | Welcome message |
|
|
472
405
|
|
|
473
406
|
## Error Handling
|
|
474
407
|
|
|
475
|
-
|
|
408
|
+
Errors are automatically caught and returned in MCP format:
|
|
476
409
|
|
|
477
410
|
```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
|
-
})
|
|
411
|
+
@Tool({ description: 'Divide numbers', inputClass: DivideInput })
|
|
490
412
|
async divide(input: DivideInput) {
|
|
491
413
|
if (input.b === 0) {
|
|
492
414
|
throw new Error("Division by zero");
|
|
@@ -495,7 +417,7 @@ async divide(input: DivideInput) {
|
|
|
495
417
|
}
|
|
496
418
|
```
|
|
497
419
|
|
|
498
|
-
|
|
420
|
+
Returns:
|
|
499
421
|
```json
|
|
500
422
|
{
|
|
501
423
|
"content": [{"type": "text", "text": "Error: Division by zero"}],
|
|
@@ -503,9 +425,20 @@ Errors are returned as:
|
|
|
503
425
|
}
|
|
504
426
|
```
|
|
505
427
|
|
|
428
|
+
## Environment Variables
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
PORT=3001 # Server port
|
|
432
|
+
NODE_ENV=production # Environment
|
|
433
|
+
```
|
|
434
|
+
|
|
506
435
|
## TypeScript Support
|
|
507
436
|
|
|
508
|
-
|
|
437
|
+
**Key Points:**
|
|
438
|
+
- Input schema is defined via `inputClass` in the decorator
|
|
439
|
+
- Output type is inferred from the return type
|
|
440
|
+
- For tools with no input, omit `inputClass`
|
|
441
|
+
- Use `@SchemaConstraint` for validation and documentation
|
|
509
442
|
|
|
510
443
|
```typescript
|
|
511
444
|
class MyInput {
|
|
@@ -513,43 +446,29 @@ class MyInput {
|
|
|
513
446
|
field!: string;
|
|
514
447
|
}
|
|
515
448
|
|
|
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;
|
|
449
|
+
@Tool({ description: 'My tool', inputClass: MyInput })
|
|
450
|
+
async myTool(input: MyInput): Promise<{ result: string }> {
|
|
451
|
+
return { result: input.field.toUpperCase() };
|
|
532
452
|
}
|
|
533
453
|
```
|
|
534
454
|
|
|
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
|
|
540
|
-
|
|
541
|
-
## License
|
|
455
|
+
## Documentation
|
|
542
456
|
|
|
543
|
-
|
|
457
|
+
- [Full Documentation](https://docs.leanmcp.com/sdk/core)
|
|
544
458
|
|
|
545
459
|
## Related Packages
|
|
546
460
|
|
|
547
|
-
- [@leanmcp/cli](
|
|
548
|
-
- [@leanmcp/auth](
|
|
549
|
-
- [@leanmcp/
|
|
461
|
+
- [@leanmcp/cli](https://www.npmjs.com/package/@leanmcp/cli) — CLI tool for project creation
|
|
462
|
+
- [@leanmcp/auth](https://www.npmjs.com/package/@leanmcp/auth) — Authentication decorators
|
|
463
|
+
- [@leanmcp/ui](https://www.npmjs.com/package/@leanmcp/ui) — MCP App UI components
|
|
464
|
+
- [@leanmcp/elicitation](https://www.npmjs.com/package/@leanmcp/elicitation) — Structured user input
|
|
550
465
|
|
|
551
466
|
## Links
|
|
552
467
|
|
|
553
468
|
- [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
|
|
469
|
+
- [NPM Package](https://www.npmjs.com/package/@leanmcp/core)
|
|
554
470
|
- [MCP Specification](https://spec.modelcontextprotocol.io/)
|
|
555
|
-
|
|
471
|
+
|
|
472
|
+
## License
|
|
473
|
+
|
|
474
|
+
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);
|
|
@@ -1157,10 +1161,27 @@ var init_index = __esm({
|
|
|
1157
1161
|
const meta = request.params._meta;
|
|
1158
1162
|
const result = await tool.method.call(tool.instance, request.params.arguments, meta);
|
|
1159
1163
|
let formattedResult = result;
|
|
1164
|
+
let structuredContent = void 0;
|
|
1160
1165
|
if (methodMeta.renderFormat === "markdown" && typeof result === "string") {
|
|
1161
1166
|
formattedResult = result;
|
|
1162
|
-
} else if (
|
|
1163
|
-
|
|
1167
|
+
} else if (typeof result === "object" && result !== null) {
|
|
1168
|
+
if ("structuredContent" in result && Object.keys(result).length === 1) {
|
|
1169
|
+
structuredContent = result.structuredContent;
|
|
1170
|
+
formattedResult = JSON.stringify(structuredContent, null, 2);
|
|
1171
|
+
} else if ("content" in result && Array.isArray(result.content)) {
|
|
1172
|
+
const textItem = result.content.find((c) => c.type === "text");
|
|
1173
|
+
if (textItem?.text) {
|
|
1174
|
+
try {
|
|
1175
|
+
structuredContent = JSON.parse(textItem.text);
|
|
1176
|
+
} catch {
|
|
1177
|
+
structuredContent = textItem.text;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
formattedResult = JSON.stringify(result, null, 2);
|
|
1181
|
+
} else {
|
|
1182
|
+
structuredContent = result;
|
|
1183
|
+
formattedResult = JSON.stringify(result, null, 2);
|
|
1184
|
+
}
|
|
1164
1185
|
} else {
|
|
1165
1186
|
formattedResult = String(result);
|
|
1166
1187
|
}
|
|
@@ -1172,6 +1193,12 @@ var init_index = __esm({
|
|
|
1172
1193
|
}
|
|
1173
1194
|
]
|
|
1174
1195
|
};
|
|
1196
|
+
if (structuredContent) {
|
|
1197
|
+
response.structuredContent = structuredContent;
|
|
1198
|
+
if (this.logger) {
|
|
1199
|
+
this.logger.debug(`[MCPServer] Setting structuredContent: ${JSON.stringify(structuredContent).slice(0, 100)}...`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1175
1202
|
if (tool._meta && Object.keys(tool._meta).length > 0) {
|
|
1176
1203
|
response._meta = tool._meta;
|
|
1177
1204
|
}
|
|
@@ -1439,8 +1466,15 @@ var init_index = __esm({
|
|
|
1439
1466
|
}
|
|
1440
1467
|
/**
|
|
1441
1468
|
* Watch UI manifest for changes and reload resources dynamically
|
|
1469
|
+
*
|
|
1470
|
+
* CRITICAL: Only for stateful mode. In stateless mode, each request
|
|
1471
|
+
* creates a fresh server that reads the manifest directly, making
|
|
1472
|
+
* watchers both unnecessary and a memory leak source.
|
|
1442
1473
|
*/
|
|
1443
1474
|
watchUIManifest() {
|
|
1475
|
+
if (this.options.stateless) {
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1444
1478
|
try {
|
|
1445
1479
|
const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
|
|
1446
1480
|
if (!import_fs.default.existsSync(manifestPath)) {
|
|
@@ -1624,6 +1658,25 @@ var init_index = __esm({
|
|
|
1624
1658
|
return this.server;
|
|
1625
1659
|
}
|
|
1626
1660
|
/**
|
|
1661
|
+
* Clean up all registered services, watchers, and resources
|
|
1662
|
+
* CRITICAL for stateless mode to prevent memory leaks
|
|
1663
|
+
*/
|
|
1664
|
+
close() {
|
|
1665
|
+
if (this.manifestWatcher) {
|
|
1666
|
+
try {
|
|
1667
|
+
this.manifestWatcher.close();
|
|
1668
|
+
} catch (e) {
|
|
1669
|
+
}
|
|
1670
|
+
this.manifestWatcher = null;
|
|
1671
|
+
}
|
|
1672
|
+
this.tools.clear();
|
|
1673
|
+
this.prompts.clear();
|
|
1674
|
+
this.resources.clear();
|
|
1675
|
+
if (this.server && typeof this.server.close === "function") {
|
|
1676
|
+
this.server.close();
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1627
1680
|
* Cleanup resources (call on server shutdown)
|
|
1628
1681
|
*/
|
|
1629
1682
|
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);
|
|
@@ -1054,10 +1058,27 @@ var MCPServer = class {
|
|
|
1054
1058
|
const meta = request.params._meta;
|
|
1055
1059
|
const result = await tool.method.call(tool.instance, request.params.arguments, meta);
|
|
1056
1060
|
let formattedResult = result;
|
|
1061
|
+
let structuredContent = void 0;
|
|
1057
1062
|
if (methodMeta.renderFormat === "markdown" && typeof result === "string") {
|
|
1058
1063
|
formattedResult = result;
|
|
1059
|
-
} else if (
|
|
1060
|
-
|
|
1064
|
+
} else if (typeof result === "object" && result !== null) {
|
|
1065
|
+
if ("structuredContent" in result && Object.keys(result).length === 1) {
|
|
1066
|
+
structuredContent = result.structuredContent;
|
|
1067
|
+
formattedResult = JSON.stringify(structuredContent, null, 2);
|
|
1068
|
+
} else if ("content" in result && Array.isArray(result.content)) {
|
|
1069
|
+
const textItem = result.content.find((c) => c.type === "text");
|
|
1070
|
+
if (textItem?.text) {
|
|
1071
|
+
try {
|
|
1072
|
+
structuredContent = JSON.parse(textItem.text);
|
|
1073
|
+
} catch {
|
|
1074
|
+
structuredContent = textItem.text;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
formattedResult = JSON.stringify(result, null, 2);
|
|
1078
|
+
} else {
|
|
1079
|
+
structuredContent = result;
|
|
1080
|
+
formattedResult = JSON.stringify(result, null, 2);
|
|
1081
|
+
}
|
|
1061
1082
|
} else {
|
|
1062
1083
|
formattedResult = String(result);
|
|
1063
1084
|
}
|
|
@@ -1069,6 +1090,12 @@ var MCPServer = class {
|
|
|
1069
1090
|
}
|
|
1070
1091
|
]
|
|
1071
1092
|
};
|
|
1093
|
+
if (structuredContent) {
|
|
1094
|
+
response.structuredContent = structuredContent;
|
|
1095
|
+
if (this.logger) {
|
|
1096
|
+
this.logger.debug(`[MCPServer] Setting structuredContent: ${JSON.stringify(structuredContent).slice(0, 100)}...`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1072
1099
|
if (tool._meta && Object.keys(tool._meta).length > 0) {
|
|
1073
1100
|
response._meta = tool._meta;
|
|
1074
1101
|
}
|
|
@@ -1336,8 +1363,15 @@ var MCPServer = class {
|
|
|
1336
1363
|
}
|
|
1337
1364
|
/**
|
|
1338
1365
|
* Watch UI manifest for changes and reload resources dynamically
|
|
1366
|
+
*
|
|
1367
|
+
* CRITICAL: Only for stateful mode. In stateless mode, each request
|
|
1368
|
+
* creates a fresh server that reads the manifest directly, making
|
|
1369
|
+
* watchers both unnecessary and a memory leak source.
|
|
1339
1370
|
*/
|
|
1340
1371
|
watchUIManifest() {
|
|
1372
|
+
if (this.options.stateless) {
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1341
1375
|
try {
|
|
1342
1376
|
const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
|
|
1343
1377
|
if (!fs.existsSync(manifestPath)) {
|
|
@@ -1521,6 +1555,25 @@ var MCPServer = class {
|
|
|
1521
1555
|
return this.server;
|
|
1522
1556
|
}
|
|
1523
1557
|
/**
|
|
1558
|
+
* Clean up all registered services, watchers, and resources
|
|
1559
|
+
* CRITICAL for stateless mode to prevent memory leaks
|
|
1560
|
+
*/
|
|
1561
|
+
close() {
|
|
1562
|
+
if (this.manifestWatcher) {
|
|
1563
|
+
try {
|
|
1564
|
+
this.manifestWatcher.close();
|
|
1565
|
+
} catch (e) {
|
|
1566
|
+
}
|
|
1567
|
+
this.manifestWatcher = null;
|
|
1568
|
+
}
|
|
1569
|
+
this.tools.clear();
|
|
1570
|
+
this.prompts.clear();
|
|
1571
|
+
this.resources.clear();
|
|
1572
|
+
if (this.server && typeof this.server.close === "function") {
|
|
1573
|
+
this.server.close();
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1524
1577
|
* Cleanup resources (call on server shutdown)
|
|
1525
1578
|
*/
|
|
1526
1579
|
async cleanup() {
|