@leanmcp/core 0.1.2 → 0.3.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 +214 -13
- package/dist/index.d.mts +72 -7
- package/dist/index.d.ts +72 -7
- package/dist/index.js +931 -665
- package/dist/index.mjs +251 -29
- package/package.json +7 -7
- package/dist/chunk-O6YSETKJ.mjs +0 -6
- package/dist/dist-LQ4M5W3M.mjs +0 -587
package/dist/index.js
CHANGED
|
@@ -6,6 +6,9 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
9
12
|
var __export = (target, all) => {
|
|
10
13
|
for (var name in all)
|
|
11
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -28,48 +31,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
31
|
));
|
|
29
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
33
|
|
|
31
|
-
// src/index.ts
|
|
32
|
-
var index_exports = {};
|
|
33
|
-
__export(index_exports, {
|
|
34
|
-
Auth: () => Auth,
|
|
35
|
-
Deprecated: () => Deprecated,
|
|
36
|
-
LogLevel: () => LogLevel,
|
|
37
|
-
Logger: () => Logger,
|
|
38
|
-
MCPServer: () => MCPServer,
|
|
39
|
-
MCPServerRuntime: () => MCPServerRuntime,
|
|
40
|
-
Optional: () => Optional,
|
|
41
|
-
Prompt: () => Prompt,
|
|
42
|
-
Render: () => Render,
|
|
43
|
-
Resource: () => Resource,
|
|
44
|
-
SchemaConstraint: () => SchemaConstraint,
|
|
45
|
-
Tool: () => Tool,
|
|
46
|
-
UI: () => UI,
|
|
47
|
-
UserEnvs: () => UserEnvs,
|
|
48
|
-
classToJsonSchema: () => classToJsonSchema,
|
|
49
|
-
classToJsonSchemaWithConstraints: () => classToJsonSchemaWithConstraints,
|
|
50
|
-
createHTTPServer: () => createHTTPServer,
|
|
51
|
-
defaultLogger: () => defaultLogger,
|
|
52
|
-
getDecoratedMethods: () => getDecoratedMethods,
|
|
53
|
-
getMethodMetadata: () => getMethodMetadata,
|
|
54
|
-
startMCPServer: () => startMCPServer,
|
|
55
|
-
validateNonEmpty: () => validateNonEmpty,
|
|
56
|
-
validatePath: () => validatePath,
|
|
57
|
-
validatePort: () => validatePort,
|
|
58
|
-
validateServiceName: () => validateServiceName,
|
|
59
|
-
validateUrl: () => validateUrl
|
|
60
|
-
});
|
|
61
|
-
module.exports = __toCommonJS(index_exports);
|
|
62
|
-
var import_reflect_metadata3 = require("reflect-metadata");
|
|
63
|
-
var import_fs = __toESM(require("fs"));
|
|
64
|
-
var import_path = __toESM(require("path"));
|
|
65
|
-
var import_url = require("url");
|
|
66
|
-
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
67
|
-
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
68
|
-
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
69
|
-
var import_ajv = __toESM(require("ajv"));
|
|
70
|
-
|
|
71
34
|
// src/decorators.ts
|
|
72
|
-
var import_reflect_metadata = require("reflect-metadata");
|
|
73
35
|
function Tool(options = {}) {
|
|
74
36
|
return (target, propertyKey, descriptor) => {
|
|
75
37
|
const toolName = String(propertyKey);
|
|
@@ -81,7 +43,6 @@ function Tool(options = {}) {
|
|
|
81
43
|
}
|
|
82
44
|
};
|
|
83
45
|
}
|
|
84
|
-
__name(Tool, "Tool");
|
|
85
46
|
function Prompt(options = {}) {
|
|
86
47
|
return (target, propertyKey, descriptor) => {
|
|
87
48
|
const promptName = String(propertyKey);
|
|
@@ -98,7 +59,6 @@ function Prompt(options = {}) {
|
|
|
98
59
|
}
|
|
99
60
|
};
|
|
100
61
|
}
|
|
101
|
-
__name(Prompt, "Prompt");
|
|
102
62
|
function Resource(options = {}) {
|
|
103
63
|
return (target, propertyKey, descriptor) => {
|
|
104
64
|
const resourceName = String(propertyKey);
|
|
@@ -114,7 +74,6 @@ function Resource(options = {}) {
|
|
|
114
74
|
}
|
|
115
75
|
};
|
|
116
76
|
}
|
|
117
|
-
__name(Resource, "Resource");
|
|
118
77
|
function Auth(options) {
|
|
119
78
|
return (target, propertyKey, descriptor) => {
|
|
120
79
|
if (propertyKey && descriptor) {
|
|
@@ -126,14 +85,12 @@ function Auth(options) {
|
|
|
126
85
|
}
|
|
127
86
|
};
|
|
128
87
|
}
|
|
129
|
-
__name(Auth, "Auth");
|
|
130
88
|
function UserEnvs() {
|
|
131
89
|
return (target, propertyKey) => {
|
|
132
90
|
const constructor = target.constructor;
|
|
133
91
|
Reflect.defineMetadata("userenvs:propertyKey", propertyKey, constructor);
|
|
134
92
|
};
|
|
135
93
|
}
|
|
136
|
-
__name(UserEnvs, "UserEnvs");
|
|
137
94
|
function UI(component) {
|
|
138
95
|
return (target, propertyKey, descriptor) => {
|
|
139
96
|
if (propertyKey && descriptor) {
|
|
@@ -143,13 +100,11 @@ function UI(component) {
|
|
|
143
100
|
}
|
|
144
101
|
};
|
|
145
102
|
}
|
|
146
|
-
__name(UI, "UI");
|
|
147
103
|
function Render(format) {
|
|
148
104
|
return (target, propertyKey, descriptor) => {
|
|
149
105
|
Reflect.defineMetadata("render:format", format, descriptor.value);
|
|
150
106
|
};
|
|
151
107
|
}
|
|
152
|
-
__name(Render, "Render");
|
|
153
108
|
function Deprecated(message) {
|
|
154
109
|
return (target, propertyKey, descriptor) => {
|
|
155
110
|
const deprecationMessage = message || "This feature is deprecated";
|
|
@@ -168,7 +123,6 @@ function Deprecated(message) {
|
|
|
168
123
|
}
|
|
169
124
|
};
|
|
170
125
|
}
|
|
171
|
-
__name(Deprecated, "Deprecated");
|
|
172
126
|
function getMethodMetadata(method) {
|
|
173
127
|
return {
|
|
174
128
|
// Tool metadata
|
|
@@ -192,7 +146,6 @@ function getMethodMetadata(method) {
|
|
|
192
146
|
deprecationMessage: Reflect.getMetadata("deprecated:message", method)
|
|
193
147
|
};
|
|
194
148
|
}
|
|
195
|
-
__name(getMethodMetadata, "getMethodMetadata");
|
|
196
149
|
function getDecoratedMethods(target, metadataKey) {
|
|
197
150
|
const methods = [];
|
|
198
151
|
const prototype = target.prototype || target;
|
|
@@ -211,10 +164,25 @@ function getDecoratedMethods(target, metadataKey) {
|
|
|
211
164
|
}
|
|
212
165
|
return methods;
|
|
213
166
|
}
|
|
214
|
-
|
|
167
|
+
var import_reflect_metadata;
|
|
168
|
+
var init_decorators = __esm({
|
|
169
|
+
"src/decorators.ts"() {
|
|
170
|
+
"use strict";
|
|
171
|
+
import_reflect_metadata = require("reflect-metadata");
|
|
172
|
+
__name(Tool, "Tool");
|
|
173
|
+
__name(Prompt, "Prompt");
|
|
174
|
+
__name(Resource, "Resource");
|
|
175
|
+
__name(Auth, "Auth");
|
|
176
|
+
__name(UserEnvs, "UserEnvs");
|
|
177
|
+
__name(UI, "UI");
|
|
178
|
+
__name(Render, "Render");
|
|
179
|
+
__name(Deprecated, "Deprecated");
|
|
180
|
+
__name(getMethodMetadata, "getMethodMetadata");
|
|
181
|
+
__name(getDecoratedMethods, "getDecoratedMethods");
|
|
182
|
+
}
|
|
183
|
+
});
|
|
215
184
|
|
|
216
185
|
// src/schema-generator.ts
|
|
217
|
-
var import_reflect_metadata2 = require("reflect-metadata");
|
|
218
186
|
function classToJsonSchema(classConstructor) {
|
|
219
187
|
const instance = new classConstructor();
|
|
220
188
|
const properties = {};
|
|
@@ -261,19 +229,16 @@ function classToJsonSchema(classConstructor) {
|
|
|
261
229
|
required: required.length > 0 ? required : void 0
|
|
262
230
|
};
|
|
263
231
|
}
|
|
264
|
-
__name(classToJsonSchema, "classToJsonSchema");
|
|
265
232
|
function Optional() {
|
|
266
233
|
return (target, propertyKey) => {
|
|
267
234
|
Reflect.defineMetadata("optional", true, target, propertyKey);
|
|
268
235
|
};
|
|
269
236
|
}
|
|
270
|
-
__name(Optional, "Optional");
|
|
271
237
|
function SchemaConstraint(constraints) {
|
|
272
238
|
return (target, propertyKey) => {
|
|
273
239
|
Reflect.defineMetadata("schema:constraints", constraints, target, propertyKey);
|
|
274
240
|
};
|
|
275
241
|
}
|
|
276
|
-
__name(SchemaConstraint, "SchemaConstraint");
|
|
277
242
|
function classToJsonSchemaWithConstraints(classConstructor) {
|
|
278
243
|
const instance = new classConstructor();
|
|
279
244
|
const properties = {};
|
|
@@ -334,70 +299,83 @@ function classToJsonSchemaWithConstraints(classConstructor) {
|
|
|
334
299
|
required: required.length > 0 ? required : void 0
|
|
335
300
|
};
|
|
336
301
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
302
|
+
var import_reflect_metadata2;
|
|
303
|
+
var init_schema_generator = __esm({
|
|
304
|
+
"src/schema-generator.ts"() {
|
|
305
|
+
"use strict";
|
|
306
|
+
import_reflect_metadata2 = require("reflect-metadata");
|
|
307
|
+
__name(classToJsonSchema, "classToJsonSchema");
|
|
308
|
+
__name(Optional, "Optional");
|
|
309
|
+
__name(SchemaConstraint, "SchemaConstraint");
|
|
310
|
+
__name(classToJsonSchemaWithConstraints, "classToJsonSchemaWithConstraints");
|
|
311
|
+
}
|
|
312
|
+
});
|
|
341
313
|
|
|
342
314
|
// src/logger.ts
|
|
343
|
-
var LogLevel
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
315
|
+
var LogLevel, Logger, defaultLogger;
|
|
316
|
+
var init_logger = __esm({
|
|
317
|
+
"src/logger.ts"() {
|
|
318
|
+
"use strict";
|
|
319
|
+
LogLevel = /* @__PURE__ */ (function(LogLevel2) {
|
|
320
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
321
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
322
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
323
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
324
|
+
LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
|
|
325
|
+
return LogLevel2;
|
|
326
|
+
})({});
|
|
327
|
+
Logger = class {
|
|
328
|
+
static {
|
|
329
|
+
__name(this, "Logger");
|
|
330
|
+
}
|
|
331
|
+
level;
|
|
332
|
+
prefix;
|
|
333
|
+
timestamps;
|
|
334
|
+
constructor(options = {}) {
|
|
335
|
+
this.level = options.level ?? 1;
|
|
336
|
+
this.prefix = options.prefix ?? "";
|
|
337
|
+
this.timestamps = options.timestamps ?? true;
|
|
338
|
+
}
|
|
339
|
+
format(level, message, ...args) {
|
|
340
|
+
const timestamp = this.timestamps ? `[${(/* @__PURE__ */ new Date()).toISOString()}]` : "";
|
|
341
|
+
const prefix = this.prefix ? `[${this.prefix}]` : "";
|
|
342
|
+
return `${timestamp}${prefix}[${level}] ${message}`;
|
|
343
|
+
}
|
|
344
|
+
shouldLog(level) {
|
|
345
|
+
return level >= this.level;
|
|
346
|
+
}
|
|
347
|
+
debug(message, ...args) {
|
|
348
|
+
if (this.shouldLog(0)) {
|
|
349
|
+
console.debug(this.format("DEBUG", message), ...args);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
info(message, ...args) {
|
|
353
|
+
if (this.shouldLog(1)) {
|
|
354
|
+
console.info(this.format("INFO", message), ...args);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
warn(message, ...args) {
|
|
358
|
+
if (this.shouldLog(2)) {
|
|
359
|
+
console.warn(this.format("WARN", message), ...args);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
error(message, ...args) {
|
|
363
|
+
if (this.shouldLog(3)) {
|
|
364
|
+
console.error(this.format("ERROR", message), ...args);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
setLevel(level) {
|
|
368
|
+
this.level = level;
|
|
369
|
+
}
|
|
370
|
+
getLevel() {
|
|
371
|
+
return this.level;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
defaultLogger = new Logger({
|
|
375
|
+
level: 1,
|
|
376
|
+
prefix: "LeanMCP"
|
|
377
|
+
});
|
|
396
378
|
}
|
|
397
|
-
};
|
|
398
|
-
var defaultLogger = new Logger({
|
|
399
|
-
level: 1,
|
|
400
|
-
prefix: "LeanMCP"
|
|
401
379
|
});
|
|
402
380
|
|
|
403
381
|
// src/validation.ts
|
|
@@ -406,26 +384,22 @@ function validatePort(port) {
|
|
|
406
384
|
throw new Error(`Invalid port: ${port}. Must be an integer between 1-65535`);
|
|
407
385
|
}
|
|
408
386
|
}
|
|
409
|
-
__name(validatePort, "validatePort");
|
|
410
387
|
function validatePath(path2) {
|
|
411
388
|
if (path2.includes("..") || path2.includes("~")) {
|
|
412
389
|
throw new Error(`Invalid path: ${path2}. Path traversal patterns are not allowed`);
|
|
413
390
|
}
|
|
414
391
|
}
|
|
415
|
-
__name(validatePath, "validatePath");
|
|
416
392
|
function validateServiceName(name) {
|
|
417
393
|
const validNamePattern = /^[a-zA-Z0-9_-]+$/;
|
|
418
394
|
if (!validNamePattern.test(name)) {
|
|
419
395
|
throw new Error(`Invalid service name: ${name}. Service names must contain only alphanumeric characters, hyphens, and underscores`);
|
|
420
396
|
}
|
|
421
397
|
}
|
|
422
|
-
__name(validateServiceName, "validateServiceName");
|
|
423
398
|
function validateNonEmpty(value, fieldName) {
|
|
424
399
|
if (!value || value.trim().length === 0) {
|
|
425
400
|
throw new Error(`${fieldName} cannot be empty`);
|
|
426
401
|
}
|
|
427
402
|
}
|
|
428
|
-
__name(validateNonEmpty, "validateNonEmpty");
|
|
429
403
|
function validateUrl(url, allowedProtocols = [
|
|
430
404
|
"http:",
|
|
431
405
|
"https:"
|
|
@@ -442,14 +416,41 @@ function validateUrl(url, allowedProtocols = [
|
|
|
442
416
|
throw error;
|
|
443
417
|
}
|
|
444
418
|
}
|
|
445
|
-
|
|
419
|
+
var init_validation = __esm({
|
|
420
|
+
"src/validation.ts"() {
|
|
421
|
+
"use strict";
|
|
422
|
+
__name(validatePort, "validatePort");
|
|
423
|
+
__name(validatePath, "validatePath");
|
|
424
|
+
__name(validateServiceName, "validateServiceName");
|
|
425
|
+
__name(validateNonEmpty, "validateNonEmpty");
|
|
426
|
+
__name(validateUrl, "validateUrl");
|
|
427
|
+
}
|
|
428
|
+
});
|
|
446
429
|
|
|
447
430
|
// src/http-server.ts
|
|
448
431
|
function isInitializeRequest(body) {
|
|
449
432
|
return body && body.method === "initialize";
|
|
450
433
|
}
|
|
451
|
-
|
|
452
|
-
|
|
434
|
+
async function createHTTPServer(serverInput, options) {
|
|
435
|
+
let serverFactory;
|
|
436
|
+
let httpOptions;
|
|
437
|
+
if (typeof serverInput === "function") {
|
|
438
|
+
serverFactory = serverInput;
|
|
439
|
+
httpOptions = options || {};
|
|
440
|
+
} else {
|
|
441
|
+
const serverOptions = serverInput;
|
|
442
|
+
const { MCPServer: MCPServer2 } = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
443
|
+
serverFactory = /* @__PURE__ */ __name(async () => {
|
|
444
|
+
const mcpServer2 = new MCPServer2(serverOptions);
|
|
445
|
+
return mcpServer2.getServer();
|
|
446
|
+
}, "serverFactory");
|
|
447
|
+
httpOptions = {
|
|
448
|
+
port: serverOptions.port,
|
|
449
|
+
cors: serverOptions.cors,
|
|
450
|
+
logging: serverOptions.logging,
|
|
451
|
+
sessionTimeout: serverOptions.sessionTimeout
|
|
452
|
+
};
|
|
453
|
+
}
|
|
453
454
|
const [express, { StreamableHTTPServerTransport }, cors] = await Promise.all([
|
|
454
455
|
// @ts-ignore
|
|
455
456
|
import("express").catch(() => {
|
|
@@ -460,19 +461,20 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
460
461
|
throw new Error("MCP SDK not found. Install with: npm install @modelcontextprotocol/sdk");
|
|
461
462
|
}),
|
|
462
463
|
// @ts-ignore
|
|
463
|
-
|
|
464
|
+
httpOptions.cors ? import("cors").catch(() => null) : Promise.resolve(null)
|
|
464
465
|
]);
|
|
465
466
|
const app = express.default();
|
|
466
|
-
const port =
|
|
467
|
+
const port = httpOptions.port || 3001;
|
|
467
468
|
validatePort(port);
|
|
468
469
|
const transports = {};
|
|
469
|
-
|
|
470
|
-
|
|
470
|
+
let mcpServer = null;
|
|
471
|
+
const logger = httpOptions.logger || new Logger({
|
|
472
|
+
level: httpOptions.logging ? LogLevel.INFO : LogLevel.NONE,
|
|
471
473
|
prefix: "HTTP"
|
|
472
474
|
});
|
|
473
|
-
if (cors &&
|
|
474
|
-
const corsOptions = typeof
|
|
475
|
-
origin:
|
|
475
|
+
if (cors && httpOptions.cors) {
|
|
476
|
+
const corsOptions = typeof httpOptions.cors === "object" ? {
|
|
477
|
+
origin: httpOptions.cors.origin || false,
|
|
476
478
|
methods: [
|
|
477
479
|
"GET",
|
|
478
480
|
"POST",
|
|
@@ -488,7 +490,7 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
488
490
|
exposedHeaders: [
|
|
489
491
|
"mcp-session-id"
|
|
490
492
|
],
|
|
491
|
-
credentials:
|
|
493
|
+
credentials: httpOptions.cors.credentials ?? false,
|
|
492
494
|
maxAge: 86400
|
|
493
495
|
} : false;
|
|
494
496
|
if (corsOptions) {
|
|
@@ -507,6 +509,18 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
507
509
|
const handleMCPRequest = /* @__PURE__ */ __name(async (req, res) => {
|
|
508
510
|
const sessionId = req.headers["mcp-session-id"];
|
|
509
511
|
let transport;
|
|
512
|
+
const method = req.body?.method || "unknown";
|
|
513
|
+
const params = req.body?.params;
|
|
514
|
+
let logMessage = `${req.method} /mcp - ${method}`;
|
|
515
|
+
if (params?.name) {
|
|
516
|
+
logMessage += ` [${params.name}]`;
|
|
517
|
+
} else if (params?.uri) {
|
|
518
|
+
logMessage += ` [${params.uri}]`;
|
|
519
|
+
}
|
|
520
|
+
if (sessionId) {
|
|
521
|
+
logMessage += ` (session: ${sessionId.substring(0, 8)}...)`;
|
|
522
|
+
}
|
|
523
|
+
logger.info(logMessage);
|
|
510
524
|
try {
|
|
511
525
|
if (sessionId && transports[sessionId]) {
|
|
512
526
|
transport = transports[sessionId];
|
|
@@ -526,8 +540,10 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
526
540
|
logger.debug(`Session cleaned up: ${transport.sessionId}`);
|
|
527
541
|
}
|
|
528
542
|
};
|
|
529
|
-
|
|
530
|
-
|
|
543
|
+
if (!mcpServer) {
|
|
544
|
+
throw new Error("MCP server not initialized");
|
|
545
|
+
}
|
|
546
|
+
await mcpServer.connect(transport);
|
|
531
547
|
} else {
|
|
532
548
|
res.status(400).json({
|
|
533
549
|
jsonrpc: "2.0",
|
|
@@ -556,583 +572,833 @@ async function createHTTPServer(serverFactory, options = {}) {
|
|
|
556
572
|
}, "handleMCPRequest");
|
|
557
573
|
app.post("/mcp", handleMCPRequest);
|
|
558
574
|
app.delete("/mcp", handleMCPRequest);
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
575
|
+
return new Promise(async (resolve, reject) => {
|
|
576
|
+
try {
|
|
577
|
+
mcpServer = await serverFactory();
|
|
578
|
+
if (mcpServer && typeof mcpServer.waitForInit === "function") {
|
|
579
|
+
await mcpServer.waitForInit();
|
|
580
|
+
}
|
|
581
|
+
const listener = app.listen(port, () => {
|
|
582
|
+
logger.info(`Server running on http://localhost:${port}`);
|
|
583
|
+
logger.info(`MCP endpoint: http://localhost:${port}/mcp`);
|
|
584
|
+
logger.info(`Health check: http://localhost:${port}/health`);
|
|
585
|
+
resolve(listener);
|
|
586
|
+
});
|
|
587
|
+
listener.on("error", (error) => {
|
|
588
|
+
logger.error(`Server error: ${error.message}`);
|
|
589
|
+
reject(error);
|
|
590
|
+
});
|
|
591
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
592
|
+
logger.info("\nShutting down server...");
|
|
593
|
+
Object.values(transports).forEach((t) => t.close?.());
|
|
594
|
+
listener.close(() => {
|
|
595
|
+
logger.info("Server closed");
|
|
596
|
+
process.exit(0);
|
|
597
|
+
});
|
|
598
|
+
setTimeout(() => {
|
|
599
|
+
logger.warn("Forcing shutdown...");
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}, 5e3);
|
|
602
|
+
}, "cleanup");
|
|
603
|
+
process.on("SIGINT", cleanup);
|
|
604
|
+
process.on("SIGTERM", cleanup);
|
|
605
|
+
} catch (error) {
|
|
606
|
+
reject(error);
|
|
607
|
+
}
|
|
571
608
|
});
|
|
572
609
|
}
|
|
573
|
-
|
|
610
|
+
var import_node_crypto;
|
|
611
|
+
var init_http_server = __esm({
|
|
612
|
+
"src/http-server.ts"() {
|
|
613
|
+
"use strict";
|
|
614
|
+
import_node_crypto = require("crypto");
|
|
615
|
+
init_logger();
|
|
616
|
+
init_validation();
|
|
617
|
+
__name(isInitializeRequest, "isInitializeRequest");
|
|
618
|
+
__name(createHTTPServer, "createHTTPServer");
|
|
619
|
+
}
|
|
620
|
+
});
|
|
574
621
|
|
|
575
622
|
// src/index.ts
|
|
576
|
-
var
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
623
|
+
var index_exports = {};
|
|
624
|
+
__export(index_exports, {
|
|
625
|
+
Auth: () => Auth,
|
|
626
|
+
Deprecated: () => Deprecated,
|
|
627
|
+
LogLevel: () => LogLevel,
|
|
628
|
+
Logger: () => Logger,
|
|
629
|
+
MCPServer: () => MCPServer,
|
|
630
|
+
MCPServerRuntime: () => MCPServerRuntime,
|
|
631
|
+
Optional: () => Optional,
|
|
632
|
+
Prompt: () => Prompt,
|
|
633
|
+
Render: () => Render,
|
|
634
|
+
Resource: () => Resource,
|
|
635
|
+
SchemaConstraint: () => SchemaConstraint,
|
|
636
|
+
Tool: () => Tool,
|
|
637
|
+
UI: () => UI,
|
|
638
|
+
UserEnvs: () => UserEnvs,
|
|
639
|
+
classToJsonSchema: () => classToJsonSchema,
|
|
640
|
+
classToJsonSchemaWithConstraints: () => classToJsonSchemaWithConstraints,
|
|
641
|
+
createHTTPServer: () => createHTTPServer,
|
|
642
|
+
defaultLogger: () => defaultLogger,
|
|
643
|
+
getDecoratedMethods: () => getDecoratedMethods,
|
|
644
|
+
getMethodMetadata: () => getMethodMetadata,
|
|
645
|
+
startMCPServer: () => startMCPServer,
|
|
646
|
+
validateNonEmpty: () => validateNonEmpty,
|
|
647
|
+
validatePath: () => validatePath,
|
|
648
|
+
validatePort: () => validatePort,
|
|
649
|
+
validateServiceName: () => validateServiceName,
|
|
650
|
+
validateUrl: () => validateUrl
|
|
651
|
+
});
|
|
652
|
+
module.exports = __toCommonJS(index_exports);
|
|
653
|
+
async function startMCPServer(options) {
|
|
654
|
+
const runtime = new MCPServerRuntime(options);
|
|
655
|
+
await runtime.start();
|
|
656
|
+
return runtime;
|
|
657
|
+
}
|
|
658
|
+
var import_reflect_metadata3, import_fs, import_path, import_url, import_server, import_stdio, import_types, import_ajv, ajv, MCPServer, MCPServerRuntime;
|
|
659
|
+
var init_index = __esm({
|
|
660
|
+
"src/index.ts"() {
|
|
661
|
+
import_reflect_metadata3 = require("reflect-metadata");
|
|
662
|
+
import_fs = __toESM(require("fs"));
|
|
663
|
+
import_path = __toESM(require("path"));
|
|
664
|
+
import_url = require("url");
|
|
665
|
+
import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
666
|
+
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
667
|
+
import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
668
|
+
import_ajv = __toESM(require("ajv"));
|
|
669
|
+
init_decorators();
|
|
670
|
+
init_schema_generator();
|
|
671
|
+
init_http_server();
|
|
672
|
+
init_logger();
|
|
673
|
+
init_validation();
|
|
674
|
+
init_decorators();
|
|
675
|
+
init_schema_generator();
|
|
676
|
+
init_logger();
|
|
677
|
+
ajv = new import_ajv.default();
|
|
678
|
+
MCPServer = class {
|
|
679
|
+
static {
|
|
680
|
+
__name(this, "MCPServer");
|
|
601
681
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
682
|
+
server;
|
|
683
|
+
tools = /* @__PURE__ */ new Map();
|
|
684
|
+
prompts = /* @__PURE__ */ new Map();
|
|
685
|
+
resources = /* @__PURE__ */ new Map();
|
|
686
|
+
logging;
|
|
687
|
+
logger;
|
|
688
|
+
options;
|
|
689
|
+
initPromise;
|
|
690
|
+
autoDiscovered = false;
|
|
691
|
+
constructor(options) {
|
|
692
|
+
this.options = options;
|
|
693
|
+
this.logging = options.logging || false;
|
|
694
|
+
let logLevel = LogLevel.NONE;
|
|
695
|
+
if (options.logging) {
|
|
696
|
+
logLevel = options.debug ? LogLevel.DEBUG : LogLevel.INFO;
|
|
697
|
+
}
|
|
698
|
+
this.logger = new Logger({
|
|
699
|
+
level: logLevel,
|
|
700
|
+
prefix: "MCPServer"
|
|
701
|
+
});
|
|
702
|
+
this.server = new import_server.Server({
|
|
703
|
+
name: options.name,
|
|
704
|
+
version: options.version
|
|
705
|
+
}, {
|
|
706
|
+
capabilities: {
|
|
707
|
+
tools: {},
|
|
708
|
+
prompts: {},
|
|
709
|
+
resources: {}
|
|
615
710
|
}
|
|
616
711
|
});
|
|
712
|
+
this.setupHandlers();
|
|
713
|
+
this.initPromise = this.autoInit();
|
|
617
714
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if (!tool) {
|
|
626
|
-
throw new Error(`Tool ${toolName} not found`);
|
|
627
|
-
}
|
|
628
|
-
const methodMeta = getMethodMetadata(tool.method);
|
|
629
|
-
if (methodMeta.inputSchema) {
|
|
630
|
-
const validate = ajv.compile(methodMeta.inputSchema);
|
|
631
|
-
const valid = validate(request.params.arguments || {});
|
|
632
|
-
if (!valid) {
|
|
633
|
-
throw new Error(`Input validation failed: ${JSON.stringify(validate.errors)}`);
|
|
715
|
+
/**
|
|
716
|
+
* Internal initialization - runs automatically in constructor
|
|
717
|
+
*/
|
|
718
|
+
async autoInit() {
|
|
719
|
+
const options = this.options;
|
|
720
|
+
if (options.autoDiscover !== false) {
|
|
721
|
+
await this.autoDiscoverServices(options.mcpDir, options.serviceFactories);
|
|
634
722
|
}
|
|
635
723
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
} else if (methodMeta.renderFormat === "json" || typeof result === "object") {
|
|
643
|
-
formattedResult = JSON.stringify(result, null, 2);
|
|
644
|
-
} else {
|
|
645
|
-
formattedResult = String(result);
|
|
646
|
-
}
|
|
647
|
-
return {
|
|
648
|
-
content: [
|
|
649
|
-
{
|
|
650
|
-
type: "text",
|
|
651
|
-
text: formattedResult
|
|
652
|
-
}
|
|
653
|
-
]
|
|
654
|
-
};
|
|
655
|
-
} catch (error) {
|
|
656
|
-
return {
|
|
657
|
-
content: [
|
|
658
|
-
{
|
|
659
|
-
type: "text",
|
|
660
|
-
text: `Error: ${error.message}`
|
|
661
|
-
}
|
|
662
|
-
],
|
|
663
|
-
isError: true
|
|
664
|
-
};
|
|
724
|
+
/**
|
|
725
|
+
* Wait for initialization to complete
|
|
726
|
+
* This is called internally by createHTTPServer
|
|
727
|
+
*/
|
|
728
|
+
async waitForInit() {
|
|
729
|
+
await this.initPromise;
|
|
665
730
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
731
|
+
/**
|
|
732
|
+
* Automatically discover and register services from the mcp directory
|
|
733
|
+
* Called by init() unless autoDiscover is set to false
|
|
734
|
+
*/
|
|
735
|
+
async autoDiscoverServices(customMcpDir, serviceFactories) {
|
|
736
|
+
if (this.autoDiscovered) return;
|
|
737
|
+
this.autoDiscovered = true;
|
|
738
|
+
try {
|
|
739
|
+
let mcpDir;
|
|
740
|
+
if (customMcpDir) {
|
|
741
|
+
mcpDir = customMcpDir;
|
|
742
|
+
} else {
|
|
743
|
+
const callerFile = this.getCallerFile();
|
|
744
|
+
if (callerFile) {
|
|
745
|
+
const callerDir = import_path.default.dirname(callerFile);
|
|
746
|
+
mcpDir = import_path.default.join(callerDir, "mcp");
|
|
747
|
+
} else {
|
|
748
|
+
mcpDir = import_path.default.join(process.cwd(), "mcp");
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (import_fs.default.existsSync(mcpDir)) {
|
|
752
|
+
this.logger.debug(`Auto-discovering services from: ${mcpDir}`);
|
|
753
|
+
await this.autoRegisterServices(mcpDir, serviceFactories);
|
|
754
|
+
} else {
|
|
755
|
+
this.logger.debug(`MCP directory not found at ${mcpDir}, skipping auto-discovery`);
|
|
756
|
+
}
|
|
757
|
+
} catch (error) {
|
|
758
|
+
this.logger.warn(`Auto-discovery failed: ${error.message}`);
|
|
678
759
|
}
|
|
679
|
-
resources.push(resourceInfo);
|
|
680
760
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
761
|
+
/**
|
|
762
|
+
* Get the file path of the caller (the file that instantiated MCPServer)
|
|
763
|
+
*/
|
|
764
|
+
getCallerFile() {
|
|
765
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
766
|
+
try {
|
|
767
|
+
const err = new Error();
|
|
768
|
+
Error.prepareStackTrace = (_, stack2) => stack2;
|
|
769
|
+
const stack = err.stack;
|
|
770
|
+
for (let i = 0; i < stack.length; i++) {
|
|
771
|
+
let fileName = stack[i].getFileName();
|
|
772
|
+
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"))) {
|
|
773
|
+
if (fileName.startsWith("file://")) {
|
|
774
|
+
fileName = fileName.replace("file:///", "").replace("file://", "");
|
|
775
|
+
if (process.platform === "win32" && fileName.startsWith("/")) {
|
|
776
|
+
fileName = fileName.substring(1);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return fileName;
|
|
699
780
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
705
|
-
});
|
|
706
|
-
this.server.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
|
|
707
|
-
const prompts = [];
|
|
708
|
-
for (const [name, prompt] of this.prompts.entries()) {
|
|
709
|
-
prompts.push({
|
|
710
|
-
name,
|
|
711
|
-
description: prompt.description,
|
|
712
|
-
arguments: prompt.arguments
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
return {
|
|
716
|
-
prompts
|
|
717
|
-
};
|
|
718
|
-
});
|
|
719
|
-
this.server.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
|
|
720
|
-
const promptName = request.params.name;
|
|
721
|
-
const prompt = this.prompts.get(promptName);
|
|
722
|
-
if (!prompt) {
|
|
723
|
-
throw new Error(`Prompt ${promptName} not found`);
|
|
724
|
-
}
|
|
725
|
-
try {
|
|
726
|
-
const result = await prompt.method.call(prompt.instance, request.params.arguments || {});
|
|
727
|
-
if (result && result.messages) {
|
|
728
|
-
return result;
|
|
781
|
+
}
|
|
782
|
+
return null;
|
|
783
|
+
} finally {
|
|
784
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
729
785
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
786
|
+
}
|
|
787
|
+
setupHandlers() {
|
|
788
|
+
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
789
|
+
const tools = [];
|
|
790
|
+
for (const [name, tool] of this.tools.entries()) {
|
|
791
|
+
tools.push({
|
|
792
|
+
name,
|
|
793
|
+
description: tool.description,
|
|
794
|
+
inputSchema: tool.inputSchema || {
|
|
795
|
+
type: "object",
|
|
796
|
+
properties: {}
|
|
738
797
|
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
tools
|
|
802
|
+
};
|
|
803
|
+
});
|
|
804
|
+
this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
805
|
+
const toolName = request.params.name;
|
|
806
|
+
const tool = this.tools.get(toolName);
|
|
807
|
+
if (!tool) {
|
|
808
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
809
|
+
}
|
|
810
|
+
const methodMeta = getMethodMetadata(tool.method);
|
|
811
|
+
if (methodMeta.inputSchema) {
|
|
812
|
+
const validate = ajv.compile(methodMeta.inputSchema);
|
|
813
|
+
const valid = validate(request.params.arguments || {});
|
|
814
|
+
if (!valid) {
|
|
815
|
+
throw new Error(`Input validation failed: ${JSON.stringify(validate.errors)}`);
|
|
739
816
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
tools: {},
|
|
850
|
-
resources: {},
|
|
851
|
-
prompts: {}
|
|
852
|
-
}
|
|
853
|
-
});
|
|
854
|
-
this.setupHandlers();
|
|
855
|
-
}
|
|
856
|
-
setupHandlers() {
|
|
857
|
-
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
858
|
-
const tools = [];
|
|
859
|
-
for (const [name, tool] of this.tools.entries()) {
|
|
860
|
-
tools.push({
|
|
861
|
-
name,
|
|
862
|
-
description: tool.description,
|
|
863
|
-
inputSchema: tool.inputSchema || {
|
|
864
|
-
type: "object",
|
|
865
|
-
properties: {}
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
const meta = request.params._meta;
|
|
820
|
+
const result = await tool.method.call(tool.instance, request.params.arguments, meta);
|
|
821
|
+
let formattedResult = result;
|
|
822
|
+
if (methodMeta.renderFormat === "markdown" && typeof result === "string") {
|
|
823
|
+
formattedResult = result;
|
|
824
|
+
} else if (methodMeta.renderFormat === "json" || typeof result === "object") {
|
|
825
|
+
formattedResult = JSON.stringify(result, null, 2);
|
|
826
|
+
} else {
|
|
827
|
+
formattedResult = String(result);
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
content: [
|
|
831
|
+
{
|
|
832
|
+
type: "text",
|
|
833
|
+
text: formattedResult
|
|
834
|
+
}
|
|
835
|
+
]
|
|
836
|
+
};
|
|
837
|
+
} catch (error) {
|
|
838
|
+
return {
|
|
839
|
+
content: [
|
|
840
|
+
{
|
|
841
|
+
type: "text",
|
|
842
|
+
text: `Error: ${error.message}`
|
|
843
|
+
}
|
|
844
|
+
],
|
|
845
|
+
isError: true
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
this.server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
|
|
850
|
+
const resources = [];
|
|
851
|
+
for (const [uri, resource] of this.resources.entries()) {
|
|
852
|
+
const resourceInfo = {
|
|
853
|
+
uri: resource.uri,
|
|
854
|
+
name: resource.name,
|
|
855
|
+
description: resource.description,
|
|
856
|
+
mimeType: resource.mimeType
|
|
857
|
+
};
|
|
858
|
+
if (resource.inputSchema) {
|
|
859
|
+
resourceInfo.inputSchema = resource.inputSchema;
|
|
860
|
+
}
|
|
861
|
+
resources.push(resourceInfo);
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
resources
|
|
865
|
+
};
|
|
866
|
+
});
|
|
867
|
+
this.server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
|
|
868
|
+
const uri = request.params.uri;
|
|
869
|
+
const resource = this.resources.get(uri);
|
|
870
|
+
if (!resource) {
|
|
871
|
+
throw new Error(`Resource ${uri} not found`);
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
const result = await resource.method.call(resource.instance);
|
|
875
|
+
return {
|
|
876
|
+
contents: [
|
|
877
|
+
{
|
|
878
|
+
uri,
|
|
879
|
+
mimeType: resource.mimeType,
|
|
880
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
881
|
+
}
|
|
882
|
+
]
|
|
883
|
+
};
|
|
884
|
+
} catch (error) {
|
|
885
|
+
throw new Error(`Failed to read resource ${uri}: ${error.message}`);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
this.server.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
|
|
889
|
+
const prompts = [];
|
|
890
|
+
for (const [name, prompt] of this.prompts.entries()) {
|
|
891
|
+
prompts.push({
|
|
892
|
+
name,
|
|
893
|
+
description: prompt.description,
|
|
894
|
+
arguments: prompt.arguments
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
prompts
|
|
899
|
+
};
|
|
900
|
+
});
|
|
901
|
+
this.server.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
|
|
902
|
+
const promptName = request.params.name;
|
|
903
|
+
const prompt = this.prompts.get(promptName);
|
|
904
|
+
if (!prompt) {
|
|
905
|
+
throw new Error(`Prompt ${promptName} not found`);
|
|
906
|
+
}
|
|
907
|
+
try {
|
|
908
|
+
const result = await prompt.method.call(prompt.instance, request.params.arguments || {});
|
|
909
|
+
if (result && result.messages) {
|
|
910
|
+
return result;
|
|
911
|
+
}
|
|
912
|
+
return {
|
|
913
|
+
description: prompt.description,
|
|
914
|
+
messages: [
|
|
915
|
+
{
|
|
916
|
+
role: "user",
|
|
917
|
+
content: {
|
|
918
|
+
type: "text",
|
|
919
|
+
text: typeof result === "string" ? result : JSON.stringify(result)
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
};
|
|
924
|
+
} catch (error) {
|
|
925
|
+
throw new Error(`Failed to get prompt ${promptName}: ${error.message}`);
|
|
866
926
|
}
|
|
867
927
|
});
|
|
868
928
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
929
|
+
/**
|
|
930
|
+
* Auto-register all services from the mcp directory
|
|
931
|
+
* Scans the directory recursively and registers all exported classes
|
|
932
|
+
*
|
|
933
|
+
* @param mcpDir - Path to the mcp directory containing service files
|
|
934
|
+
* @param serviceFactories - Optional map of service class names to factory functions for dependency injection
|
|
935
|
+
*
|
|
936
|
+
* @example
|
|
937
|
+
* // Auto-register services with no dependencies
|
|
938
|
+
* await server.autoRegisterServices('./mcp');
|
|
939
|
+
*
|
|
940
|
+
* @example
|
|
941
|
+
* // Auto-register with dependency injection
|
|
942
|
+
* await server.autoRegisterServices('./mcp', {
|
|
943
|
+
* SlackService: () => new SlackService(process.env.SLACK_TOKEN),
|
|
944
|
+
* AuthService: () => new AuthService(authProvider)
|
|
945
|
+
* });
|
|
946
|
+
*/
|
|
947
|
+
async autoRegisterServices(mcpDir, serviceFactories) {
|
|
948
|
+
this.logger.debug(`Auto-registering services from: ${mcpDir}`);
|
|
949
|
+
if (!import_fs.default.existsSync(mcpDir)) {
|
|
950
|
+
this.logger.warn(`MCP directory not found: ${mcpDir}`);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const serviceFiles = this.findServiceFiles(mcpDir);
|
|
954
|
+
this.logger.debug(`Found ${serviceFiles.length} service file(s)`);
|
|
955
|
+
for (const filePath of serviceFiles) {
|
|
956
|
+
try {
|
|
957
|
+
await this.loadAndRegisterService(filePath, serviceFactories);
|
|
958
|
+
} catch (error) {
|
|
959
|
+
this.logger.error(`Failed to load service from ${filePath}: ${error.message}`);
|
|
960
|
+
}
|
|
885
961
|
}
|
|
886
962
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
963
|
+
/**
|
|
964
|
+
* Recursively find all index.ts/index.js files in the mcp directory
|
|
965
|
+
*/
|
|
966
|
+
findServiceFiles(dir) {
|
|
967
|
+
const files = [];
|
|
968
|
+
const entries = import_fs.default.readdirSync(dir, {
|
|
969
|
+
withFileTypes: true
|
|
970
|
+
});
|
|
971
|
+
for (const entry of entries) {
|
|
972
|
+
const fullPath = import_path.default.join(dir, entry.name);
|
|
973
|
+
if (entry.isDirectory()) {
|
|
974
|
+
files.push(...this.findServiceFiles(fullPath));
|
|
975
|
+
} else if (entry.isFile()) {
|
|
976
|
+
if (entry.name === "index.ts" || entry.name === "index.js") {
|
|
977
|
+
files.push(fullPath);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
890
980
|
}
|
|
981
|
+
return files;
|
|
891
982
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
983
|
+
/**
|
|
984
|
+
* Load a service file and register all exported classes
|
|
985
|
+
*/
|
|
986
|
+
async loadAndRegisterService(filePath, serviceFactories) {
|
|
987
|
+
this.logger.debug(`Loading service from: ${filePath}`);
|
|
988
|
+
const fileUrl = (0, import_url.pathToFileURL)(filePath).href;
|
|
989
|
+
const module2 = await import(fileUrl);
|
|
990
|
+
let registeredCount = 0;
|
|
991
|
+
for (const [exportName, exportValue] of Object.entries(module2)) {
|
|
992
|
+
if (typeof exportValue === "function" && exportValue.prototype) {
|
|
993
|
+
try {
|
|
994
|
+
let instance;
|
|
995
|
+
if (serviceFactories && serviceFactories[exportName]) {
|
|
996
|
+
instance = serviceFactories[exportName]();
|
|
997
|
+
this.logger.info(`Using factory for service: ${exportName}`);
|
|
998
|
+
} else {
|
|
999
|
+
instance = new exportValue();
|
|
901
1000
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1001
|
+
this.registerService(instance);
|
|
1002
|
+
registeredCount++;
|
|
1003
|
+
this.logger.debug(`Registered service: ${exportName} from ${import_path.default.basename(filePath)}`);
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
this.logger.warn(`Skipped ${exportName}: ${error.message}`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
905
1008
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
formattedResult = result;
|
|
909
|
-
} else if (methodMeta.renderFormat === "json" || typeof result === "object") {
|
|
910
|
-
formattedResult = JSON.stringify(result, null, 2);
|
|
911
|
-
} else {
|
|
912
|
-
formattedResult = String(result);
|
|
1009
|
+
if (registeredCount === 0) {
|
|
1010
|
+
this.logger.warn(`No services registered from ${filePath}`);
|
|
913
1011
|
}
|
|
914
|
-
return {
|
|
915
|
-
content: [
|
|
916
|
-
{
|
|
917
|
-
type: "text",
|
|
918
|
-
text: formattedResult
|
|
919
|
-
}
|
|
920
|
-
]
|
|
921
|
-
};
|
|
922
|
-
} catch (error) {
|
|
923
|
-
return {
|
|
924
|
-
content: [
|
|
925
|
-
{
|
|
926
|
-
type: "text",
|
|
927
|
-
text: `Error: ${error.message}`
|
|
928
|
-
}
|
|
929
|
-
],
|
|
930
|
-
isError: true
|
|
931
|
-
};
|
|
932
1012
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1013
|
+
/**
|
|
1014
|
+
* Register a service instance with decorated methods
|
|
1015
|
+
*/
|
|
1016
|
+
registerService(instance) {
|
|
1017
|
+
const cls = instance.constructor;
|
|
1018
|
+
const toolMethods = getDecoratedMethods(cls, "tool:name");
|
|
1019
|
+
for (const { method, propertyKey } of toolMethods) {
|
|
1020
|
+
const methodMeta = getMethodMetadata(method);
|
|
1021
|
+
const inputClass = Reflect.getMetadata?.("tool:inputClass", method);
|
|
1022
|
+
let inputSchema = methodMeta.inputSchema;
|
|
1023
|
+
if (inputClass) {
|
|
1024
|
+
inputSchema = classToJsonSchemaWithConstraints(inputClass);
|
|
1025
|
+
}
|
|
1026
|
+
this.tools.set(methodMeta.toolName, {
|
|
1027
|
+
name: methodMeta.toolName,
|
|
1028
|
+
description: methodMeta.toolDescription || "",
|
|
1029
|
+
inputSchema,
|
|
1030
|
+
method,
|
|
1031
|
+
instance,
|
|
1032
|
+
propertyKey
|
|
1033
|
+
});
|
|
1034
|
+
if (this.logging) {
|
|
1035
|
+
this.logger.debug(`Registered tool: ${methodMeta.toolName}${inputClass ? " (class-based schema)" : ""}`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
const promptMethods = getDecoratedMethods(cls, "prompt:name");
|
|
1039
|
+
for (const { method, propertyKey } of promptMethods) {
|
|
1040
|
+
const methodMeta = getMethodMetadata(method);
|
|
1041
|
+
const inputClass = Reflect.getMetadata?.("prompt:inputClass", method);
|
|
1042
|
+
let inputSchema = methodMeta.inputSchema;
|
|
1043
|
+
if (inputClass) {
|
|
1044
|
+
inputSchema = classToJsonSchemaWithConstraints(inputClass);
|
|
1045
|
+
}
|
|
1046
|
+
const promptArgs = inputSchema?.properties ? Object.keys(inputSchema.properties).map((key) => ({
|
|
1047
|
+
name: key,
|
|
1048
|
+
description: inputSchema?.properties?.[key]?.description || "",
|
|
1049
|
+
required: inputSchema?.required?.includes(key) || false
|
|
1050
|
+
})) : [];
|
|
1051
|
+
this.prompts.set(methodMeta.promptName, {
|
|
1052
|
+
name: methodMeta.promptName,
|
|
1053
|
+
description: methodMeta.promptDescription || "",
|
|
1054
|
+
arguments: promptArgs,
|
|
1055
|
+
method,
|
|
1056
|
+
instance,
|
|
1057
|
+
propertyKey
|
|
1058
|
+
});
|
|
1059
|
+
if (this.logging) {
|
|
1060
|
+
this.logger.debug(`Registered prompt: ${methodMeta.promptName}`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
const resourceMethods = getDecoratedMethods(cls, "resource:uri");
|
|
1064
|
+
for (const { method, propertyKey } of resourceMethods) {
|
|
1065
|
+
const methodMeta = getMethodMetadata(method);
|
|
1066
|
+
const inputClass = Reflect.getMetadata?.("resource:inputClass", method);
|
|
1067
|
+
let inputSchema = methodMeta.inputSchema;
|
|
1068
|
+
if (inputClass) {
|
|
1069
|
+
inputSchema = classToJsonSchemaWithConstraints(inputClass);
|
|
1070
|
+
}
|
|
1071
|
+
const mimeType = Reflect.getMetadata?.("resource:mimeType", method) || "application/json";
|
|
1072
|
+
this.resources.set(methodMeta.resourceUri, {
|
|
1073
|
+
uri: methodMeta.resourceUri,
|
|
1074
|
+
name: methodMeta.resourceName || methodMeta.resourceUri,
|
|
1075
|
+
description: methodMeta.resourceDescription || "",
|
|
1076
|
+
mimeType,
|
|
1077
|
+
inputSchema,
|
|
1078
|
+
method,
|
|
1079
|
+
instance,
|
|
1080
|
+
propertyKey
|
|
1081
|
+
});
|
|
1082
|
+
if (this.logging) {
|
|
1083
|
+
this.logger.debug(`Registered resource: ${methodMeta.resourceUri}`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
943
1086
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
if (!resource) {
|
|
952
|
-
throw new Error(`Resource ${uri} not found`);
|
|
1087
|
+
/**
|
|
1088
|
+
* Get the underlying MCP SDK Server instance
|
|
1089
|
+
* Attaches waitForInit method for HTTP server initialization
|
|
1090
|
+
*/
|
|
1091
|
+
getServer() {
|
|
1092
|
+
this.server.waitForInit = () => this.waitForInit();
|
|
1093
|
+
return this.server;
|
|
953
1094
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
{
|
|
959
|
-
uri,
|
|
960
|
-
mimeType: resource.mimeType,
|
|
961
|
-
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
962
|
-
}
|
|
963
|
-
]
|
|
964
|
-
};
|
|
965
|
-
} catch (error) {
|
|
966
|
-
throw new Error(`Failed to read resource ${uri}: ${error.message}`);
|
|
1095
|
+
};
|
|
1096
|
+
MCPServerRuntime = class {
|
|
1097
|
+
static {
|
|
1098
|
+
__name(this, "MCPServerRuntime");
|
|
967
1099
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1100
|
+
server;
|
|
1101
|
+
tools = /* @__PURE__ */ new Map();
|
|
1102
|
+
prompts = /* @__PURE__ */ new Map();
|
|
1103
|
+
resources = /* @__PURE__ */ new Map();
|
|
1104
|
+
options;
|
|
1105
|
+
logger;
|
|
1106
|
+
constructor(options) {
|
|
1107
|
+
this.options = options;
|
|
1108
|
+
this.logger = new Logger({
|
|
1109
|
+
level: this.options.logging ? LogLevel.INFO : LogLevel.NONE,
|
|
1110
|
+
prefix: "MCPServerRuntime"
|
|
976
1111
|
});
|
|
1112
|
+
this.server = new import_server.Server({
|
|
1113
|
+
name: "leanmcp-server",
|
|
1114
|
+
version: "0.1.0"
|
|
1115
|
+
}, {
|
|
1116
|
+
capabilities: {
|
|
1117
|
+
tools: {},
|
|
1118
|
+
resources: {},
|
|
1119
|
+
prompts: {}
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
this.setupHandlers();
|
|
977
1123
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
try {
|
|
989
|
-
const result = await prompt.method.call(prompt.instance, request.params.arguments || {});
|
|
990
|
-
if (result && result.messages) {
|
|
991
|
-
return result;
|
|
992
|
-
}
|
|
993
|
-
return {
|
|
994
|
-
description: prompt.description,
|
|
995
|
-
messages: [
|
|
996
|
-
{
|
|
997
|
-
role: "user",
|
|
998
|
-
content: {
|
|
999
|
-
type: "text",
|
|
1000
|
-
text: typeof result === "string" ? result : JSON.stringify(result)
|
|
1124
|
+
setupHandlers() {
|
|
1125
|
+
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
1126
|
+
const tools = [];
|
|
1127
|
+
for (const [name, tool] of this.tools.entries()) {
|
|
1128
|
+
tools.push({
|
|
1129
|
+
name,
|
|
1130
|
+
description: tool.description,
|
|
1131
|
+
inputSchema: tool.inputSchema || {
|
|
1132
|
+
type: "object",
|
|
1133
|
+
properties: {}
|
|
1001
1134
|
}
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
tools
|
|
1139
|
+
};
|
|
1140
|
+
});
|
|
1141
|
+
this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
1142
|
+
const toolName = request.params.name;
|
|
1143
|
+
const tool = this.tools.get(toolName);
|
|
1144
|
+
if (!tool) {
|
|
1145
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
1146
|
+
}
|
|
1147
|
+
const methodMeta = getMethodMetadata(tool.method);
|
|
1148
|
+
if (methodMeta.inputSchema) {
|
|
1149
|
+
const validate = ajv.compile(methodMeta.inputSchema);
|
|
1150
|
+
const valid = validate(request.params.arguments || {});
|
|
1151
|
+
if (!valid) {
|
|
1152
|
+
throw new Error(`Input validation failed: ${JSON.stringify(validate.errors)}`);
|
|
1002
1153
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
async loadServices() {
|
|
1011
|
-
const absPath = import_path.default.resolve(this.options.servicesDir);
|
|
1012
|
-
if (!import_fs.default.existsSync(absPath)) {
|
|
1013
|
-
this.logger.error(`Services directory not found: ${absPath}`);
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
const files = import_fs.default.readdirSync(absPath);
|
|
1017
|
-
let toolCount = 0;
|
|
1018
|
-
let promptCount = 0;
|
|
1019
|
-
let resourceCount = 0;
|
|
1020
|
-
for (const dir of files) {
|
|
1021
|
-
const modulePath = import_path.default.join(absPath, dir, "index.ts");
|
|
1022
|
-
const modulePathJs = import_path.default.join(absPath, dir, "index.js");
|
|
1023
|
-
const finalPath = import_fs.default.existsSync(modulePath) ? modulePath : import_fs.default.existsSync(modulePathJs) ? modulePathJs : null;
|
|
1024
|
-
if (finalPath) {
|
|
1025
|
-
try {
|
|
1026
|
-
const fileUrl = (0, import_url.pathToFileURL)(finalPath).href;
|
|
1027
|
-
const mod = await import(fileUrl);
|
|
1028
|
-
const exportedClasses = Object.values(mod).filter((val) => typeof val === "function" && val.prototype);
|
|
1029
|
-
for (const cls of exportedClasses) {
|
|
1030
|
-
const instance = new cls();
|
|
1031
|
-
const envsPropKey = Reflect.getMetadata?.("userenvs:propertyKey", cls);
|
|
1032
|
-
if (envsPropKey) {
|
|
1033
|
-
instance[envsPropKey] = process.env;
|
|
1154
|
+
}
|
|
1155
|
+
if (methodMeta.authRequired) {
|
|
1156
|
+
if (this.options.logging) {
|
|
1157
|
+
this.logger.info(`Auth required for ${toolName} (provider: ${methodMeta.authProvider})`);
|
|
1034
1158
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
instance,
|
|
1050
|
-
propertyKey
|
|
1051
|
-
});
|
|
1052
|
-
toolCount++;
|
|
1053
|
-
if (this.options.logging) {
|
|
1054
|
-
this.logger.info(`Loaded tool: ${methodMeta.toolName}${inputClass ? " (class-based schema)" : ""}`);
|
|
1055
|
-
}
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
const meta = request.params._meta;
|
|
1162
|
+
const result = await tool.method.call(tool.instance, request.params.arguments, meta);
|
|
1163
|
+
if (result && typeof result === "object" && result.type === "elicitation") {
|
|
1164
|
+
return {
|
|
1165
|
+
content: [
|
|
1166
|
+
{
|
|
1167
|
+
type: "text",
|
|
1168
|
+
text: JSON.stringify(result, null, 2)
|
|
1169
|
+
}
|
|
1170
|
+
],
|
|
1171
|
+
isError: false
|
|
1172
|
+
};
|
|
1056
1173
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1174
|
+
let formattedResult = result;
|
|
1175
|
+
if (methodMeta.renderFormat === "markdown" && typeof result === "string") {
|
|
1176
|
+
formattedResult = result;
|
|
1177
|
+
} else if (methodMeta.renderFormat === "json" || typeof result === "object") {
|
|
1178
|
+
formattedResult = JSON.stringify(result, null, 2);
|
|
1179
|
+
} else {
|
|
1180
|
+
formattedResult = String(result);
|
|
1181
|
+
}
|
|
1182
|
+
return {
|
|
1183
|
+
content: [
|
|
1184
|
+
{
|
|
1185
|
+
type: "text",
|
|
1186
|
+
text: formattedResult
|
|
1187
|
+
}
|
|
1188
|
+
]
|
|
1189
|
+
};
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
return {
|
|
1192
|
+
content: [
|
|
1193
|
+
{
|
|
1194
|
+
type: "text",
|
|
1195
|
+
text: `Error: ${error.message}`
|
|
1196
|
+
}
|
|
1197
|
+
],
|
|
1198
|
+
isError: true
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
this.server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
|
|
1203
|
+
const resources = [];
|
|
1204
|
+
for (const [uri, resource] of this.resources.entries()) {
|
|
1205
|
+
resources.push({
|
|
1206
|
+
uri: resource.uri,
|
|
1207
|
+
name: resource.name,
|
|
1208
|
+
description: resource.description,
|
|
1209
|
+
mimeType: resource.mimeType
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
return {
|
|
1213
|
+
resources
|
|
1214
|
+
};
|
|
1215
|
+
});
|
|
1216
|
+
this.server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
|
|
1217
|
+
const uri = request.params.uri;
|
|
1218
|
+
const resource = this.resources.get(uri);
|
|
1219
|
+
if (!resource) {
|
|
1220
|
+
throw new Error(`Resource ${uri} not found`);
|
|
1221
|
+
}
|
|
1222
|
+
try {
|
|
1223
|
+
const result = await resource.method.call(resource.instance);
|
|
1224
|
+
return {
|
|
1225
|
+
contents: [
|
|
1226
|
+
{
|
|
1227
|
+
uri,
|
|
1228
|
+
mimeType: resource.mimeType,
|
|
1229
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
1230
|
+
}
|
|
1231
|
+
]
|
|
1232
|
+
};
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
throw new Error(`Failed to read resource ${uri}: ${error.message}`);
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
this.server.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
|
|
1238
|
+
const prompts = [];
|
|
1239
|
+
for (const [name, prompt] of this.prompts.entries()) {
|
|
1240
|
+
prompts.push({
|
|
1241
|
+
name,
|
|
1242
|
+
description: prompt.description,
|
|
1243
|
+
arguments: prompt.arguments
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
return {
|
|
1247
|
+
prompts
|
|
1248
|
+
};
|
|
1249
|
+
});
|
|
1250
|
+
this.server.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
|
|
1251
|
+
const promptName = request.params.name;
|
|
1252
|
+
const prompt = this.prompts.get(promptName);
|
|
1253
|
+
if (!prompt) {
|
|
1254
|
+
throw new Error(`Prompt ${promptName} not found`);
|
|
1255
|
+
}
|
|
1256
|
+
try {
|
|
1257
|
+
const result = await prompt.method.call(prompt.instance, request.params.arguments || {});
|
|
1258
|
+
if (result && result.messages) {
|
|
1259
|
+
return result;
|
|
1077
1260
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1261
|
+
return {
|
|
1262
|
+
description: prompt.description,
|
|
1263
|
+
messages: [
|
|
1264
|
+
{
|
|
1265
|
+
role: "user",
|
|
1266
|
+
content: {
|
|
1267
|
+
type: "text",
|
|
1268
|
+
text: typeof result === "string" ? result : JSON.stringify(result)
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
]
|
|
1272
|
+
};
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
throw new Error(`Failed to get prompt ${promptName}: ${error.message}`);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
async loadServices() {
|
|
1279
|
+
const absPath = import_path.default.resolve(this.options.servicesDir);
|
|
1280
|
+
if (!import_fs.default.existsSync(absPath)) {
|
|
1281
|
+
this.logger.error(`Services directory not found: ${absPath}`);
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
const files = import_fs.default.readdirSync(absPath);
|
|
1285
|
+
let toolCount = 0;
|
|
1286
|
+
let promptCount = 0;
|
|
1287
|
+
let resourceCount = 0;
|
|
1288
|
+
for (const dir of files) {
|
|
1289
|
+
const modulePath = import_path.default.join(absPath, dir, "index.ts");
|
|
1290
|
+
const modulePathJs = import_path.default.join(absPath, dir, "index.js");
|
|
1291
|
+
const finalPath = import_fs.default.existsSync(modulePath) ? modulePath : import_fs.default.existsSync(modulePathJs) ? modulePathJs : null;
|
|
1292
|
+
if (finalPath) {
|
|
1293
|
+
try {
|
|
1294
|
+
const fileUrl = (0, import_url.pathToFileURL)(finalPath).href;
|
|
1295
|
+
const mod = await import(fileUrl);
|
|
1296
|
+
const exportedClasses = Object.values(mod).filter((val) => typeof val === "function" && val.prototype);
|
|
1297
|
+
for (const cls of exportedClasses) {
|
|
1298
|
+
const instance = new cls();
|
|
1299
|
+
const envsPropKey = Reflect.getMetadata?.("userenvs:propertyKey", cls);
|
|
1300
|
+
if (envsPropKey) {
|
|
1301
|
+
instance[envsPropKey] = process.env;
|
|
1302
|
+
}
|
|
1303
|
+
const toolMethods = getDecoratedMethods(cls, "tool:name");
|
|
1304
|
+
for (const { method, propertyKey, metadata } of toolMethods) {
|
|
1305
|
+
const methodMeta = getMethodMetadata(method);
|
|
1306
|
+
const inputClass = Reflect.getMetadata?.("tool:inputClass", method);
|
|
1307
|
+
const outputClass = Reflect.getMetadata?.("tool:outputClass", method);
|
|
1308
|
+
let inputSchema = methodMeta.inputSchema;
|
|
1309
|
+
if (inputClass) {
|
|
1310
|
+
inputSchema = classToJsonSchemaWithConstraints(inputClass);
|
|
1311
|
+
}
|
|
1312
|
+
this.tools.set(methodMeta.toolName, {
|
|
1313
|
+
name: methodMeta.toolName,
|
|
1314
|
+
description: methodMeta.toolDescription || "",
|
|
1315
|
+
inputSchema,
|
|
1316
|
+
method,
|
|
1317
|
+
instance,
|
|
1318
|
+
propertyKey
|
|
1319
|
+
});
|
|
1320
|
+
toolCount++;
|
|
1321
|
+
if (this.options.logging) {
|
|
1322
|
+
this.logger.info(`Loaded tool: ${methodMeta.toolName}${inputClass ? " (class-based schema)" : ""}`);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
const promptMethods = getDecoratedMethods(cls, "prompt:name");
|
|
1326
|
+
for (const { method, propertyKey, metadata } of promptMethods) {
|
|
1327
|
+
const methodMeta = getMethodMetadata(method);
|
|
1328
|
+
const promptArgs = methodMeta.inputSchema?.properties ? Object.keys(methodMeta.inputSchema.properties).map((key) => ({
|
|
1329
|
+
name: key,
|
|
1330
|
+
description: methodMeta.inputSchema?.properties?.[key]?.description || "",
|
|
1331
|
+
required: methodMeta.inputSchema?.required?.includes(key) || false
|
|
1332
|
+
})) : [];
|
|
1333
|
+
this.prompts.set(methodMeta.promptName, {
|
|
1334
|
+
name: methodMeta.promptName,
|
|
1335
|
+
description: methodMeta.promptDescription || "",
|
|
1336
|
+
arguments: promptArgs,
|
|
1337
|
+
method,
|
|
1338
|
+
instance,
|
|
1339
|
+
propertyKey
|
|
1340
|
+
});
|
|
1341
|
+
promptCount++;
|
|
1342
|
+
if (this.options.logging) {
|
|
1343
|
+
this.logger.info(`Loaded prompt: ${methodMeta.promptName}`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
const resourceMethods = getDecoratedMethods(cls, "resource:uri");
|
|
1347
|
+
for (const { method, propertyKey, metadata } of resourceMethods) {
|
|
1348
|
+
const methodMeta = getMethodMetadata(method);
|
|
1349
|
+
this.resources.set(methodMeta.resourceUri, {
|
|
1350
|
+
uri: methodMeta.resourceUri,
|
|
1351
|
+
name: methodMeta.resourceName || methodMeta.resourceUri,
|
|
1352
|
+
description: methodMeta.resourceDescription || "",
|
|
1353
|
+
mimeType: "application/json",
|
|
1354
|
+
method,
|
|
1355
|
+
instance,
|
|
1356
|
+
propertyKey
|
|
1357
|
+
});
|
|
1358
|
+
resourceCount++;
|
|
1359
|
+
if (this.options.logging) {
|
|
1360
|
+
this.logger.info(`Loaded resource: ${methodMeta.resourceUri}`);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
this.logger.error(`Failed to load from ${dir}:`, error.message || error);
|
|
1091
1366
|
if (this.options.logging) {
|
|
1092
|
-
this.logger.
|
|
1367
|
+
this.logger.error("Full error:", error);
|
|
1093
1368
|
}
|
|
1094
1369
|
}
|
|
1095
1370
|
}
|
|
1096
|
-
} catch (error) {
|
|
1097
|
-
this.logger.error(`Failed to load from ${dir}:`, error.message || error);
|
|
1098
|
-
if (this.options.logging) {
|
|
1099
|
-
this.logger.error("Full error:", error);
|
|
1100
|
-
}
|
|
1101
1371
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
if (this.options.logging) {
|
|
1105
|
-
this.logger.info(`
|
|
1372
|
+
if (this.options.logging) {
|
|
1373
|
+
this.logger.info(`
|
|
1106
1374
|
Loaded ${toolCount} tools, ${promptCount} prompts, ${resourceCount} resources`);
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
async start() {
|
|
1378
|
+
await this.loadServices();
|
|
1379
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
1380
|
+
await this.server.connect(transport);
|
|
1381
|
+
if (this.options.logging) {
|
|
1382
|
+
this.logger.info("LeanMCP server running on stdio");
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
getServer() {
|
|
1386
|
+
return this.server;
|
|
1387
|
+
}
|
|
1388
|
+
getTools() {
|
|
1389
|
+
return Array.from(this.tools.values());
|
|
1390
|
+
}
|
|
1391
|
+
getPrompts() {
|
|
1392
|
+
return Array.from(this.prompts.values());
|
|
1393
|
+
}
|
|
1394
|
+
getResources() {
|
|
1395
|
+
return Array.from(this.resources.values());
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
__name(startMCPServer, "startMCPServer");
|
|
1128
1399
|
}
|
|
1129
|
-
};
|
|
1130
|
-
|
|
1131
|
-
const runtime = new MCPServerRuntime(options);
|
|
1132
|
-
await runtime.start();
|
|
1133
|
-
return runtime;
|
|
1134
|
-
}
|
|
1135
|
-
__name(startMCPServer, "startMCPServer");
|
|
1400
|
+
});
|
|
1401
|
+
init_index();
|
|
1136
1402
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1137
1403
|
0 && (module.exports = {
|
|
1138
1404
|
Auth,
|