@leanmcp/core 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -10
- package/dist/index.d.mts +62 -7
- package/dist/index.d.ts +62 -7
- package/dist/index.js +223 -20
- package/dist/index.mjs +223 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,19 +75,68 @@ export class SentimentService {
|
|
|
75
75
|
|
|
76
76
|
### 2. Create and Start Server
|
|
77
77
|
|
|
78
|
+
#### Option A: Zero-Config Auto-Discovery (Recommended)
|
|
79
|
+
|
|
80
|
+
Automatically discover and register all services from the `./mcp` directory:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
84
|
+
|
|
85
|
+
// Create MCP server with auto-discovery
|
|
86
|
+
const serverFactory = async () => {
|
|
87
|
+
const server = new MCPServer({
|
|
88
|
+
name: "my-mcp-server",
|
|
89
|
+
version: "1.0.0",
|
|
90
|
+
logging: true // Enable HTTP server logs
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Services are automatically discovered and registered from ./mcp directory
|
|
94
|
+
return server.getServer();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Start HTTP server
|
|
98
|
+
await createHTTPServer(serverFactory, {
|
|
99
|
+
port: 3000,
|
|
100
|
+
cors: true,
|
|
101
|
+
logging: true // Log HTTP requests
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
console.log('\nMCP Server running');
|
|
105
|
+
console.log('HTTP endpoint: http://localhost:3000/mcp');
|
|
106
|
+
console.log('Health check: http://localhost:3000/health');
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Directory Structure:**
|
|
110
|
+
```
|
|
111
|
+
your-project/
|
|
112
|
+
├── main.ts
|
|
113
|
+
└── mcp/
|
|
114
|
+
├── sentiment/
|
|
115
|
+
│ └── index.ts # export class SentimentService
|
|
116
|
+
├── weather/
|
|
117
|
+
│ └── index.ts # export class WeatherService
|
|
118
|
+
└── database/
|
|
119
|
+
└── index.ts # export class DatabaseService
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Option B: Manual Registration
|
|
123
|
+
|
|
124
|
+
Manually import and register each service:
|
|
125
|
+
|
|
78
126
|
```typescript
|
|
79
127
|
import { createHTTPServer, MCPServer } from "@leanmcp/core";
|
|
80
128
|
import { SentimentService } from "./services/sentiment";
|
|
81
129
|
|
|
82
130
|
// Create MCP server
|
|
83
|
-
const serverFactory = () => {
|
|
131
|
+
const serverFactory = async () => {
|
|
84
132
|
const server = new MCPServer({
|
|
85
133
|
name: "my-mcp-server",
|
|
86
134
|
version: "1.0.0",
|
|
87
|
-
logging: true
|
|
135
|
+
logging: true,
|
|
136
|
+
autoDiscover: false // Disable auto-discovery for manual registration
|
|
88
137
|
});
|
|
89
138
|
|
|
90
|
-
// Register services
|
|
139
|
+
// Register services manually
|
|
91
140
|
server.registerService(new SentimentService());
|
|
92
141
|
|
|
93
142
|
return server.getServer();
|
|
@@ -97,7 +146,7 @@ const serverFactory = () => {
|
|
|
97
146
|
await createHTTPServer(serverFactory, {
|
|
98
147
|
port: 3000,
|
|
99
148
|
cors: true,
|
|
100
|
-
logging: true
|
|
149
|
+
logging: true // Log HTTP requests
|
|
101
150
|
});
|
|
102
151
|
```
|
|
103
152
|
|
|
@@ -220,13 +269,137 @@ Main server class for registering services.
|
|
|
220
269
|
|
|
221
270
|
```typescript
|
|
222
271
|
const server = new MCPServer({
|
|
223
|
-
name: string;
|
|
224
|
-
version: string;
|
|
225
|
-
logging?: boolean;
|
|
272
|
+
name: string; // Server name
|
|
273
|
+
version: string; // Server version
|
|
274
|
+
logging?: boolean; // Enable logging (default: false)
|
|
275
|
+
debug?: boolean; // Enable verbose debug logs (default: false)
|
|
276
|
+
autoDiscover?: boolean; // Enable auto-discovery (default: true)
|
|
277
|
+
mcpDir?: string; // Custom mcp directory path (optional)
|
|
226
278
|
});
|
|
227
279
|
|
|
280
|
+
// Manual registration
|
|
228
281
|
server.registerService(instance: any): void;
|
|
229
|
-
|
|
282
|
+
|
|
283
|
+
// Get underlying MCP SDK server
|
|
284
|
+
server.getServer(): Server;
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Options:**
|
|
288
|
+
|
|
289
|
+
- **`logging`**: Enable basic logging for server operations
|
|
290
|
+
- **`debug`**: Enable verbose debug logs showing detailed service registration (requires `logging: true`)
|
|
291
|
+
- **`autoDiscover`**: Automatically discover and register services from `./mcp` directory (default: `true`)
|
|
292
|
+
- **`mcpDir`**: Custom path to the mcp directory (default: auto-detected `./mcp`)
|
|
293
|
+
|
|
294
|
+
#### Zero-Config Auto-Discovery
|
|
295
|
+
|
|
296
|
+
Services are automatically discovered and registered from the `./mcp` directory when the server is created:
|
|
297
|
+
|
|
298
|
+
**Basic Usage:**
|
|
299
|
+
```typescript
|
|
300
|
+
const serverFactory = async () => {
|
|
301
|
+
const server = new MCPServer({
|
|
302
|
+
name: "my-server",
|
|
303
|
+
version: "1.0.0",
|
|
304
|
+
logging: true // Enable logging
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Services are automatically discovered and registered
|
|
308
|
+
return server.getServer();
|
|
309
|
+
};
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**With Debug Logging:**
|
|
313
|
+
```typescript
|
|
314
|
+
const serverFactory = async () => {
|
|
315
|
+
const server = new MCPServer({
|
|
316
|
+
name: "my-server",
|
|
317
|
+
version: "1.0.0",
|
|
318
|
+
logging: true,
|
|
319
|
+
debug: true // Show detailed service registration logs
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return server.getServer();
|
|
323
|
+
};
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**With Shared Dependencies:**
|
|
327
|
+
|
|
328
|
+
For services that need shared dependencies, create a `config.ts` (example) file in your `mcp` directory:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// mcp/config.ts
|
|
332
|
+
import { AuthProvider } from "@leanmcp/auth";
|
|
333
|
+
|
|
334
|
+
if (!process.env.COGNITO_USER_POOL_ID || !process.env.COGNITO_CLIENT_ID) {
|
|
335
|
+
throw new Error('Missing required Cognito configuration');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export const authProvider = new AuthProvider('cognito', {
|
|
339
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
340
|
+
userPoolId: process.env.COGNITO_USER_POOL_ID,
|
|
341
|
+
clientId: process.env.COGNITO_CLIENT_ID,
|
|
342
|
+
clientSecret: process.env.COGNITO_CLIENT_SECRET
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
await authProvider.init();
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Then import in your services:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// mcp/slack/index.ts
|
|
352
|
+
import { Tool } from "@leanmcp/core";
|
|
353
|
+
import { Authenticated } from "@leanmcp/auth";
|
|
354
|
+
import { authProvider } from "../config.js";
|
|
355
|
+
|
|
356
|
+
@Authenticated(authProvider)
|
|
357
|
+
export class SlackService {
|
|
358
|
+
constructor() {
|
|
359
|
+
// No parameters needed - use environment or imported config
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
@Tool({ description: 'Send a message' })
|
|
363
|
+
async sendMessage(args: any) {
|
|
364
|
+
// Implementation
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Your main file stays clean:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
const serverFactory = async () => {
|
|
373
|
+
const server = new MCPServer({
|
|
374
|
+
name: "my-server",
|
|
375
|
+
version: "1.0.0",
|
|
376
|
+
logging: true
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Services are automatically discovered and registered
|
|
380
|
+
return server.getServer();
|
|
381
|
+
};
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**How It Works:**
|
|
385
|
+
- Automatically discovers and registers services from the `./mcp` directory during server initialization
|
|
386
|
+
- Recursively scans for `index.ts` or `index.js` files
|
|
387
|
+
- Dynamically imports each file and looks for exported classes
|
|
388
|
+
- Instantiates services with no-args constructors
|
|
389
|
+
- Registers all discovered services with their decorated methods
|
|
390
|
+
|
|
391
|
+
**Directory Structure:**
|
|
392
|
+
```
|
|
393
|
+
your-project/
|
|
394
|
+
├── main.ts
|
|
395
|
+
└── mcp/
|
|
396
|
+
├── config.ts # Optional: shared dependencies
|
|
397
|
+
├── slack/
|
|
398
|
+
│ └── index.ts # export class SlackService
|
|
399
|
+
├── database/
|
|
400
|
+
│ └── index.ts # export class DatabaseService
|
|
401
|
+
└── auth/
|
|
402
|
+
└── index.ts # export class AuthService
|
|
230
403
|
```
|
|
231
404
|
|
|
232
405
|
### createHTTPServer
|
|
@@ -235,11 +408,11 @@ Create and start an HTTP server with streamable transport.
|
|
|
235
408
|
|
|
236
409
|
```typescript
|
|
237
410
|
await createHTTPServer(
|
|
238
|
-
serverFactory: () => Server
|
|
411
|
+
serverFactory: () => Server | Promise<Server>,
|
|
239
412
|
options: {
|
|
240
413
|
port?: number; // Port number (default: 3000)
|
|
241
414
|
cors?: boolean; // Enable CORS (default: false)
|
|
242
|
-
logging?: boolean; // Enable logging (default:
|
|
415
|
+
logging?: boolean; // Enable HTTP request logging (default: false)
|
|
243
416
|
}
|
|
244
417
|
);
|
|
245
418
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -225,8 +225,9 @@ interface MCPServerFactory {
|
|
|
225
225
|
}
|
|
226
226
|
/**
|
|
227
227
|
* Create an HTTP server for MCP with Streamable HTTP transport
|
|
228
|
+
* Returns the HTTP server instance to keep the process alive
|
|
228
229
|
*/
|
|
229
|
-
declare function createHTTPServer(serverFactory: MCPServerFactory, options?: HTTPServerOptions): Promise<
|
|
230
|
+
declare function createHTTPServer(serverFactory: MCPServerFactory, options?: HTTPServerOptions): Promise<any>;
|
|
230
231
|
|
|
231
232
|
/**
|
|
232
233
|
* Input validation utilities for LeanMCP
|
|
@@ -315,6 +316,15 @@ interface MCPServerOptions {
|
|
|
315
316
|
cors?: boolean;
|
|
316
317
|
logging?: boolean;
|
|
317
318
|
}
|
|
319
|
+
interface MCPServerConstructorOptions {
|
|
320
|
+
name: string;
|
|
321
|
+
version: string;
|
|
322
|
+
logging?: boolean;
|
|
323
|
+
debug?: boolean;
|
|
324
|
+
autoDiscover?: boolean;
|
|
325
|
+
mcpDir?: string;
|
|
326
|
+
serviceFactories?: Record<string, () => any>;
|
|
327
|
+
}
|
|
318
328
|
interface RegisteredTool {
|
|
319
329
|
name: string;
|
|
320
330
|
description: string;
|
|
@@ -352,18 +362,63 @@ declare class MCPServer {
|
|
|
352
362
|
private resources;
|
|
353
363
|
private logging;
|
|
354
364
|
private logger;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
365
|
+
private options;
|
|
366
|
+
private initPromise;
|
|
367
|
+
private autoDiscovered;
|
|
368
|
+
constructor(options: MCPServerConstructorOptions);
|
|
369
|
+
/**
|
|
370
|
+
* Internal initialization - runs automatically in constructor
|
|
371
|
+
*/
|
|
372
|
+
private autoInit;
|
|
373
|
+
/**
|
|
374
|
+
* Wait for initialization to complete
|
|
375
|
+
* This is called internally by createHTTPServer
|
|
376
|
+
*/
|
|
377
|
+
waitForInit(): Promise<void>;
|
|
378
|
+
/**
|
|
379
|
+
* Automatically discover and register services from the mcp directory
|
|
380
|
+
* Called by init() unless autoDiscover is set to false
|
|
381
|
+
*/
|
|
382
|
+
private autoDiscoverServices;
|
|
383
|
+
/**
|
|
384
|
+
* Get the file path of the caller (the file that instantiated MCPServer)
|
|
385
|
+
*/
|
|
386
|
+
private getCallerFile;
|
|
360
387
|
private setupHandlers;
|
|
388
|
+
/**
|
|
389
|
+
* Auto-register all services from the mcp directory
|
|
390
|
+
* Scans the directory recursively and registers all exported classes
|
|
391
|
+
*
|
|
392
|
+
* @param mcpDir - Path to the mcp directory containing service files
|
|
393
|
+
* @param serviceFactories - Optional map of service class names to factory functions for dependency injection
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* // Auto-register services with no dependencies
|
|
397
|
+
* await server.autoRegisterServices('./mcp');
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* // Auto-register with dependency injection
|
|
401
|
+
* await server.autoRegisterServices('./mcp', {
|
|
402
|
+
* SlackService: () => new SlackService(process.env.SLACK_TOKEN),
|
|
403
|
+
* AuthService: () => new AuthService(authProvider)
|
|
404
|
+
* });
|
|
405
|
+
*/
|
|
406
|
+
autoRegisterServices(mcpDir: string, serviceFactories?: Record<string, () => any>): Promise<void>;
|
|
407
|
+
/**
|
|
408
|
+
* Recursively find all index.ts/index.js files in the mcp directory
|
|
409
|
+
*/
|
|
410
|
+
private findServiceFiles;
|
|
411
|
+
/**
|
|
412
|
+
* Load a service file and register all exported classes
|
|
413
|
+
*/
|
|
414
|
+
private loadAndRegisterService;
|
|
361
415
|
/**
|
|
362
416
|
* Register a service instance with decorated methods
|
|
363
417
|
*/
|
|
364
418
|
registerService(instance: any): void;
|
|
365
419
|
/**
|
|
366
420
|
* Get the underlying MCP SDK Server instance
|
|
421
|
+
* Attaches waitForInit method for HTTP server initialization
|
|
367
422
|
*/
|
|
368
423
|
getServer(): Server<{
|
|
369
424
|
method: string;
|
|
@@ -432,4 +487,4 @@ declare class MCPServerRuntime {
|
|
|
432
487
|
*/
|
|
433
488
|
declare function startMCPServer(options: MCPServerOptions): Promise<MCPServerRuntime>;
|
|
434
489
|
|
|
435
|
-
export { Auth, type AuthOptions, Deprecated, type HTTPServerOptions, LogLevel, Logger, type LoggerOptions, MCPServer, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, Render, Resource, type ResourceOptions, SchemaConstraint, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, createHTTPServer, defaultLogger, getDecoratedMethods, getMethodMetadata, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
|
|
490
|
+
export { Auth, type AuthOptions, Deprecated, type HTTPServerOptions, LogLevel, Logger, type LoggerOptions, MCPServer, type MCPServerConstructorOptions, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, Render, Resource, type ResourceOptions, SchemaConstraint, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, createHTTPServer, defaultLogger, getDecoratedMethods, getMethodMetadata, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -225,8 +225,9 @@ interface MCPServerFactory {
|
|
|
225
225
|
}
|
|
226
226
|
/**
|
|
227
227
|
* Create an HTTP server for MCP with Streamable HTTP transport
|
|
228
|
+
* Returns the HTTP server instance to keep the process alive
|
|
228
229
|
*/
|
|
229
|
-
declare function createHTTPServer(serverFactory: MCPServerFactory, options?: HTTPServerOptions): Promise<
|
|
230
|
+
declare function createHTTPServer(serverFactory: MCPServerFactory, options?: HTTPServerOptions): Promise<any>;
|
|
230
231
|
|
|
231
232
|
/**
|
|
232
233
|
* Input validation utilities for LeanMCP
|
|
@@ -315,6 +316,15 @@ interface MCPServerOptions {
|
|
|
315
316
|
cors?: boolean;
|
|
316
317
|
logging?: boolean;
|
|
317
318
|
}
|
|
319
|
+
interface MCPServerConstructorOptions {
|
|
320
|
+
name: string;
|
|
321
|
+
version: string;
|
|
322
|
+
logging?: boolean;
|
|
323
|
+
debug?: boolean;
|
|
324
|
+
autoDiscover?: boolean;
|
|
325
|
+
mcpDir?: string;
|
|
326
|
+
serviceFactories?: Record<string, () => any>;
|
|
327
|
+
}
|
|
318
328
|
interface RegisteredTool {
|
|
319
329
|
name: string;
|
|
320
330
|
description: string;
|
|
@@ -352,18 +362,63 @@ declare class MCPServer {
|
|
|
352
362
|
private resources;
|
|
353
363
|
private logging;
|
|
354
364
|
private logger;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
365
|
+
private options;
|
|
366
|
+
private initPromise;
|
|
367
|
+
private autoDiscovered;
|
|
368
|
+
constructor(options: MCPServerConstructorOptions);
|
|
369
|
+
/**
|
|
370
|
+
* Internal initialization - runs automatically in constructor
|
|
371
|
+
*/
|
|
372
|
+
private autoInit;
|
|
373
|
+
/**
|
|
374
|
+
* Wait for initialization to complete
|
|
375
|
+
* This is called internally by createHTTPServer
|
|
376
|
+
*/
|
|
377
|
+
waitForInit(): Promise<void>;
|
|
378
|
+
/**
|
|
379
|
+
* Automatically discover and register services from the mcp directory
|
|
380
|
+
* Called by init() unless autoDiscover is set to false
|
|
381
|
+
*/
|
|
382
|
+
private autoDiscoverServices;
|
|
383
|
+
/**
|
|
384
|
+
* Get the file path of the caller (the file that instantiated MCPServer)
|
|
385
|
+
*/
|
|
386
|
+
private getCallerFile;
|
|
360
387
|
private setupHandlers;
|
|
388
|
+
/**
|
|
389
|
+
* Auto-register all services from the mcp directory
|
|
390
|
+
* Scans the directory recursively and registers all exported classes
|
|
391
|
+
*
|
|
392
|
+
* @param mcpDir - Path to the mcp directory containing service files
|
|
393
|
+
* @param serviceFactories - Optional map of service class names to factory functions for dependency injection
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* // Auto-register services with no dependencies
|
|
397
|
+
* await server.autoRegisterServices('./mcp');
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* // Auto-register with dependency injection
|
|
401
|
+
* await server.autoRegisterServices('./mcp', {
|
|
402
|
+
* SlackService: () => new SlackService(process.env.SLACK_TOKEN),
|
|
403
|
+
* AuthService: () => new AuthService(authProvider)
|
|
404
|
+
* });
|
|
405
|
+
*/
|
|
406
|
+
autoRegisterServices(mcpDir: string, serviceFactories?: Record<string, () => any>): Promise<void>;
|
|
407
|
+
/**
|
|
408
|
+
* Recursively find all index.ts/index.js files in the mcp directory
|
|
409
|
+
*/
|
|
410
|
+
private findServiceFiles;
|
|
411
|
+
/**
|
|
412
|
+
* Load a service file and register all exported classes
|
|
413
|
+
*/
|
|
414
|
+
private loadAndRegisterService;
|
|
361
415
|
/**
|
|
362
416
|
* Register a service instance with decorated methods
|
|
363
417
|
*/
|
|
364
418
|
registerService(instance: any): void;
|
|
365
419
|
/**
|
|
366
420
|
* Get the underlying MCP SDK Server instance
|
|
421
|
+
* Attaches waitForInit method for HTTP server initialization
|
|
367
422
|
*/
|
|
368
423
|
getServer(): Server<{
|
|
369
424
|
method: string;
|
|
@@ -432,4 +487,4 @@ declare class MCPServerRuntime {
|
|
|
432
487
|
*/
|
|
433
488
|
declare function startMCPServer(options: MCPServerOptions): Promise<MCPServerRuntime>;
|
|
434
489
|
|
|
435
|
-
export { Auth, type AuthOptions, Deprecated, type HTTPServerOptions, LogLevel, Logger, type LoggerOptions, MCPServer, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, Render, Resource, type ResourceOptions, SchemaConstraint, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, createHTTPServer, defaultLogger, getDecoratedMethods, getMethodMetadata, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
|
|
490
|
+
export { Auth, type AuthOptions, Deprecated, type HTTPServerOptions, LogLevel, Logger, type LoggerOptions, MCPServer, type MCPServerConstructorOptions, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, Render, Resource, type ResourceOptions, SchemaConstraint, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, createHTTPServer, defaultLogger, getDecoratedMethods, getMethodMetadata, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
|
package/dist/index.js
CHANGED
|
@@ -466,6 +466,7 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
466
466
|
const port = options.port || 3001;
|
|
467
467
|
validatePort(port);
|
|
468
468
|
const transports = {};
|
|
469
|
+
let mcpServer = null;
|
|
469
470
|
const logger = options.logger || new Logger({
|
|
470
471
|
level: options.logging ? LogLevel.INFO : LogLevel.NONE,
|
|
471
472
|
prefix: "HTTP"
|
|
@@ -507,6 +508,18 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
507
508
|
const handleMCPRequest = /* @__PURE__ */ __name(async (req, res) => {
|
|
508
509
|
const sessionId = req.headers["mcp-session-id"];
|
|
509
510
|
let transport;
|
|
511
|
+
const method = req.body?.method || "unknown";
|
|
512
|
+
const params = req.body?.params;
|
|
513
|
+
let logMessage = `${req.method} /mcp - ${method}`;
|
|
514
|
+
if (params?.name) {
|
|
515
|
+
logMessage += ` [${params.name}]`;
|
|
516
|
+
} else if (params?.uri) {
|
|
517
|
+
logMessage += ` [${params.uri}]`;
|
|
518
|
+
}
|
|
519
|
+
if (sessionId) {
|
|
520
|
+
logMessage += ` (session: ${sessionId.substring(0, 8)}...)`;
|
|
521
|
+
}
|
|
522
|
+
logger.info(logMessage);
|
|
510
523
|
try {
|
|
511
524
|
if (sessionId && transports[sessionId]) {
|
|
512
525
|
transport = transports[sessionId];
|
|
@@ -526,8 +539,10 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
526
539
|
logger.debug(`Session cleaned up: ${transport.sessionId}`);
|
|
527
540
|
}
|
|
528
541
|
};
|
|
529
|
-
|
|
530
|
-
|
|
542
|
+
if (!mcpServer) {
|
|
543
|
+
throw new Error("MCP server not initialized");
|
|
544
|
+
}
|
|
545
|
+
await mcpServer.connect(transport);
|
|
531
546
|
} else {
|
|
532
547
|
res.status(400).json({
|
|
533
548
|
jsonrpc: "2.0",
|
|
@@ -556,18 +571,39 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
556
571
|
}, "handleMCPRequest");
|
|
557
572
|
app.post("/mcp", handleMCPRequest);
|
|
558
573
|
app.delete("/mcp", handleMCPRequest);
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
574
|
+
return new Promise(async (resolve, reject) => {
|
|
575
|
+
try {
|
|
576
|
+
mcpServer = await serverFactory();
|
|
577
|
+
if (mcpServer && typeof mcpServer.waitForInit === "function") {
|
|
578
|
+
await mcpServer.waitForInit();
|
|
579
|
+
}
|
|
580
|
+
const listener = app.listen(port, () => {
|
|
581
|
+
logger.info(`Server running on http://localhost:${port}`);
|
|
582
|
+
logger.info(`MCP endpoint: http://localhost:${port}/mcp`);
|
|
583
|
+
logger.info(`Health check: http://localhost:${port}/health`);
|
|
584
|
+
resolve(listener);
|
|
585
|
+
});
|
|
586
|
+
listener.on("error", (error) => {
|
|
587
|
+
logger.error(`Server error: ${error.message}`);
|
|
588
|
+
reject(error);
|
|
589
|
+
});
|
|
590
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
591
|
+
logger.info("\nShutting down server...");
|
|
592
|
+
Object.values(transports).forEach((t) => t.close?.());
|
|
593
|
+
listener.close(() => {
|
|
594
|
+
logger.info("Server closed");
|
|
595
|
+
process.exit(0);
|
|
596
|
+
});
|
|
597
|
+
setTimeout(() => {
|
|
598
|
+
logger.warn("Forcing shutdown...");
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}, 5e3);
|
|
601
|
+
}, "cleanup");
|
|
602
|
+
process.on("SIGINT", cleanup);
|
|
603
|
+
process.on("SIGTERM", cleanup);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
reject(error);
|
|
606
|
+
}
|
|
571
607
|
});
|
|
572
608
|
}
|
|
573
609
|
__name(createHTTPServer, "createHTTPServer");
|
|
@@ -584,10 +620,18 @@ var MCPServer = class {
|
|
|
584
620
|
resources = /* @__PURE__ */ new Map();
|
|
585
621
|
logging;
|
|
586
622
|
logger;
|
|
623
|
+
options;
|
|
624
|
+
initPromise;
|
|
625
|
+
autoDiscovered = false;
|
|
587
626
|
constructor(options) {
|
|
627
|
+
this.options = options;
|
|
588
628
|
this.logging = options.logging || false;
|
|
629
|
+
let logLevel = LogLevel.NONE;
|
|
630
|
+
if (options.logging) {
|
|
631
|
+
logLevel = options.debug ? LogLevel.DEBUG : LogLevel.INFO;
|
|
632
|
+
}
|
|
589
633
|
this.logger = new Logger({
|
|
590
|
-
level:
|
|
634
|
+
level: logLevel,
|
|
591
635
|
prefix: "MCPServer"
|
|
592
636
|
});
|
|
593
637
|
this.server = new import_server.Server({
|
|
@@ -596,11 +640,84 @@ var MCPServer = class {
|
|
|
596
640
|
}, {
|
|
597
641
|
capabilities: {
|
|
598
642
|
tools: {},
|
|
599
|
-
|
|
600
|
-
|
|
643
|
+
prompts: {},
|
|
644
|
+
resources: {}
|
|
601
645
|
}
|
|
602
646
|
});
|
|
603
647
|
this.setupHandlers();
|
|
648
|
+
this.initPromise = this.autoInit();
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Internal initialization - runs automatically in constructor
|
|
652
|
+
*/
|
|
653
|
+
async autoInit() {
|
|
654
|
+
const options = this.options;
|
|
655
|
+
if (options.autoDiscover !== false) {
|
|
656
|
+
await this.autoDiscoverServices(options.mcpDir, options.serviceFactories);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Wait for initialization to complete
|
|
661
|
+
* This is called internally by createHTTPServer
|
|
662
|
+
*/
|
|
663
|
+
async waitForInit() {
|
|
664
|
+
await this.initPromise;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Automatically discover and register services from the mcp directory
|
|
668
|
+
* Called by init() unless autoDiscover is set to false
|
|
669
|
+
*/
|
|
670
|
+
async autoDiscoverServices(customMcpDir, serviceFactories) {
|
|
671
|
+
if (this.autoDiscovered) return;
|
|
672
|
+
this.autoDiscovered = true;
|
|
673
|
+
try {
|
|
674
|
+
let mcpDir;
|
|
675
|
+
if (customMcpDir) {
|
|
676
|
+
mcpDir = customMcpDir;
|
|
677
|
+
} else {
|
|
678
|
+
const callerFile = this.getCallerFile();
|
|
679
|
+
if (callerFile) {
|
|
680
|
+
const callerDir = import_path.default.dirname(callerFile);
|
|
681
|
+
mcpDir = import_path.default.join(callerDir, "mcp");
|
|
682
|
+
} else {
|
|
683
|
+
mcpDir = import_path.default.join(process.cwd(), "mcp");
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (import_fs.default.existsSync(mcpDir)) {
|
|
687
|
+
this.logger.debug(`Auto-discovering services from: ${mcpDir}`);
|
|
688
|
+
await this.autoRegisterServices(mcpDir, serviceFactories);
|
|
689
|
+
} else {
|
|
690
|
+
this.logger.debug(`MCP directory not found at ${mcpDir}, skipping auto-discovery`);
|
|
691
|
+
}
|
|
692
|
+
} catch (error) {
|
|
693
|
+
this.logger.warn(`Auto-discovery failed: ${error.message}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Get the file path of the caller (the file that instantiated MCPServer)
|
|
698
|
+
*/
|
|
699
|
+
getCallerFile() {
|
|
700
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
701
|
+
try {
|
|
702
|
+
const err = new Error();
|
|
703
|
+
Error.prepareStackTrace = (_, stack2) => stack2;
|
|
704
|
+
const stack = err.stack;
|
|
705
|
+
for (let i = 0; i < stack.length; i++) {
|
|
706
|
+
let fileName = stack[i].getFileName();
|
|
707
|
+
if (fileName && !fileName.includes("@leanmcp") && !fileName.includes("leanmcp-sdk\\packages\\core") && !fileName.includes("leanmcp-sdk/packages/core") && (fileName.endsWith(".ts") || fileName.endsWith(".js") || fileName.endsWith(".mjs"))) {
|
|
708
|
+
if (fileName.startsWith("file://")) {
|
|
709
|
+
fileName = fileName.replace("file:///", "").replace("file://", "");
|
|
710
|
+
if (process.platform === "win32" && fileName.startsWith("/")) {
|
|
711
|
+
fileName = fileName.substring(1);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return fileName;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
} finally {
|
|
719
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
720
|
+
}
|
|
604
721
|
}
|
|
605
722
|
setupHandlers() {
|
|
606
723
|
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
@@ -745,6 +862,90 @@ var MCPServer = class {
|
|
|
745
862
|
});
|
|
746
863
|
}
|
|
747
864
|
/**
|
|
865
|
+
* Auto-register all services from the mcp directory
|
|
866
|
+
* Scans the directory recursively and registers all exported classes
|
|
867
|
+
*
|
|
868
|
+
* @param mcpDir - Path to the mcp directory containing service files
|
|
869
|
+
* @param serviceFactories - Optional map of service class names to factory functions for dependency injection
|
|
870
|
+
*
|
|
871
|
+
* @example
|
|
872
|
+
* // Auto-register services with no dependencies
|
|
873
|
+
* await server.autoRegisterServices('./mcp');
|
|
874
|
+
*
|
|
875
|
+
* @example
|
|
876
|
+
* // Auto-register with dependency injection
|
|
877
|
+
* await server.autoRegisterServices('./mcp', {
|
|
878
|
+
* SlackService: () => new SlackService(process.env.SLACK_TOKEN),
|
|
879
|
+
* AuthService: () => new AuthService(authProvider)
|
|
880
|
+
* });
|
|
881
|
+
*/
|
|
882
|
+
async autoRegisterServices(mcpDir, serviceFactories) {
|
|
883
|
+
this.logger.debug(`Auto-registering services from: ${mcpDir}`);
|
|
884
|
+
if (!import_fs.default.existsSync(mcpDir)) {
|
|
885
|
+
this.logger.warn(`MCP directory not found: ${mcpDir}`);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const serviceFiles = this.findServiceFiles(mcpDir);
|
|
889
|
+
this.logger.debug(`Found ${serviceFiles.length} service file(s)`);
|
|
890
|
+
for (const filePath of serviceFiles) {
|
|
891
|
+
try {
|
|
892
|
+
await this.loadAndRegisterService(filePath, serviceFactories);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
this.logger.error(`Failed to load service from ${filePath}: ${error.message}`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Recursively find all index.ts/index.js files in the mcp directory
|
|
900
|
+
*/
|
|
901
|
+
findServiceFiles(dir) {
|
|
902
|
+
const files = [];
|
|
903
|
+
const entries = import_fs.default.readdirSync(dir, {
|
|
904
|
+
withFileTypes: true
|
|
905
|
+
});
|
|
906
|
+
for (const entry of entries) {
|
|
907
|
+
const fullPath = import_path.default.join(dir, entry.name);
|
|
908
|
+
if (entry.isDirectory()) {
|
|
909
|
+
files.push(...this.findServiceFiles(fullPath));
|
|
910
|
+
} else if (entry.isFile()) {
|
|
911
|
+
if (entry.name === "index.ts" || entry.name === "index.js") {
|
|
912
|
+
files.push(fullPath);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return files;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Load a service file and register all exported classes
|
|
920
|
+
*/
|
|
921
|
+
async loadAndRegisterService(filePath, serviceFactories) {
|
|
922
|
+
this.logger.debug(`Loading service from: ${filePath}`);
|
|
923
|
+
const fileUrl = (0, import_url.pathToFileURL)(filePath).href;
|
|
924
|
+
const module2 = await import(fileUrl);
|
|
925
|
+
let registeredCount = 0;
|
|
926
|
+
for (const [exportName, exportValue] of Object.entries(module2)) {
|
|
927
|
+
if (typeof exportValue === "function" && exportValue.prototype) {
|
|
928
|
+
try {
|
|
929
|
+
let instance;
|
|
930
|
+
if (serviceFactories && serviceFactories[exportName]) {
|
|
931
|
+
instance = serviceFactories[exportName]();
|
|
932
|
+
this.logger.info(`Using factory for service: ${exportName}`);
|
|
933
|
+
} else {
|
|
934
|
+
instance = new exportValue();
|
|
935
|
+
}
|
|
936
|
+
this.registerService(instance);
|
|
937
|
+
registeredCount++;
|
|
938
|
+
this.logger.debug(`Registered service: ${exportName} from ${import_path.default.basename(filePath)}`);
|
|
939
|
+
} catch (error) {
|
|
940
|
+
this.logger.warn(`Skipped ${exportName}: ${error.message}`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (registeredCount === 0) {
|
|
945
|
+
this.logger.warn(`No services registered from ${filePath}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
748
949
|
* Register a service instance with decorated methods
|
|
749
950
|
*/
|
|
750
951
|
registerService(instance) {
|
|
@@ -766,7 +967,7 @@ var MCPServer = class {
|
|
|
766
967
|
propertyKey
|
|
767
968
|
});
|
|
768
969
|
if (this.logging) {
|
|
769
|
-
this.logger.
|
|
970
|
+
this.logger.debug(`Registered tool: ${methodMeta.toolName}${inputClass ? " (class-based schema)" : ""}`);
|
|
770
971
|
}
|
|
771
972
|
}
|
|
772
973
|
const promptMethods = getDecoratedMethods(cls, "prompt:name");
|
|
@@ -791,7 +992,7 @@ var MCPServer = class {
|
|
|
791
992
|
propertyKey
|
|
792
993
|
});
|
|
793
994
|
if (this.logging) {
|
|
794
|
-
this.logger.
|
|
995
|
+
this.logger.debug(`Registered prompt: ${methodMeta.promptName}`);
|
|
795
996
|
}
|
|
796
997
|
}
|
|
797
998
|
const resourceMethods = getDecoratedMethods(cls, "resource:uri");
|
|
@@ -814,14 +1015,16 @@ var MCPServer = class {
|
|
|
814
1015
|
propertyKey
|
|
815
1016
|
});
|
|
816
1017
|
if (this.logging) {
|
|
817
|
-
this.logger.
|
|
1018
|
+
this.logger.debug(`Registered resource: ${methodMeta.resourceUri}`);
|
|
818
1019
|
}
|
|
819
1020
|
}
|
|
820
1021
|
}
|
|
821
1022
|
/**
|
|
822
1023
|
* Get the underlying MCP SDK Server instance
|
|
1024
|
+
* Attaches waitForInit method for HTTP server initialization
|
|
823
1025
|
*/
|
|
824
1026
|
getServer() {
|
|
1027
|
+
this.server.waitForInit = () => this.waitForInit();
|
|
825
1028
|
return this.server;
|
|
826
1029
|
}
|
|
827
1030
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -409,6 +409,7 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
409
409
|
const port = options.port || 3001;
|
|
410
410
|
validatePort(port);
|
|
411
411
|
const transports = {};
|
|
412
|
+
let mcpServer = null;
|
|
412
413
|
const logger = options.logger || new Logger({
|
|
413
414
|
level: options.logging ? LogLevel.INFO : LogLevel.NONE,
|
|
414
415
|
prefix: "HTTP"
|
|
@@ -450,6 +451,18 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
450
451
|
const handleMCPRequest = /* @__PURE__ */ __name(async (req, res) => {
|
|
451
452
|
const sessionId = req.headers["mcp-session-id"];
|
|
452
453
|
let transport;
|
|
454
|
+
const method = req.body?.method || "unknown";
|
|
455
|
+
const params = req.body?.params;
|
|
456
|
+
let logMessage = `${req.method} /mcp - ${method}`;
|
|
457
|
+
if (params?.name) {
|
|
458
|
+
logMessage += ` [${params.name}]`;
|
|
459
|
+
} else if (params?.uri) {
|
|
460
|
+
logMessage += ` [${params.uri}]`;
|
|
461
|
+
}
|
|
462
|
+
if (sessionId) {
|
|
463
|
+
logMessage += ` (session: ${sessionId.substring(0, 8)}...)`;
|
|
464
|
+
}
|
|
465
|
+
logger.info(logMessage);
|
|
453
466
|
try {
|
|
454
467
|
if (sessionId && transports[sessionId]) {
|
|
455
468
|
transport = transports[sessionId];
|
|
@@ -469,8 +482,10 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
469
482
|
logger.debug(`Session cleaned up: ${transport.sessionId}`);
|
|
470
483
|
}
|
|
471
484
|
};
|
|
472
|
-
|
|
473
|
-
|
|
485
|
+
if (!mcpServer) {
|
|
486
|
+
throw new Error("MCP server not initialized");
|
|
487
|
+
}
|
|
488
|
+
await mcpServer.connect(transport);
|
|
474
489
|
} else {
|
|
475
490
|
res.status(400).json({
|
|
476
491
|
jsonrpc: "2.0",
|
|
@@ -499,18 +514,39 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
499
514
|
}, "handleMCPRequest");
|
|
500
515
|
app.post("/mcp", handleMCPRequest);
|
|
501
516
|
app.delete("/mcp", handleMCPRequest);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
517
|
+
return new Promise(async (resolve, reject) => {
|
|
518
|
+
try {
|
|
519
|
+
mcpServer = await serverFactory();
|
|
520
|
+
if (mcpServer && typeof mcpServer.waitForInit === "function") {
|
|
521
|
+
await mcpServer.waitForInit();
|
|
522
|
+
}
|
|
523
|
+
const listener = app.listen(port, () => {
|
|
524
|
+
logger.info(`Server running on http://localhost:${port}`);
|
|
525
|
+
logger.info(`MCP endpoint: http://localhost:${port}/mcp`);
|
|
526
|
+
logger.info(`Health check: http://localhost:${port}/health`);
|
|
527
|
+
resolve(listener);
|
|
528
|
+
});
|
|
529
|
+
listener.on("error", (error) => {
|
|
530
|
+
logger.error(`Server error: ${error.message}`);
|
|
531
|
+
reject(error);
|
|
532
|
+
});
|
|
533
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
534
|
+
logger.info("\nShutting down server...");
|
|
535
|
+
Object.values(transports).forEach((t) => t.close?.());
|
|
536
|
+
listener.close(() => {
|
|
537
|
+
logger.info("Server closed");
|
|
538
|
+
process.exit(0);
|
|
539
|
+
});
|
|
540
|
+
setTimeout(() => {
|
|
541
|
+
logger.warn("Forcing shutdown...");
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}, 5e3);
|
|
544
|
+
}, "cleanup");
|
|
545
|
+
process.on("SIGINT", cleanup);
|
|
546
|
+
process.on("SIGTERM", cleanup);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
reject(error);
|
|
549
|
+
}
|
|
514
550
|
});
|
|
515
551
|
}
|
|
516
552
|
__name(createHTTPServer, "createHTTPServer");
|
|
@@ -527,10 +563,18 @@ var MCPServer = class {
|
|
|
527
563
|
resources = /* @__PURE__ */ new Map();
|
|
528
564
|
logging;
|
|
529
565
|
logger;
|
|
566
|
+
options;
|
|
567
|
+
initPromise;
|
|
568
|
+
autoDiscovered = false;
|
|
530
569
|
constructor(options) {
|
|
570
|
+
this.options = options;
|
|
531
571
|
this.logging = options.logging || false;
|
|
572
|
+
let logLevel = LogLevel.NONE;
|
|
573
|
+
if (options.logging) {
|
|
574
|
+
logLevel = options.debug ? LogLevel.DEBUG : LogLevel.INFO;
|
|
575
|
+
}
|
|
532
576
|
this.logger = new Logger({
|
|
533
|
-
level:
|
|
577
|
+
level: logLevel,
|
|
534
578
|
prefix: "MCPServer"
|
|
535
579
|
});
|
|
536
580
|
this.server = new Server({
|
|
@@ -539,11 +583,84 @@ var MCPServer = class {
|
|
|
539
583
|
}, {
|
|
540
584
|
capabilities: {
|
|
541
585
|
tools: {},
|
|
542
|
-
|
|
543
|
-
|
|
586
|
+
prompts: {},
|
|
587
|
+
resources: {}
|
|
544
588
|
}
|
|
545
589
|
});
|
|
546
590
|
this.setupHandlers();
|
|
591
|
+
this.initPromise = this.autoInit();
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Internal initialization - runs automatically in constructor
|
|
595
|
+
*/
|
|
596
|
+
async autoInit() {
|
|
597
|
+
const options = this.options;
|
|
598
|
+
if (options.autoDiscover !== false) {
|
|
599
|
+
await this.autoDiscoverServices(options.mcpDir, options.serviceFactories);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Wait for initialization to complete
|
|
604
|
+
* This is called internally by createHTTPServer
|
|
605
|
+
*/
|
|
606
|
+
async waitForInit() {
|
|
607
|
+
await this.initPromise;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Automatically discover and register services from the mcp directory
|
|
611
|
+
* Called by init() unless autoDiscover is set to false
|
|
612
|
+
*/
|
|
613
|
+
async autoDiscoverServices(customMcpDir, serviceFactories) {
|
|
614
|
+
if (this.autoDiscovered) return;
|
|
615
|
+
this.autoDiscovered = true;
|
|
616
|
+
try {
|
|
617
|
+
let mcpDir;
|
|
618
|
+
if (customMcpDir) {
|
|
619
|
+
mcpDir = customMcpDir;
|
|
620
|
+
} else {
|
|
621
|
+
const callerFile = this.getCallerFile();
|
|
622
|
+
if (callerFile) {
|
|
623
|
+
const callerDir = path.dirname(callerFile);
|
|
624
|
+
mcpDir = path.join(callerDir, "mcp");
|
|
625
|
+
} else {
|
|
626
|
+
mcpDir = path.join(process.cwd(), "mcp");
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (fs.existsSync(mcpDir)) {
|
|
630
|
+
this.logger.debug(`Auto-discovering services from: ${mcpDir}`);
|
|
631
|
+
await this.autoRegisterServices(mcpDir, serviceFactories);
|
|
632
|
+
} else {
|
|
633
|
+
this.logger.debug(`MCP directory not found at ${mcpDir}, skipping auto-discovery`);
|
|
634
|
+
}
|
|
635
|
+
} catch (error) {
|
|
636
|
+
this.logger.warn(`Auto-discovery failed: ${error.message}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Get the file path of the caller (the file that instantiated MCPServer)
|
|
641
|
+
*/
|
|
642
|
+
getCallerFile() {
|
|
643
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
644
|
+
try {
|
|
645
|
+
const err = new Error();
|
|
646
|
+
Error.prepareStackTrace = (_, stack2) => stack2;
|
|
647
|
+
const stack = err.stack;
|
|
648
|
+
for (let i = 0; i < stack.length; i++) {
|
|
649
|
+
let fileName = stack[i].getFileName();
|
|
650
|
+
if (fileName && !fileName.includes("@leanmcp") && !fileName.includes("leanmcp-sdk\\packages\\core") && !fileName.includes("leanmcp-sdk/packages/core") && (fileName.endsWith(".ts") || fileName.endsWith(".js") || fileName.endsWith(".mjs"))) {
|
|
651
|
+
if (fileName.startsWith("file://")) {
|
|
652
|
+
fileName = fileName.replace("file:///", "").replace("file://", "");
|
|
653
|
+
if (process.platform === "win32" && fileName.startsWith("/")) {
|
|
654
|
+
fileName = fileName.substring(1);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return fileName;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return null;
|
|
661
|
+
} finally {
|
|
662
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
663
|
+
}
|
|
547
664
|
}
|
|
548
665
|
setupHandlers() {
|
|
549
666
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -688,6 +805,90 @@ var MCPServer = class {
|
|
|
688
805
|
});
|
|
689
806
|
}
|
|
690
807
|
/**
|
|
808
|
+
* Auto-register all services from the mcp directory
|
|
809
|
+
* Scans the directory recursively and registers all exported classes
|
|
810
|
+
*
|
|
811
|
+
* @param mcpDir - Path to the mcp directory containing service files
|
|
812
|
+
* @param serviceFactories - Optional map of service class names to factory functions for dependency injection
|
|
813
|
+
*
|
|
814
|
+
* @example
|
|
815
|
+
* // Auto-register services with no dependencies
|
|
816
|
+
* await server.autoRegisterServices('./mcp');
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* // Auto-register with dependency injection
|
|
820
|
+
* await server.autoRegisterServices('./mcp', {
|
|
821
|
+
* SlackService: () => new SlackService(process.env.SLACK_TOKEN),
|
|
822
|
+
* AuthService: () => new AuthService(authProvider)
|
|
823
|
+
* });
|
|
824
|
+
*/
|
|
825
|
+
async autoRegisterServices(mcpDir, serviceFactories) {
|
|
826
|
+
this.logger.debug(`Auto-registering services from: ${mcpDir}`);
|
|
827
|
+
if (!fs.existsSync(mcpDir)) {
|
|
828
|
+
this.logger.warn(`MCP directory not found: ${mcpDir}`);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const serviceFiles = this.findServiceFiles(mcpDir);
|
|
832
|
+
this.logger.debug(`Found ${serviceFiles.length} service file(s)`);
|
|
833
|
+
for (const filePath of serviceFiles) {
|
|
834
|
+
try {
|
|
835
|
+
await this.loadAndRegisterService(filePath, serviceFactories);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
this.logger.error(`Failed to load service from ${filePath}: ${error.message}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Recursively find all index.ts/index.js files in the mcp directory
|
|
843
|
+
*/
|
|
844
|
+
findServiceFiles(dir) {
|
|
845
|
+
const files = [];
|
|
846
|
+
const entries = fs.readdirSync(dir, {
|
|
847
|
+
withFileTypes: true
|
|
848
|
+
});
|
|
849
|
+
for (const entry of entries) {
|
|
850
|
+
const fullPath = path.join(dir, entry.name);
|
|
851
|
+
if (entry.isDirectory()) {
|
|
852
|
+
files.push(...this.findServiceFiles(fullPath));
|
|
853
|
+
} else if (entry.isFile()) {
|
|
854
|
+
if (entry.name === "index.ts" || entry.name === "index.js") {
|
|
855
|
+
files.push(fullPath);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return files;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Load a service file and register all exported classes
|
|
863
|
+
*/
|
|
864
|
+
async loadAndRegisterService(filePath, serviceFactories) {
|
|
865
|
+
this.logger.debug(`Loading service from: ${filePath}`);
|
|
866
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
867
|
+
const module = await import(fileUrl);
|
|
868
|
+
let registeredCount = 0;
|
|
869
|
+
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
870
|
+
if (typeof exportValue === "function" && exportValue.prototype) {
|
|
871
|
+
try {
|
|
872
|
+
let instance;
|
|
873
|
+
if (serviceFactories && serviceFactories[exportName]) {
|
|
874
|
+
instance = serviceFactories[exportName]();
|
|
875
|
+
this.logger.info(`Using factory for service: ${exportName}`);
|
|
876
|
+
} else {
|
|
877
|
+
instance = new exportValue();
|
|
878
|
+
}
|
|
879
|
+
this.registerService(instance);
|
|
880
|
+
registeredCount++;
|
|
881
|
+
this.logger.debug(`Registered service: ${exportName} from ${path.basename(filePath)}`);
|
|
882
|
+
} catch (error) {
|
|
883
|
+
this.logger.warn(`Skipped ${exportName}: ${error.message}`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (registeredCount === 0) {
|
|
888
|
+
this.logger.warn(`No services registered from ${filePath}`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
691
892
|
* Register a service instance with decorated methods
|
|
692
893
|
*/
|
|
693
894
|
registerService(instance) {
|
|
@@ -709,7 +910,7 @@ var MCPServer = class {
|
|
|
709
910
|
propertyKey
|
|
710
911
|
});
|
|
711
912
|
if (this.logging) {
|
|
712
|
-
this.logger.
|
|
913
|
+
this.logger.debug(`Registered tool: ${methodMeta.toolName}${inputClass ? " (class-based schema)" : ""}`);
|
|
713
914
|
}
|
|
714
915
|
}
|
|
715
916
|
const promptMethods = getDecoratedMethods(cls, "prompt:name");
|
|
@@ -734,7 +935,7 @@ var MCPServer = class {
|
|
|
734
935
|
propertyKey
|
|
735
936
|
});
|
|
736
937
|
if (this.logging) {
|
|
737
|
-
this.logger.
|
|
938
|
+
this.logger.debug(`Registered prompt: ${methodMeta.promptName}`);
|
|
738
939
|
}
|
|
739
940
|
}
|
|
740
941
|
const resourceMethods = getDecoratedMethods(cls, "resource:uri");
|
|
@@ -757,14 +958,16 @@ var MCPServer = class {
|
|
|
757
958
|
propertyKey
|
|
758
959
|
});
|
|
759
960
|
if (this.logging) {
|
|
760
|
-
this.logger.
|
|
961
|
+
this.logger.debug(`Registered resource: ${methodMeta.resourceUri}`);
|
|
761
962
|
}
|
|
762
963
|
}
|
|
763
964
|
}
|
|
764
965
|
/**
|
|
765
966
|
* Get the underlying MCP SDK Server instance
|
|
967
|
+
* Attaches waitForInit method for HTTP server initialization
|
|
766
968
|
*/
|
|
767
969
|
getServer() {
|
|
970
|
+
this.server.waitForInit = () => this.waitForInit();
|
|
768
971
|
return this.server;
|
|
769
972
|
}
|
|
770
973
|
};
|