@leanmcp/core 0.1.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/LICENSE +21 -0
- package/README.md +354 -0
- package/dist/chunk-O6YSETKJ.mjs +6 -0
- package/dist/dist-LQ4M5W3M.mjs +587 -0
- package/dist/index.d.mts +435 -0
- package/dist/index.d.ts +435 -0
- package/dist/index.js +1162 -0
- package/dist/index.mjs +1104 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
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
|
+
// src/decorators.ts
|
|
72
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
73
|
+
function Tool(options = {}) {
|
|
74
|
+
return (target, propertyKey, descriptor) => {
|
|
75
|
+
const toolName = String(propertyKey);
|
|
76
|
+
Reflect.defineMetadata("tool:name", toolName, descriptor.value);
|
|
77
|
+
Reflect.defineMetadata("tool:description", options.description || "", descriptor.value);
|
|
78
|
+
Reflect.defineMetadata("tool:propertyKey", propertyKey, descriptor.value);
|
|
79
|
+
if (options.inputClass) {
|
|
80
|
+
Reflect.defineMetadata("tool:inputClass", options.inputClass, descriptor.value);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
__name(Tool, "Tool");
|
|
85
|
+
function Prompt(options = {}) {
|
|
86
|
+
return (target, propertyKey, descriptor) => {
|
|
87
|
+
const promptName = String(propertyKey);
|
|
88
|
+
Reflect.defineMetadata("prompt:name", promptName, descriptor.value);
|
|
89
|
+
Reflect.defineMetadata("prompt:description", options.description || "", descriptor.value);
|
|
90
|
+
Reflect.defineMetadata("prompt:propertyKey", propertyKey, descriptor.value);
|
|
91
|
+
if (options.inputClass) {
|
|
92
|
+
Reflect.defineMetadata("prompt:inputClass", options.inputClass, descriptor.value);
|
|
93
|
+
} else {
|
|
94
|
+
const paramTypes = Reflect.getMetadata("design:paramtypes", target, propertyKey);
|
|
95
|
+
if (paramTypes && paramTypes.length > 0 && paramTypes[0] !== Object) {
|
|
96
|
+
Reflect.defineMetadata("prompt:inputClass", paramTypes[0], descriptor.value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
__name(Prompt, "Prompt");
|
|
102
|
+
function Resource(options = {}) {
|
|
103
|
+
return (target, propertyKey, descriptor) => {
|
|
104
|
+
const resourceName = String(propertyKey);
|
|
105
|
+
const className = target.constructor.name.toLowerCase().replace("service", "");
|
|
106
|
+
const resourceUri = `${className}://${resourceName}`;
|
|
107
|
+
Reflect.defineMetadata("resource:uri", resourceUri, descriptor.value);
|
|
108
|
+
Reflect.defineMetadata("resource:name", resourceName, descriptor.value);
|
|
109
|
+
Reflect.defineMetadata("resource:description", options.description || "", descriptor.value);
|
|
110
|
+
Reflect.defineMetadata("resource:mimeType", options.mimeType || "application/json", descriptor.value);
|
|
111
|
+
Reflect.defineMetadata("resource:propertyKey", propertyKey, descriptor.value);
|
|
112
|
+
if (options.inputClass) {
|
|
113
|
+
Reflect.defineMetadata("resource:inputClass", options.inputClass, descriptor.value);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
__name(Resource, "Resource");
|
|
118
|
+
function Auth(options) {
|
|
119
|
+
return (target, propertyKey, descriptor) => {
|
|
120
|
+
if (propertyKey && descriptor) {
|
|
121
|
+
Reflect.defineMetadata("auth:provider", options.provider, descriptor.value);
|
|
122
|
+
Reflect.defineMetadata("auth:required", true, descriptor.value);
|
|
123
|
+
} else {
|
|
124
|
+
Reflect.defineMetadata("auth:provider", options.provider, target);
|
|
125
|
+
Reflect.defineMetadata("auth:required", true, target);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
__name(Auth, "Auth");
|
|
130
|
+
function UserEnvs() {
|
|
131
|
+
return (target, propertyKey) => {
|
|
132
|
+
const constructor = target.constructor;
|
|
133
|
+
Reflect.defineMetadata("userenvs:propertyKey", propertyKey, constructor);
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
__name(UserEnvs, "UserEnvs");
|
|
137
|
+
function UI(component) {
|
|
138
|
+
return (target, propertyKey, descriptor) => {
|
|
139
|
+
if (propertyKey && descriptor) {
|
|
140
|
+
Reflect.defineMetadata("ui:component", component, descriptor.value);
|
|
141
|
+
} else {
|
|
142
|
+
Reflect.defineMetadata("ui:component", component, target);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
__name(UI, "UI");
|
|
147
|
+
function Render(format) {
|
|
148
|
+
return (target, propertyKey, descriptor) => {
|
|
149
|
+
Reflect.defineMetadata("render:format", format, descriptor.value);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
__name(Render, "Render");
|
|
153
|
+
function Deprecated(message) {
|
|
154
|
+
return (target, propertyKey, descriptor) => {
|
|
155
|
+
const deprecationMessage = message || "This feature is deprecated";
|
|
156
|
+
if (propertyKey && descriptor) {
|
|
157
|
+
Reflect.defineMetadata("deprecated:message", deprecationMessage, descriptor.value);
|
|
158
|
+
Reflect.defineMetadata("deprecated:true", true, descriptor.value);
|
|
159
|
+
const originalMethod = descriptor.value;
|
|
160
|
+
descriptor.value = function(...args) {
|
|
161
|
+
console.warn(`DEPRECATED: ${String(propertyKey)} - ${deprecationMessage}`);
|
|
162
|
+
return originalMethod.apply(this, args);
|
|
163
|
+
};
|
|
164
|
+
} else {
|
|
165
|
+
Reflect.defineMetadata("deprecated:message", deprecationMessage, target);
|
|
166
|
+
Reflect.defineMetadata("deprecated:true", true, target);
|
|
167
|
+
console.warn(`DEPRECATED: ${target.name} - ${deprecationMessage}`);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
__name(Deprecated, "Deprecated");
|
|
172
|
+
function getMethodMetadata(method) {
|
|
173
|
+
return {
|
|
174
|
+
// Tool metadata
|
|
175
|
+
toolName: Reflect.getMetadata("tool:name", method),
|
|
176
|
+
toolDescription: Reflect.getMetadata("tool:description", method),
|
|
177
|
+
// Prompt metadata
|
|
178
|
+
promptName: Reflect.getMetadata("prompt:name", method),
|
|
179
|
+
promptDescription: Reflect.getMetadata("prompt:description", method),
|
|
180
|
+
// Resource metadata
|
|
181
|
+
resourceUri: Reflect.getMetadata("resource:uri", method),
|
|
182
|
+
resourceName: Reflect.getMetadata("resource:name", method),
|
|
183
|
+
resourceDescription: Reflect.getMetadata("resource:description", method),
|
|
184
|
+
// Common metadata
|
|
185
|
+
inputSchema: Reflect.getMetadata("schema:input", method),
|
|
186
|
+
outputSchema: Reflect.getMetadata("schema:output", method),
|
|
187
|
+
authProvider: Reflect.getMetadata("auth:provider", method),
|
|
188
|
+
authRequired: Reflect.getMetadata("auth:required", method),
|
|
189
|
+
uiComponent: Reflect.getMetadata("ui:component", method),
|
|
190
|
+
renderFormat: Reflect.getMetadata("render:format", method),
|
|
191
|
+
deprecated: Reflect.getMetadata("deprecated:true", method),
|
|
192
|
+
deprecationMessage: Reflect.getMetadata("deprecated:message", method)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
__name(getMethodMetadata, "getMethodMetadata");
|
|
196
|
+
function getDecoratedMethods(target, metadataKey) {
|
|
197
|
+
const methods = [];
|
|
198
|
+
const prototype = target.prototype || target;
|
|
199
|
+
for (const propertyKey of Object.getOwnPropertyNames(prototype)) {
|
|
200
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, propertyKey);
|
|
201
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
202
|
+
const metadata = Reflect.getMetadata(metadataKey, descriptor.value);
|
|
203
|
+
if (metadata !== void 0) {
|
|
204
|
+
methods.push({
|
|
205
|
+
method: descriptor.value,
|
|
206
|
+
propertyKey,
|
|
207
|
+
metadata
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return methods;
|
|
213
|
+
}
|
|
214
|
+
__name(getDecoratedMethods, "getDecoratedMethods");
|
|
215
|
+
|
|
216
|
+
// src/schema-generator.ts
|
|
217
|
+
var import_reflect_metadata2 = require("reflect-metadata");
|
|
218
|
+
function classToJsonSchema(classConstructor) {
|
|
219
|
+
const instance = new classConstructor();
|
|
220
|
+
const properties = {};
|
|
221
|
+
const required = [];
|
|
222
|
+
const propertyNames = Object.keys(instance);
|
|
223
|
+
for (const propertyName of propertyNames) {
|
|
224
|
+
const propertyType = Reflect.getMetadata("design:type", instance, propertyName);
|
|
225
|
+
let jsonSchemaType = "any";
|
|
226
|
+
if (propertyType) {
|
|
227
|
+
switch (propertyType.name) {
|
|
228
|
+
case "String":
|
|
229
|
+
jsonSchemaType = "string";
|
|
230
|
+
break;
|
|
231
|
+
case "Number":
|
|
232
|
+
jsonSchemaType = "number";
|
|
233
|
+
break;
|
|
234
|
+
case "Boolean":
|
|
235
|
+
jsonSchemaType = "boolean";
|
|
236
|
+
break;
|
|
237
|
+
case "Array":
|
|
238
|
+
jsonSchemaType = "array";
|
|
239
|
+
break;
|
|
240
|
+
case "Object":
|
|
241
|
+
jsonSchemaType = "object";
|
|
242
|
+
break;
|
|
243
|
+
default:
|
|
244
|
+
jsonSchemaType = "object";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
properties[propertyName] = {
|
|
248
|
+
type: jsonSchemaType
|
|
249
|
+
};
|
|
250
|
+
const descriptor = Object.getOwnPropertyDescriptor(instance, propertyName);
|
|
251
|
+
if (descriptor && descriptor.value === void 0) {
|
|
252
|
+
const isOptional = propertyName.endsWith("?") || Reflect.getMetadata("optional", instance, propertyName);
|
|
253
|
+
if (!isOptional) {
|
|
254
|
+
required.push(propertyName);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
type: "object",
|
|
260
|
+
properties,
|
|
261
|
+
required: required.length > 0 ? required : void 0
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
__name(classToJsonSchema, "classToJsonSchema");
|
|
265
|
+
function Optional() {
|
|
266
|
+
return (target, propertyKey) => {
|
|
267
|
+
Reflect.defineMetadata("optional", true, target, propertyKey);
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
__name(Optional, "Optional");
|
|
271
|
+
function SchemaConstraint(constraints) {
|
|
272
|
+
return (target, propertyKey) => {
|
|
273
|
+
Reflect.defineMetadata("schema:constraints", constraints, target, propertyKey);
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
__name(SchemaConstraint, "SchemaConstraint");
|
|
277
|
+
function classToJsonSchemaWithConstraints(classConstructor) {
|
|
278
|
+
const instance = new classConstructor();
|
|
279
|
+
const properties = {};
|
|
280
|
+
const required = [];
|
|
281
|
+
const propertyNames = Object.keys(instance);
|
|
282
|
+
for (const propertyName of propertyNames) {
|
|
283
|
+
const propertyType = Reflect.getMetadata("design:type", instance, propertyName);
|
|
284
|
+
const constraints = Reflect.getMetadata("schema:constraints", instance, propertyName);
|
|
285
|
+
const isOptional = Reflect.getMetadata("optional", instance, propertyName);
|
|
286
|
+
let jsonSchemaType = "string";
|
|
287
|
+
if (propertyType) {
|
|
288
|
+
switch (propertyType.name) {
|
|
289
|
+
case "String":
|
|
290
|
+
jsonSchemaType = "string";
|
|
291
|
+
break;
|
|
292
|
+
case "Number":
|
|
293
|
+
jsonSchemaType = "number";
|
|
294
|
+
break;
|
|
295
|
+
case "Boolean":
|
|
296
|
+
jsonSchemaType = "boolean";
|
|
297
|
+
break;
|
|
298
|
+
case "Array":
|
|
299
|
+
jsonSchemaType = "array";
|
|
300
|
+
break;
|
|
301
|
+
case "Object":
|
|
302
|
+
jsonSchemaType = "object";
|
|
303
|
+
break;
|
|
304
|
+
default:
|
|
305
|
+
jsonSchemaType = "object";
|
|
306
|
+
}
|
|
307
|
+
} else if (constraints) {
|
|
308
|
+
if (constraints.minLength !== void 0 || constraints.maxLength !== void 0 || constraints.pattern) {
|
|
309
|
+
jsonSchemaType = "string";
|
|
310
|
+
} else if (constraints.minimum !== void 0 || constraints.maximum !== void 0) {
|
|
311
|
+
jsonSchemaType = "number";
|
|
312
|
+
} else if (constraints.enum && constraints.enum.length > 0) {
|
|
313
|
+
const firstValue = constraints.enum[0];
|
|
314
|
+
if (typeof firstValue === "number") {
|
|
315
|
+
jsonSchemaType = "number";
|
|
316
|
+
} else if (typeof firstValue === "boolean") {
|
|
317
|
+
jsonSchemaType = "boolean";
|
|
318
|
+
} else {
|
|
319
|
+
jsonSchemaType = "string";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
properties[propertyName] = {
|
|
324
|
+
type: jsonSchemaType,
|
|
325
|
+
...constraints || {}
|
|
326
|
+
};
|
|
327
|
+
if (!isOptional) {
|
|
328
|
+
required.push(propertyName);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
type: "object",
|
|
333
|
+
properties,
|
|
334
|
+
required: required.length > 0 ? required : void 0
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
__name(classToJsonSchemaWithConstraints, "classToJsonSchemaWithConstraints");
|
|
338
|
+
|
|
339
|
+
// src/http-server.ts
|
|
340
|
+
var import_node_crypto = require("crypto");
|
|
341
|
+
|
|
342
|
+
// src/logger.ts
|
|
343
|
+
var LogLevel = /* @__PURE__ */ (function(LogLevel2) {
|
|
344
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
345
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
346
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
347
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
348
|
+
LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
|
|
349
|
+
return LogLevel2;
|
|
350
|
+
})({});
|
|
351
|
+
var Logger = class {
|
|
352
|
+
static {
|
|
353
|
+
__name(this, "Logger");
|
|
354
|
+
}
|
|
355
|
+
level;
|
|
356
|
+
prefix;
|
|
357
|
+
timestamps;
|
|
358
|
+
constructor(options = {}) {
|
|
359
|
+
this.level = options.level ?? 1;
|
|
360
|
+
this.prefix = options.prefix ?? "";
|
|
361
|
+
this.timestamps = options.timestamps ?? true;
|
|
362
|
+
}
|
|
363
|
+
format(level, message, ...args) {
|
|
364
|
+
const timestamp = this.timestamps ? `[${(/* @__PURE__ */ new Date()).toISOString()}]` : "";
|
|
365
|
+
const prefix = this.prefix ? `[${this.prefix}]` : "";
|
|
366
|
+
return `${timestamp}${prefix}[${level}] ${message}`;
|
|
367
|
+
}
|
|
368
|
+
shouldLog(level) {
|
|
369
|
+
return level >= this.level;
|
|
370
|
+
}
|
|
371
|
+
debug(message, ...args) {
|
|
372
|
+
if (this.shouldLog(0)) {
|
|
373
|
+
console.debug(this.format("DEBUG", message), ...args);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
info(message, ...args) {
|
|
377
|
+
if (this.shouldLog(1)) {
|
|
378
|
+
console.info(this.format("INFO", message), ...args);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
warn(message, ...args) {
|
|
382
|
+
if (this.shouldLog(2)) {
|
|
383
|
+
console.warn(this.format("WARN", message), ...args);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
error(message, ...args) {
|
|
387
|
+
if (this.shouldLog(3)) {
|
|
388
|
+
console.error(this.format("ERROR", message), ...args);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
setLevel(level) {
|
|
392
|
+
this.level = level;
|
|
393
|
+
}
|
|
394
|
+
getLevel() {
|
|
395
|
+
return this.level;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
var defaultLogger = new Logger({
|
|
399
|
+
level: 1,
|
|
400
|
+
prefix: "LeanMCP"
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// src/validation.ts
|
|
404
|
+
function validatePort(port) {
|
|
405
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
406
|
+
throw new Error(`Invalid port: ${port}. Must be an integer between 1-65535`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
__name(validatePort, "validatePort");
|
|
410
|
+
function validatePath(path2) {
|
|
411
|
+
if (path2.includes("..") || path2.includes("~")) {
|
|
412
|
+
throw new Error(`Invalid path: ${path2}. Path traversal patterns are not allowed`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
__name(validatePath, "validatePath");
|
|
416
|
+
function validateServiceName(name) {
|
|
417
|
+
const validNamePattern = /^[a-zA-Z0-9_-]+$/;
|
|
418
|
+
if (!validNamePattern.test(name)) {
|
|
419
|
+
throw new Error(`Invalid service name: ${name}. Service names must contain only alphanumeric characters, hyphens, and underscores`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
__name(validateServiceName, "validateServiceName");
|
|
423
|
+
function validateNonEmpty(value, fieldName) {
|
|
424
|
+
if (!value || value.trim().length === 0) {
|
|
425
|
+
throw new Error(`${fieldName} cannot be empty`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
__name(validateNonEmpty, "validateNonEmpty");
|
|
429
|
+
function validateUrl(url, allowedProtocols = [
|
|
430
|
+
"http:",
|
|
431
|
+
"https:"
|
|
432
|
+
]) {
|
|
433
|
+
try {
|
|
434
|
+
const parsed = new URL(url);
|
|
435
|
+
if (!allowedProtocols.includes(parsed.protocol)) {
|
|
436
|
+
throw new Error(`Invalid URL protocol: ${parsed.protocol}. Allowed protocols: ${allowedProtocols.join(", ")}`);
|
|
437
|
+
}
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (error instanceof TypeError) {
|
|
440
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
441
|
+
}
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
__name(validateUrl, "validateUrl");
|
|
446
|
+
|
|
447
|
+
// src/http-server.ts
|
|
448
|
+
function isInitializeRequest(body) {
|
|
449
|
+
return body && body.method === "initialize";
|
|
450
|
+
}
|
|
451
|
+
__name(isInitializeRequest, "isInitializeRequest");
|
|
452
|
+
async function createHTTPServer(serverFactory, options = {}) {
|
|
453
|
+
const [express, { StreamableHTTPServerTransport }, cors] = await Promise.all([
|
|
454
|
+
// @ts-ignore
|
|
455
|
+
import("express").catch(() => {
|
|
456
|
+
throw new Error("Express not found. Install with: npm install express @types/express");
|
|
457
|
+
}),
|
|
458
|
+
// @ts-ignore
|
|
459
|
+
import("@modelcontextprotocol/sdk/server/streamableHttp.js").catch(() => {
|
|
460
|
+
throw new Error("MCP SDK not found. Install with: npm install @modelcontextprotocol/sdk");
|
|
461
|
+
}),
|
|
462
|
+
// @ts-ignore
|
|
463
|
+
options.cors ? import("cors").catch(() => null) : Promise.resolve(null)
|
|
464
|
+
]);
|
|
465
|
+
const app = express.default();
|
|
466
|
+
const port = options.port || 3001;
|
|
467
|
+
validatePort(port);
|
|
468
|
+
const transports = {};
|
|
469
|
+
const logger = options.logger || new Logger({
|
|
470
|
+
level: options.logging ? LogLevel.INFO : LogLevel.NONE,
|
|
471
|
+
prefix: "HTTP"
|
|
472
|
+
});
|
|
473
|
+
if (cors && options.cors) {
|
|
474
|
+
const corsOptions = typeof options.cors === "object" ? {
|
|
475
|
+
origin: options.cors.origin || false,
|
|
476
|
+
methods: [
|
|
477
|
+
"GET",
|
|
478
|
+
"POST",
|
|
479
|
+
"DELETE",
|
|
480
|
+
"OPTIONS"
|
|
481
|
+
],
|
|
482
|
+
allowedHeaders: [
|
|
483
|
+
"Content-Type",
|
|
484
|
+
"mcp-session-id",
|
|
485
|
+
"mcp-protocol-version",
|
|
486
|
+
"Authorization"
|
|
487
|
+
],
|
|
488
|
+
exposedHeaders: [
|
|
489
|
+
"mcp-session-id"
|
|
490
|
+
],
|
|
491
|
+
credentials: options.cors.credentials ?? false,
|
|
492
|
+
maxAge: 86400
|
|
493
|
+
} : false;
|
|
494
|
+
if (corsOptions) {
|
|
495
|
+
app.use(cors.default(corsOptions));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
app.use(express.json());
|
|
499
|
+
logger.info("Starting LeanMCP HTTP Server...");
|
|
500
|
+
app.get("/health", (req, res) => {
|
|
501
|
+
res.json({
|
|
502
|
+
status: "ok",
|
|
503
|
+
activeSessions: Object.keys(transports).length,
|
|
504
|
+
uptime: process.uptime()
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
const handleMCPRequest = /* @__PURE__ */ __name(async (req, res) => {
|
|
508
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
509
|
+
let transport;
|
|
510
|
+
try {
|
|
511
|
+
if (sessionId && transports[sessionId]) {
|
|
512
|
+
transport = transports[sessionId];
|
|
513
|
+
logger.debug(`Reusing session: ${sessionId}`);
|
|
514
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
515
|
+
logger.info("Creating new MCP session...");
|
|
516
|
+
transport = new StreamableHTTPServerTransport({
|
|
517
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => (0, import_node_crypto.randomUUID)(), "sessionIdGenerator"),
|
|
518
|
+
onsessioninitialized: /* @__PURE__ */ __name((newSessionId) => {
|
|
519
|
+
transports[newSessionId] = transport;
|
|
520
|
+
logger.info(`Session initialized: ${newSessionId}`);
|
|
521
|
+
}, "onsessioninitialized")
|
|
522
|
+
});
|
|
523
|
+
transport.onclose = () => {
|
|
524
|
+
if (transport.sessionId) {
|
|
525
|
+
delete transports[transport.sessionId];
|
|
526
|
+
logger.debug(`Session cleaned up: ${transport.sessionId}`);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
const server = await serverFactory();
|
|
530
|
+
await server.connect(transport);
|
|
531
|
+
} else {
|
|
532
|
+
res.status(400).json({
|
|
533
|
+
jsonrpc: "2.0",
|
|
534
|
+
error: {
|
|
535
|
+
code: -32e3,
|
|
536
|
+
message: "Bad Request: Invalid session or not an init request"
|
|
537
|
+
},
|
|
538
|
+
id: null
|
|
539
|
+
});
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
await transport.handleRequest(req, res, req.body);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
logger.error("Error handling MCP request:", error);
|
|
545
|
+
if (!res.headersSent) {
|
|
546
|
+
res.status(500).json({
|
|
547
|
+
jsonrpc: "2.0",
|
|
548
|
+
error: {
|
|
549
|
+
code: -32603,
|
|
550
|
+
message: "Internal server error"
|
|
551
|
+
},
|
|
552
|
+
id: null
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}, "handleMCPRequest");
|
|
557
|
+
app.post("/mcp", handleMCPRequest);
|
|
558
|
+
app.delete("/mcp", handleMCPRequest);
|
|
559
|
+
process.on("SIGINT", () => {
|
|
560
|
+
logger.info("\nShutting down server...");
|
|
561
|
+
Object.values(transports).forEach((t) => t.close?.());
|
|
562
|
+
process.exit(0);
|
|
563
|
+
});
|
|
564
|
+
return new Promise((resolve) => {
|
|
565
|
+
app.listen(port, () => {
|
|
566
|
+
logger.info(`Server running on http://localhost:${port}`);
|
|
567
|
+
logger.info(`MCP endpoint: http://localhost:${port}/mcp`);
|
|
568
|
+
logger.info(`Health check: http://localhost:${port}/health`);
|
|
569
|
+
resolve();
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
__name(createHTTPServer, "createHTTPServer");
|
|
574
|
+
|
|
575
|
+
// src/index.ts
|
|
576
|
+
var ajv = new import_ajv.default();
|
|
577
|
+
var MCPServer = class {
|
|
578
|
+
static {
|
|
579
|
+
__name(this, "MCPServer");
|
|
580
|
+
}
|
|
581
|
+
server;
|
|
582
|
+
tools = /* @__PURE__ */ new Map();
|
|
583
|
+
prompts = /* @__PURE__ */ new Map();
|
|
584
|
+
resources = /* @__PURE__ */ new Map();
|
|
585
|
+
logging;
|
|
586
|
+
logger;
|
|
587
|
+
constructor(options) {
|
|
588
|
+
this.logging = options.logging || false;
|
|
589
|
+
this.logger = new Logger({
|
|
590
|
+
level: this.logging ? LogLevel.INFO : LogLevel.NONE,
|
|
591
|
+
prefix: "MCPServer"
|
|
592
|
+
});
|
|
593
|
+
this.server = new import_server.Server({
|
|
594
|
+
name: options.name,
|
|
595
|
+
version: options.version
|
|
596
|
+
}, {
|
|
597
|
+
capabilities: {
|
|
598
|
+
tools: {},
|
|
599
|
+
resources: {},
|
|
600
|
+
prompts: {}
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
this.setupHandlers();
|
|
604
|
+
}
|
|
605
|
+
setupHandlers() {
|
|
606
|
+
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
607
|
+
const tools = [];
|
|
608
|
+
for (const [name, tool] of this.tools.entries()) {
|
|
609
|
+
tools.push({
|
|
610
|
+
name,
|
|
611
|
+
description: tool.description,
|
|
612
|
+
inputSchema: tool.inputSchema || {
|
|
613
|
+
type: "object",
|
|
614
|
+
properties: {}
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return {
|
|
619
|
+
tools
|
|
620
|
+
};
|
|
621
|
+
});
|
|
622
|
+
this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
623
|
+
const toolName = request.params.name;
|
|
624
|
+
const tool = this.tools.get(toolName);
|
|
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)}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
const result = await tool.method.call(tool.instance, request.params.arguments);
|
|
638
|
+
let formattedResult = result;
|
|
639
|
+
if (methodMeta.renderFormat === "markdown" && typeof result === "string") {
|
|
640
|
+
formattedResult = result;
|
|
641
|
+
} else if (methodMeta.renderFormat === "json" || typeof result === "object") {
|
|
642
|
+
formattedResult = JSON.stringify(result, null, 2);
|
|
643
|
+
} else {
|
|
644
|
+
formattedResult = String(result);
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
content: [
|
|
648
|
+
{
|
|
649
|
+
type: "text",
|
|
650
|
+
text: formattedResult
|
|
651
|
+
}
|
|
652
|
+
]
|
|
653
|
+
};
|
|
654
|
+
} catch (error) {
|
|
655
|
+
return {
|
|
656
|
+
content: [
|
|
657
|
+
{
|
|
658
|
+
type: "text",
|
|
659
|
+
text: `Error: ${error.message}`
|
|
660
|
+
}
|
|
661
|
+
],
|
|
662
|
+
isError: true
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
this.server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
|
|
667
|
+
const resources = [];
|
|
668
|
+
for (const [uri, resource] of this.resources.entries()) {
|
|
669
|
+
const resourceInfo = {
|
|
670
|
+
uri: resource.uri,
|
|
671
|
+
name: resource.name,
|
|
672
|
+
description: resource.description,
|
|
673
|
+
mimeType: resource.mimeType
|
|
674
|
+
};
|
|
675
|
+
if (resource.inputSchema) {
|
|
676
|
+
resourceInfo.inputSchema = resource.inputSchema;
|
|
677
|
+
}
|
|
678
|
+
resources.push(resourceInfo);
|
|
679
|
+
}
|
|
680
|
+
return {
|
|
681
|
+
resources
|
|
682
|
+
};
|
|
683
|
+
});
|
|
684
|
+
this.server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
|
|
685
|
+
const uri = request.params.uri;
|
|
686
|
+
const resource = this.resources.get(uri);
|
|
687
|
+
if (!resource) {
|
|
688
|
+
throw new Error(`Resource ${uri} not found`);
|
|
689
|
+
}
|
|
690
|
+
try {
|
|
691
|
+
const result = await resource.method.call(resource.instance);
|
|
692
|
+
return {
|
|
693
|
+
contents: [
|
|
694
|
+
{
|
|
695
|
+
uri,
|
|
696
|
+
mimeType: resource.mimeType,
|
|
697
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
698
|
+
}
|
|
699
|
+
]
|
|
700
|
+
};
|
|
701
|
+
} catch (error) {
|
|
702
|
+
throw new Error(`Failed to read resource ${uri}: ${error.message}`);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
this.server.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
|
|
706
|
+
const prompts = [];
|
|
707
|
+
for (const [name, prompt] of this.prompts.entries()) {
|
|
708
|
+
prompts.push({
|
|
709
|
+
name,
|
|
710
|
+
description: prompt.description,
|
|
711
|
+
arguments: prompt.arguments
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
prompts
|
|
716
|
+
};
|
|
717
|
+
});
|
|
718
|
+
this.server.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
|
|
719
|
+
const promptName = request.params.name;
|
|
720
|
+
const prompt = this.prompts.get(promptName);
|
|
721
|
+
if (!prompt) {
|
|
722
|
+
throw new Error(`Prompt ${promptName} not found`);
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
const result = await prompt.method.call(prompt.instance, request.params.arguments || {});
|
|
726
|
+
if (result && result.messages) {
|
|
727
|
+
return result;
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
description: prompt.description,
|
|
731
|
+
messages: [
|
|
732
|
+
{
|
|
733
|
+
role: "user",
|
|
734
|
+
content: {
|
|
735
|
+
type: "text",
|
|
736
|
+
text: typeof result === "string" ? result : JSON.stringify(result)
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
]
|
|
740
|
+
};
|
|
741
|
+
} catch (error) {
|
|
742
|
+
throw new Error(`Failed to get prompt ${promptName}: ${error.message}`);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Register a service instance with decorated methods
|
|
748
|
+
*/
|
|
749
|
+
registerService(instance) {
|
|
750
|
+
const cls = instance.constructor;
|
|
751
|
+
const toolMethods = getDecoratedMethods(cls, "tool:name");
|
|
752
|
+
for (const { method, propertyKey } of toolMethods) {
|
|
753
|
+
const methodMeta = getMethodMetadata(method);
|
|
754
|
+
const inputClass = Reflect.getMetadata?.("tool:inputClass", method);
|
|
755
|
+
let inputSchema = methodMeta.inputSchema;
|
|
756
|
+
if (inputClass) {
|
|
757
|
+
inputSchema = classToJsonSchemaWithConstraints(inputClass);
|
|
758
|
+
}
|
|
759
|
+
this.tools.set(methodMeta.toolName, {
|
|
760
|
+
name: methodMeta.toolName,
|
|
761
|
+
description: methodMeta.toolDescription || "",
|
|
762
|
+
inputSchema,
|
|
763
|
+
method,
|
|
764
|
+
instance,
|
|
765
|
+
propertyKey
|
|
766
|
+
});
|
|
767
|
+
if (this.logging) {
|
|
768
|
+
this.logger.info(`Registered tool: ${methodMeta.toolName}${inputClass ? " (class-based schema)" : ""}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const promptMethods = getDecoratedMethods(cls, "prompt:name");
|
|
772
|
+
for (const { method, propertyKey } of promptMethods) {
|
|
773
|
+
const methodMeta = getMethodMetadata(method);
|
|
774
|
+
const inputClass = Reflect.getMetadata?.("prompt:inputClass", method);
|
|
775
|
+
let inputSchema = methodMeta.inputSchema;
|
|
776
|
+
if (inputClass) {
|
|
777
|
+
inputSchema = classToJsonSchemaWithConstraints(inputClass);
|
|
778
|
+
}
|
|
779
|
+
const promptArgs = inputSchema?.properties ? Object.keys(inputSchema.properties).map((key) => ({
|
|
780
|
+
name: key,
|
|
781
|
+
description: inputSchema?.properties?.[key]?.description || "",
|
|
782
|
+
required: inputSchema?.required?.includes(key) || false
|
|
783
|
+
})) : [];
|
|
784
|
+
this.prompts.set(methodMeta.promptName, {
|
|
785
|
+
name: methodMeta.promptName,
|
|
786
|
+
description: methodMeta.promptDescription || "",
|
|
787
|
+
arguments: promptArgs,
|
|
788
|
+
method,
|
|
789
|
+
instance,
|
|
790
|
+
propertyKey
|
|
791
|
+
});
|
|
792
|
+
if (this.logging) {
|
|
793
|
+
this.logger.info(`Registered prompt: ${methodMeta.promptName}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const resourceMethods = getDecoratedMethods(cls, "resource:uri");
|
|
797
|
+
for (const { method, propertyKey } of resourceMethods) {
|
|
798
|
+
const methodMeta = getMethodMetadata(method);
|
|
799
|
+
const inputClass = Reflect.getMetadata?.("resource:inputClass", method);
|
|
800
|
+
let inputSchema = methodMeta.inputSchema;
|
|
801
|
+
if (inputClass) {
|
|
802
|
+
inputSchema = classToJsonSchemaWithConstraints(inputClass);
|
|
803
|
+
}
|
|
804
|
+
const mimeType = Reflect.getMetadata?.("resource:mimeType", method) || "application/json";
|
|
805
|
+
this.resources.set(methodMeta.resourceUri, {
|
|
806
|
+
uri: methodMeta.resourceUri,
|
|
807
|
+
name: methodMeta.resourceName || methodMeta.resourceUri,
|
|
808
|
+
description: methodMeta.resourceDescription || "",
|
|
809
|
+
mimeType,
|
|
810
|
+
inputSchema,
|
|
811
|
+
method,
|
|
812
|
+
instance,
|
|
813
|
+
propertyKey
|
|
814
|
+
});
|
|
815
|
+
if (this.logging) {
|
|
816
|
+
this.logger.info(`Registered resource: ${methodMeta.resourceUri}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Get the underlying MCP SDK Server instance
|
|
822
|
+
*/
|
|
823
|
+
getServer() {
|
|
824
|
+
return this.server;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
var MCPServerRuntime = class {
|
|
828
|
+
static {
|
|
829
|
+
__name(this, "MCPServerRuntime");
|
|
830
|
+
}
|
|
831
|
+
server;
|
|
832
|
+
tools = /* @__PURE__ */ new Map();
|
|
833
|
+
prompts = /* @__PURE__ */ new Map();
|
|
834
|
+
resources = /* @__PURE__ */ new Map();
|
|
835
|
+
options;
|
|
836
|
+
logger;
|
|
837
|
+
constructor(options) {
|
|
838
|
+
this.options = options;
|
|
839
|
+
this.logger = new Logger({
|
|
840
|
+
level: this.options.logging ? LogLevel.INFO : LogLevel.NONE,
|
|
841
|
+
prefix: "MCPServerRuntime"
|
|
842
|
+
});
|
|
843
|
+
this.server = new import_server.Server({
|
|
844
|
+
name: "leanmcp-server",
|
|
845
|
+
version: "0.1.0"
|
|
846
|
+
}, {
|
|
847
|
+
capabilities: {
|
|
848
|
+
tools: {},
|
|
849
|
+
resources: {},
|
|
850
|
+
prompts: {}
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
this.setupHandlers();
|
|
854
|
+
}
|
|
855
|
+
setupHandlers() {
|
|
856
|
+
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
|
|
857
|
+
const tools = [];
|
|
858
|
+
for (const [name, tool] of this.tools.entries()) {
|
|
859
|
+
tools.push({
|
|
860
|
+
name,
|
|
861
|
+
description: tool.description,
|
|
862
|
+
inputSchema: tool.inputSchema || {
|
|
863
|
+
type: "object",
|
|
864
|
+
properties: {}
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
return {
|
|
869
|
+
tools
|
|
870
|
+
};
|
|
871
|
+
});
|
|
872
|
+
this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
873
|
+
const toolName = request.params.name;
|
|
874
|
+
const tool = this.tools.get(toolName);
|
|
875
|
+
if (!tool) {
|
|
876
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
877
|
+
}
|
|
878
|
+
const methodMeta = getMethodMetadata(tool.method);
|
|
879
|
+
if (methodMeta.inputSchema) {
|
|
880
|
+
const validate = ajv.compile(methodMeta.inputSchema);
|
|
881
|
+
const valid = validate(request.params.arguments || {});
|
|
882
|
+
if (!valid) {
|
|
883
|
+
throw new Error(`Input validation failed: ${JSON.stringify(validate.errors)}`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (methodMeta.authRequired) {
|
|
887
|
+
if (this.options.logging) {
|
|
888
|
+
this.logger.info(`Auth required for ${toolName} (provider: ${methodMeta.authProvider})`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
try {
|
|
892
|
+
const result = await tool.method.call(tool.instance, request.params.arguments);
|
|
893
|
+
if (result && typeof result === "object" && result.type === "elicitation") {
|
|
894
|
+
return {
|
|
895
|
+
content: [
|
|
896
|
+
{
|
|
897
|
+
type: "text",
|
|
898
|
+
text: JSON.stringify(result, null, 2)
|
|
899
|
+
}
|
|
900
|
+
],
|
|
901
|
+
isError: false
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
let formattedResult = result;
|
|
905
|
+
if (methodMeta.renderFormat === "markdown" && typeof result === "string") {
|
|
906
|
+
formattedResult = result;
|
|
907
|
+
} else if (methodMeta.renderFormat === "json" || typeof result === "object") {
|
|
908
|
+
formattedResult = JSON.stringify(result, null, 2);
|
|
909
|
+
} else {
|
|
910
|
+
formattedResult = String(result);
|
|
911
|
+
}
|
|
912
|
+
return {
|
|
913
|
+
content: [
|
|
914
|
+
{
|
|
915
|
+
type: "text",
|
|
916
|
+
text: formattedResult
|
|
917
|
+
}
|
|
918
|
+
]
|
|
919
|
+
};
|
|
920
|
+
} catch (error) {
|
|
921
|
+
return {
|
|
922
|
+
content: [
|
|
923
|
+
{
|
|
924
|
+
type: "text",
|
|
925
|
+
text: `Error: ${error.message}`
|
|
926
|
+
}
|
|
927
|
+
],
|
|
928
|
+
isError: true
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
this.server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
|
|
933
|
+
const resources = [];
|
|
934
|
+
for (const [uri, resource] of this.resources.entries()) {
|
|
935
|
+
resources.push({
|
|
936
|
+
uri: resource.uri,
|
|
937
|
+
name: resource.name,
|
|
938
|
+
description: resource.description,
|
|
939
|
+
mimeType: resource.mimeType
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
return {
|
|
943
|
+
resources
|
|
944
|
+
};
|
|
945
|
+
});
|
|
946
|
+
this.server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
|
|
947
|
+
const uri = request.params.uri;
|
|
948
|
+
const resource = this.resources.get(uri);
|
|
949
|
+
if (!resource) {
|
|
950
|
+
throw new Error(`Resource ${uri} not found`);
|
|
951
|
+
}
|
|
952
|
+
try {
|
|
953
|
+
const result = await resource.method.call(resource.instance);
|
|
954
|
+
return {
|
|
955
|
+
contents: [
|
|
956
|
+
{
|
|
957
|
+
uri,
|
|
958
|
+
mimeType: resource.mimeType,
|
|
959
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
960
|
+
}
|
|
961
|
+
]
|
|
962
|
+
};
|
|
963
|
+
} catch (error) {
|
|
964
|
+
throw new Error(`Failed to read resource ${uri}: ${error.message}`);
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
this.server.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
|
|
968
|
+
const prompts = [];
|
|
969
|
+
for (const [name, prompt] of this.prompts.entries()) {
|
|
970
|
+
prompts.push({
|
|
971
|
+
name,
|
|
972
|
+
description: prompt.description,
|
|
973
|
+
arguments: prompt.arguments
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
return {
|
|
977
|
+
prompts
|
|
978
|
+
};
|
|
979
|
+
});
|
|
980
|
+
this.server.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
|
|
981
|
+
const promptName = request.params.name;
|
|
982
|
+
const prompt = this.prompts.get(promptName);
|
|
983
|
+
if (!prompt) {
|
|
984
|
+
throw new Error(`Prompt ${promptName} not found`);
|
|
985
|
+
}
|
|
986
|
+
try {
|
|
987
|
+
const result = await prompt.method.call(prompt.instance, request.params.arguments || {});
|
|
988
|
+
if (result && result.messages) {
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
description: prompt.description,
|
|
993
|
+
messages: [
|
|
994
|
+
{
|
|
995
|
+
role: "user",
|
|
996
|
+
content: {
|
|
997
|
+
type: "text",
|
|
998
|
+
text: typeof result === "string" ? result : JSON.stringify(result)
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
]
|
|
1002
|
+
};
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
throw new Error(`Failed to get prompt ${promptName}: ${error.message}`);
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
async loadServices() {
|
|
1009
|
+
const absPath = import_path.default.resolve(this.options.servicesDir);
|
|
1010
|
+
if (!import_fs.default.existsSync(absPath)) {
|
|
1011
|
+
this.logger.error(`Services directory not found: ${absPath}`);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const files = import_fs.default.readdirSync(absPath);
|
|
1015
|
+
let toolCount = 0;
|
|
1016
|
+
let promptCount = 0;
|
|
1017
|
+
let resourceCount = 0;
|
|
1018
|
+
for (const dir of files) {
|
|
1019
|
+
const modulePath = import_path.default.join(absPath, dir, "index.ts");
|
|
1020
|
+
const modulePathJs = import_path.default.join(absPath, dir, "index.js");
|
|
1021
|
+
const finalPath = import_fs.default.existsSync(modulePath) ? modulePath : import_fs.default.existsSync(modulePathJs) ? modulePathJs : null;
|
|
1022
|
+
if (finalPath) {
|
|
1023
|
+
try {
|
|
1024
|
+
const fileUrl = (0, import_url.pathToFileURL)(finalPath).href;
|
|
1025
|
+
const mod = await import(fileUrl);
|
|
1026
|
+
const exportedClasses = Object.values(mod).filter((val) => typeof val === "function" && val.prototype);
|
|
1027
|
+
for (const cls of exportedClasses) {
|
|
1028
|
+
const instance = new cls();
|
|
1029
|
+
const envsPropKey = Reflect.getMetadata?.("userenvs:propertyKey", cls);
|
|
1030
|
+
if (envsPropKey) {
|
|
1031
|
+
instance[envsPropKey] = process.env;
|
|
1032
|
+
}
|
|
1033
|
+
const toolMethods = getDecoratedMethods(cls, "tool:name");
|
|
1034
|
+
for (const { method, propertyKey, metadata } of toolMethods) {
|
|
1035
|
+
const methodMeta = getMethodMetadata(method);
|
|
1036
|
+
const inputClass = Reflect.getMetadata?.("tool:inputClass", method);
|
|
1037
|
+
const outputClass = Reflect.getMetadata?.("tool:outputClass", method);
|
|
1038
|
+
let inputSchema = methodMeta.inputSchema;
|
|
1039
|
+
if (inputClass) {
|
|
1040
|
+
inputSchema = classToJsonSchemaWithConstraints(inputClass);
|
|
1041
|
+
}
|
|
1042
|
+
this.tools.set(methodMeta.toolName, {
|
|
1043
|
+
name: methodMeta.toolName,
|
|
1044
|
+
description: methodMeta.toolDescription || "",
|
|
1045
|
+
inputSchema,
|
|
1046
|
+
method,
|
|
1047
|
+
instance,
|
|
1048
|
+
propertyKey
|
|
1049
|
+
});
|
|
1050
|
+
toolCount++;
|
|
1051
|
+
if (this.options.logging) {
|
|
1052
|
+
this.logger.info(`Loaded tool: ${methodMeta.toolName}${inputClass ? " (class-based schema)" : ""}`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
const promptMethods = getDecoratedMethods(cls, "prompt:name");
|
|
1056
|
+
for (const { method, propertyKey, metadata } of promptMethods) {
|
|
1057
|
+
const methodMeta = getMethodMetadata(method);
|
|
1058
|
+
const promptArgs = methodMeta.inputSchema?.properties ? Object.keys(methodMeta.inputSchema.properties).map((key) => ({
|
|
1059
|
+
name: key,
|
|
1060
|
+
description: methodMeta.inputSchema?.properties?.[key]?.description || "",
|
|
1061
|
+
required: methodMeta.inputSchema?.required?.includes(key) || false
|
|
1062
|
+
})) : [];
|
|
1063
|
+
this.prompts.set(methodMeta.promptName, {
|
|
1064
|
+
name: methodMeta.promptName,
|
|
1065
|
+
description: methodMeta.promptDescription || "",
|
|
1066
|
+
arguments: promptArgs,
|
|
1067
|
+
method,
|
|
1068
|
+
instance,
|
|
1069
|
+
propertyKey
|
|
1070
|
+
});
|
|
1071
|
+
promptCount++;
|
|
1072
|
+
if (this.options.logging) {
|
|
1073
|
+
this.logger.info(`Loaded prompt: ${methodMeta.promptName}`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const resourceMethods = getDecoratedMethods(cls, "resource:uri");
|
|
1077
|
+
for (const { method, propertyKey, metadata } of resourceMethods) {
|
|
1078
|
+
const methodMeta = getMethodMetadata(method);
|
|
1079
|
+
this.resources.set(methodMeta.resourceUri, {
|
|
1080
|
+
uri: methodMeta.resourceUri,
|
|
1081
|
+
name: methodMeta.resourceName || methodMeta.resourceUri,
|
|
1082
|
+
description: methodMeta.resourceDescription || "",
|
|
1083
|
+
mimeType: "application/json",
|
|
1084
|
+
method,
|
|
1085
|
+
instance,
|
|
1086
|
+
propertyKey
|
|
1087
|
+
});
|
|
1088
|
+
resourceCount++;
|
|
1089
|
+
if (this.options.logging) {
|
|
1090
|
+
this.logger.info(`Loaded resource: ${methodMeta.resourceUri}`);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
this.logger.error(`Failed to load from ${dir}:`, error.message || error);
|
|
1096
|
+
if (this.options.logging) {
|
|
1097
|
+
this.logger.error("Full error:", error);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (this.options.logging) {
|
|
1103
|
+
this.logger.info(`
|
|
1104
|
+
Loaded ${toolCount} tools, ${promptCount} prompts, ${resourceCount} resources`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
async start() {
|
|
1108
|
+
await this.loadServices();
|
|
1109
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
1110
|
+
await this.server.connect(transport);
|
|
1111
|
+
if (this.options.logging) {
|
|
1112
|
+
this.logger.info("LeanMCP server running on stdio");
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
getServer() {
|
|
1116
|
+
return this.server;
|
|
1117
|
+
}
|
|
1118
|
+
getTools() {
|
|
1119
|
+
return Array.from(this.tools.values());
|
|
1120
|
+
}
|
|
1121
|
+
getPrompts() {
|
|
1122
|
+
return Array.from(this.prompts.values());
|
|
1123
|
+
}
|
|
1124
|
+
getResources() {
|
|
1125
|
+
return Array.from(this.resources.values());
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
async function startMCPServer(options) {
|
|
1129
|
+
const runtime = new MCPServerRuntime(options);
|
|
1130
|
+
await runtime.start();
|
|
1131
|
+
return runtime;
|
|
1132
|
+
}
|
|
1133
|
+
__name(startMCPServer, "startMCPServer");
|
|
1134
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1135
|
+
0 && (module.exports = {
|
|
1136
|
+
Auth,
|
|
1137
|
+
Deprecated,
|
|
1138
|
+
LogLevel,
|
|
1139
|
+
Logger,
|
|
1140
|
+
MCPServer,
|
|
1141
|
+
MCPServerRuntime,
|
|
1142
|
+
Optional,
|
|
1143
|
+
Prompt,
|
|
1144
|
+
Render,
|
|
1145
|
+
Resource,
|
|
1146
|
+
SchemaConstraint,
|
|
1147
|
+
Tool,
|
|
1148
|
+
UI,
|
|
1149
|
+
UserEnvs,
|
|
1150
|
+
classToJsonSchema,
|
|
1151
|
+
classToJsonSchemaWithConstraints,
|
|
1152
|
+
createHTTPServer,
|
|
1153
|
+
defaultLogger,
|
|
1154
|
+
getDecoratedMethods,
|
|
1155
|
+
getMethodMetadata,
|
|
1156
|
+
startMCPServer,
|
|
1157
|
+
validateNonEmpty,
|
|
1158
|
+
validatePath,
|
|
1159
|
+
validatePort,
|
|
1160
|
+
validateServiceName,
|
|
1161
|
+
validateUrl
|
|
1162
|
+
});
|