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