@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 CHANGED
@@ -1,15 +1,42 @@
1
- # @leanmcp/core
2
-
3
- Core library for building Model Context Protocol (MCP) servers with TypeScript decorators and declarative schema definition.
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-safe decorators** - `@Tool`, `@Prompt`, `@Resource` with full TypeScript support
8
- - **Schema generation** - Define JSON Schema declaratively using `@SchemaConstraint` decorators on class properties
9
- - **Streamable HTTP transport** - Production-ready HTTP server with session management
10
- - **Input validation** - Built-in AJV validation for all inputs
11
- - **Clean API** - Function names automatically become tool/prompt/resource names
12
- - **MCP compliant** - Built on official `@modelcontextprotocol/sdk`
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
- ### 1. Define Your Service with Class-Based Schema
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): Promise<AnalyzeSentimentOutput> {
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
- ### 2. Create and Start Server
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 an MCP tool (callable function). Use `inputClass` to specify the input schema class.
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 an MCP prompt template. Input schema is automatically inferred from parameter type.
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({ description: 'Get system configuration', mimeType: 'application/json' })
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 for automatic schema generation.
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; // Enable verbose debug logs (default: false)
272
- autoDiscover?: boolean; // Enable auto-discovery (default: true)
273
- mcpDir?: string; // Custom mcp directory path (optional)
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.registerService(instance: any): void;
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
- **Options:**
313
+ ---
284
314
 
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`)
315
+ ## Auto-Discovery
289
316
 
290
- #### Zero-Config Auto-Discovery
317
+ Services are automatically discovered from the `./mcp` directory:
291
318
 
292
- Services are automatically discovered and registered from the `./mcp` directory when the server is created:
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
- **Basic Usage (Simplified API):**
295
- ```typescript
296
- import { createHTTPServer } from "@leanmcp/core";
325
+ ### Shared Dependencies
297
326
 
298
- await createHTTPServer({
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 || 'us-east-1',
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: any) {
353
+ async sendMessage(args: { channel: string; message: string }) {
355
354
  // Implementation
356
355
  }
357
356
  }
358
357
  ```
359
358
 
360
- Your main file stays clean:
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
- When using `createHTTPServer`, the following endpoints are available:
461
-
462
- - `POST /mcp` - MCP protocol endpoint (accepts JSON-RPC 2.0 messages)
463
- - `GET /health` - Health check endpoint
464
- - `GET /` - Welcome message
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
- All tools automatically handle errors and return them in MCP format:
371
+ Errors are automatically caught and returned in MCP format:
476
372
 
477
373
  ```typescript
478
- class DivideInput {
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
- Errors are returned as:
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
- Full TypeScript support with type inference:
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
- class MyOutput {
517
- result!: string;
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
- **Key Points:**
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
- ## License
542
-
543
- MIT
420
+ - [Full Documentation](https://docs.leanmcp.com/sdk/core)
544
421
 
545
422
  ## Related Packages
546
423
 
547
- - [@leanmcp/cli](../cli) - CLI tool for creating new projects
548
- - [@leanmcp/auth](../auth) - Authentication decorators and providers
549
- - [@leanmcp/utils](../utils) - Utility functions
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
- - [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/core",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Core library implementing decorators, reflection, and MCP runtime server",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",