@leanmcp/core 0.4.5 → 0.4.6

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.
@@ -0,0 +1,279 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ // src/dynamodb-session-store.ts
11
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
12
+ import { DynamoDBDocumentClient, GetCommand, PutCommand, DeleteCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
13
+
14
+ // src/logger.ts
15
+ var LogLevel = /* @__PURE__ */ (function(LogLevel2) {
16
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
17
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
18
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
19
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
20
+ LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
21
+ return LogLevel2;
22
+ })({});
23
+ var COLORS = {
24
+ reset: "\x1B[0m",
25
+ gray: "\x1B[38;5;244m",
26
+ blue: "\x1B[1;34m",
27
+ amber: "\x1B[38;5;214m",
28
+ red: "\x1B[1;31m"
29
+ };
30
+ var levelStyles = {
31
+ [0]: {
32
+ label: "DEBUG",
33
+ color: COLORS.gray
34
+ },
35
+ [1]: {
36
+ label: "INFO",
37
+ color: COLORS.blue
38
+ },
39
+ [2]: {
40
+ label: "WARN",
41
+ color: COLORS.amber
42
+ },
43
+ [3]: {
44
+ label: "ERROR",
45
+ color: COLORS.red
46
+ },
47
+ [4]: {
48
+ label: "NONE",
49
+ color: COLORS.gray
50
+ }
51
+ };
52
+ var Logger = class {
53
+ static {
54
+ __name(this, "Logger");
55
+ }
56
+ level;
57
+ prefix;
58
+ timestamps;
59
+ colorize;
60
+ context;
61
+ handlers;
62
+ constructor(options = {}) {
63
+ this.level = options.level ?? 1;
64
+ this.prefix = options.prefix ?? "";
65
+ this.timestamps = options.timestamps ?? true;
66
+ this.colorize = options.colorize ?? true;
67
+ this.context = options.context;
68
+ this.handlers = options.handlers ?? [];
69
+ }
70
+ format(level, message) {
71
+ const style = levelStyles[level];
72
+ const timestamp = this.timestamps ? `[${(/* @__PURE__ */ new Date()).toISOString()}]` : "";
73
+ const prefix = this.prefix ? `[${this.prefix}]` : "";
74
+ const context = this.context ? `[${this.context}]` : "";
75
+ const label = `[${style.label}]`;
76
+ const parts = `${timestamp}${prefix}${context}${label} ${message}`;
77
+ if (!this.colorize) return parts;
78
+ return `${style.color}${parts}${COLORS.reset}`;
79
+ }
80
+ shouldLog(level) {
81
+ return level >= this.level && this.level !== 4;
82
+ }
83
+ emit(level, message, consoleFn, ...args) {
84
+ if (!this.shouldLog(level)) return;
85
+ const payload = {
86
+ level,
87
+ levelLabel: levelStyles[level].label,
88
+ message,
89
+ args,
90
+ prefix: this.prefix,
91
+ context: this.context,
92
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
93
+ };
94
+ consoleFn(this.format(level, message), ...args);
95
+ this.handlers.forEach((handler) => {
96
+ try {
97
+ handler(payload);
98
+ } catch (err) {
99
+ console.debug("Logger handler error", err);
100
+ }
101
+ });
102
+ }
103
+ debug(message, ...args) {
104
+ this.emit(0, message, console.debug, ...args);
105
+ }
106
+ info(message, ...args) {
107
+ this.emit(1, message, console.info, ...args);
108
+ }
109
+ warn(message, ...args) {
110
+ this.emit(2, message, console.warn, ...args);
111
+ }
112
+ error(message, ...args) {
113
+ this.emit(3, message, console.error, ...args);
114
+ }
115
+ setLevel(level) {
116
+ this.level = level;
117
+ }
118
+ getLevel() {
119
+ return this.level;
120
+ }
121
+ };
122
+ var defaultLogger = new Logger({
123
+ level: 1,
124
+ prefix: "LeanMCP"
125
+ });
126
+
127
+ // src/dynamodb-session-store.ts
128
+ var DEFAULT_TABLE_NAME = "leanmcp-sessions";
129
+ var DEFAULT_TTL_SECONDS = 86400;
130
+ var DynamoDBSessionStore = class {
131
+ static {
132
+ __name(this, "DynamoDBSessionStore");
133
+ }
134
+ client;
135
+ tableName;
136
+ ttlSeconds;
137
+ logger;
138
+ constructor(options) {
139
+ this.tableName = options?.tableName || process.env.DYNAMODB_TABLE_NAME || DEFAULT_TABLE_NAME;
140
+ this.ttlSeconds = options?.ttlSeconds || DEFAULT_TTL_SECONDS;
141
+ this.logger = new Logger({
142
+ level: options?.logging ? LogLevel.INFO : LogLevel.NONE,
143
+ prefix: "DynamoDBSessionStore"
144
+ });
145
+ const dynamoClient = new DynamoDBClient({
146
+ region: options?.region || process.env.AWS_REGION || "us-east-1"
147
+ });
148
+ this.client = DynamoDBDocumentClient.from(dynamoClient);
149
+ this.logger.info(`Initialized with table: ${this.tableName}, TTL: ${this.ttlSeconds}s`);
150
+ }
151
+ /**
152
+ * Check if a session exists in DynamoDB
153
+ */
154
+ async sessionExists(sessionId) {
155
+ try {
156
+ const result = await this.client.send(new GetCommand({
157
+ TableName: this.tableName,
158
+ Key: {
159
+ sessionId
160
+ },
161
+ ProjectionExpression: "sessionId"
162
+ }));
163
+ const exists = !!result.Item;
164
+ this.logger.debug(`Session ${sessionId} exists: ${exists}`);
165
+ return exists;
166
+ } catch (error) {
167
+ this.logger.error(`Error checking session existence: ${error.message}`);
168
+ return false;
169
+ }
170
+ }
171
+ /**
172
+ * Create a new session in DynamoDB
173
+ */
174
+ async createSession(sessionId, data) {
175
+ try {
176
+ const now = /* @__PURE__ */ new Date();
177
+ const ttl = Math.floor(Date.now() / 1e3) + this.ttlSeconds;
178
+ await this.client.send(new PutCommand({
179
+ TableName: this.tableName,
180
+ Item: {
181
+ sessionId,
182
+ createdAt: now.toISOString(),
183
+ updatedAt: now.toISOString(),
184
+ ttl,
185
+ data: data || {}
186
+ }
187
+ }));
188
+ this.logger.info(`Created session: ${sessionId} (TTL: ${new Date(ttl * 1e3).toISOString()})`);
189
+ } catch (error) {
190
+ this.logger.error(`Error creating session ${sessionId}: ${error.message}`);
191
+ throw new Error(`Failed to create session: ${error.message}`);
192
+ }
193
+ }
194
+ /**
195
+ * Get session data from DynamoDB
196
+ */
197
+ async getSession(sessionId) {
198
+ try {
199
+ const result = await this.client.send(new GetCommand({
200
+ TableName: this.tableName,
201
+ Key: {
202
+ sessionId
203
+ }
204
+ }));
205
+ if (!result.Item) {
206
+ this.logger.debug(`Session ${sessionId} not found`);
207
+ return null;
208
+ }
209
+ const sessionData = {
210
+ sessionId: result.Item.sessionId,
211
+ createdAt: new Date(result.Item.createdAt),
212
+ updatedAt: new Date(result.Item.updatedAt),
213
+ ttl: result.Item.ttl,
214
+ data: result.Item.data
215
+ };
216
+ this.logger.debug(`Retrieved session: ${sessionId}`);
217
+ return sessionData;
218
+ } catch (error) {
219
+ this.logger.error(`Error getting session ${sessionId}: ${error.message}`);
220
+ return null;
221
+ }
222
+ }
223
+ /**
224
+ * Update session data in DynamoDB
225
+ * Automatically refreshes TTL on each update
226
+ */
227
+ async updateSession(sessionId, updates) {
228
+ try {
229
+ const ttl = Math.floor(Date.now() / 1e3) + this.ttlSeconds;
230
+ await this.client.send(new UpdateCommand({
231
+ TableName: this.tableName,
232
+ Key: {
233
+ sessionId
234
+ },
235
+ UpdateExpression: "SET updatedAt = :now, #data = :data, #ttl = :ttl",
236
+ ExpressionAttributeNames: {
237
+ "#data": "data",
238
+ "#ttl": "ttl"
239
+ },
240
+ ExpressionAttributeValues: {
241
+ ":now": (/* @__PURE__ */ new Date()).toISOString(),
242
+ ":data": updates.data || {},
243
+ ":ttl": ttl
244
+ }
245
+ }));
246
+ this.logger.debug(`Updated session: ${sessionId}`);
247
+ } catch (error) {
248
+ this.logger.error(`Error updating session ${sessionId}: ${error.message}`);
249
+ throw new Error(`Failed to update session: ${error.message}`);
250
+ }
251
+ }
252
+ /**
253
+ * Delete a session from DynamoDB
254
+ */
255
+ async deleteSession(sessionId) {
256
+ try {
257
+ await this.client.send(new DeleteCommand({
258
+ TableName: this.tableName,
259
+ Key: {
260
+ sessionId
261
+ }
262
+ }));
263
+ this.logger.info(`Deleted session: ${sessionId}`);
264
+ } catch (error) {
265
+ this.logger.error(`Error deleting session ${sessionId}: ${error.message}`);
266
+ }
267
+ }
268
+ };
269
+
270
+ export {
271
+ __name,
272
+ __require,
273
+ LogLevel,
274
+ Logger,
275
+ defaultLogger,
276
+ DEFAULT_TABLE_NAME,
277
+ DEFAULT_TTL_SECONDS,
278
+ DynamoDBSessionStore
279
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ DEFAULT_TABLE_NAME,
3
+ DEFAULT_TTL_SECONDS,
4
+ DynamoDBSessionStore
5
+ } from "./chunk-GTCGBX2K.mjs";
6
+ export {
7
+ DEFAULT_TABLE_NAME,
8
+ DEFAULT_TTL_SECONDS,
9
+ DynamoDBSessionStore
10
+ };
package/dist/index.d.mts CHANGED
@@ -382,15 +382,31 @@ declare function SchemaConstraint(constraints: {
382
382
  enum?: any[];
383
383
  description?: string;
384
384
  default?: any;
385
- items?: {
386
- type: string;
387
- description?: string;
388
- };
385
+ type?: string;
389
386
  }): PropertyDecorator;
390
387
  /**
391
- * Enhanced schema generator that includes constraints
388
+ * Enhanced schema generator that includes constraints.
389
+ * Now integrates with type-parser to extract actual TypeScript types
390
+ * (including array element types like `string[]`).
391
+ */
392
+ declare function classToJsonSchemaWithConstraints(classConstructor: new () => any, sourceFilePath?: string): any;
393
+
394
+ /**
395
+ * Register the source file path for an input class.
396
+ * This is called during decorator execution to capture the source location.
397
+ */
398
+ declare function registerClassSource(className: string, sourceFile: string): void;
399
+ /**
400
+ * Parse a TypeScript class and extract JSON Schema types for each property.
401
+ * Automatically detects array types like `string[]` and adds proper `items`.
402
+ *
403
+ * This is now SYNCHRONOUS for easier integration with existing code.
404
+ */
405
+ declare function parseClassTypesSync(classConstructor: new () => any, sourceFilePath?: string): Map<string, any>;
406
+ /**
407
+ * Clear the type cache (useful for hot reloading)
392
408
  */
393
- declare function classToJsonSchemaWithConstraints(classConstructor: new () => any): any;
409
+ declare function clearTypeCache(): void;
394
410
 
395
411
  /**
396
412
  * Input validation utilities for LeanMCP
@@ -969,4 +985,4 @@ declare class MCPServerRuntime {
969
985
  */
970
986
  declare function startMCPServer(options: MCPServerOptions): Promise<MCPServerRuntime>;
971
987
 
972
- export { Auth, type AuthErrorOptions, type AuthErrorResult, type AuthOptions, DEFAULT_TABLE_NAME, DEFAULT_TTL_SECONDS, Deprecated, DynamoDBSessionStore, type HTTPServerAuthOptions, type HTTPServerInput, type HTTPServerOptions, type ISessionStore, InMemorySessionStore, LeanMCPSessionProvider, type LeanMCPSessionProviderOptions, LogLevel, type LogPayload, Logger, type LoggerHandler, type LoggerOptions, MCPServer, type MCPServerConstructorOptions, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, type ProtectedResourceMetadata, Render, Resource, type ResourceOptions, SchemaConstraint, type SecurityScheme, type SessionData, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, createAuthError, createHTTPServer, createProtectedResourceMetadata, defaultLogger, extractBearerToken, getDecoratedMethods, getMethodMetadata, isAuthError, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
988
+ export { Auth, type AuthErrorOptions, type AuthErrorResult, type AuthOptions, DEFAULT_TABLE_NAME, DEFAULT_TTL_SECONDS, Deprecated, DynamoDBSessionStore, type HTTPServerAuthOptions, type HTTPServerInput, type HTTPServerOptions, type ISessionStore, InMemorySessionStore, LeanMCPSessionProvider, type LeanMCPSessionProviderOptions, LogLevel, type LogPayload, Logger, type LoggerHandler, type LoggerOptions, MCPServer, type MCPServerConstructorOptions, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, type ProtectedResourceMetadata, Render, Resource, type ResourceOptions, SchemaConstraint, type SecurityScheme, type SessionData, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, clearTypeCache, createAuthError, createHTTPServer, createProtectedResourceMetadata, defaultLogger, extractBearerToken, getDecoratedMethods, getMethodMetadata, isAuthError, parseClassTypesSync, registerClassSource, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
package/dist/index.d.ts CHANGED
@@ -382,15 +382,31 @@ declare function SchemaConstraint(constraints: {
382
382
  enum?: any[];
383
383
  description?: string;
384
384
  default?: any;
385
- items?: {
386
- type: string;
387
- description?: string;
388
- };
385
+ type?: string;
389
386
  }): PropertyDecorator;
390
387
  /**
391
- * Enhanced schema generator that includes constraints
388
+ * Enhanced schema generator that includes constraints.
389
+ * Now integrates with type-parser to extract actual TypeScript types
390
+ * (including array element types like `string[]`).
391
+ */
392
+ declare function classToJsonSchemaWithConstraints(classConstructor: new () => any, sourceFilePath?: string): any;
393
+
394
+ /**
395
+ * Register the source file path for an input class.
396
+ * This is called during decorator execution to capture the source location.
397
+ */
398
+ declare function registerClassSource(className: string, sourceFile: string): void;
399
+ /**
400
+ * Parse a TypeScript class and extract JSON Schema types for each property.
401
+ * Automatically detects array types like `string[]` and adds proper `items`.
402
+ *
403
+ * This is now SYNCHRONOUS for easier integration with existing code.
404
+ */
405
+ declare function parseClassTypesSync(classConstructor: new () => any, sourceFilePath?: string): Map<string, any>;
406
+ /**
407
+ * Clear the type cache (useful for hot reloading)
392
408
  */
393
- declare function classToJsonSchemaWithConstraints(classConstructor: new () => any): any;
409
+ declare function clearTypeCache(): void;
394
410
 
395
411
  /**
396
412
  * Input validation utilities for LeanMCP
@@ -969,4 +985,4 @@ declare class MCPServerRuntime {
969
985
  */
970
986
  declare function startMCPServer(options: MCPServerOptions): Promise<MCPServerRuntime>;
971
987
 
972
- export { Auth, type AuthErrorOptions, type AuthErrorResult, type AuthOptions, DEFAULT_TABLE_NAME, DEFAULT_TTL_SECONDS, Deprecated, DynamoDBSessionStore, type HTTPServerAuthOptions, type HTTPServerInput, type HTTPServerOptions, type ISessionStore, InMemorySessionStore, LeanMCPSessionProvider, type LeanMCPSessionProviderOptions, LogLevel, type LogPayload, Logger, type LoggerHandler, type LoggerOptions, MCPServer, type MCPServerConstructorOptions, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, type ProtectedResourceMetadata, Render, Resource, type ResourceOptions, SchemaConstraint, type SecurityScheme, type SessionData, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, createAuthError, createHTTPServer, createProtectedResourceMetadata, defaultLogger, extractBearerToken, getDecoratedMethods, getMethodMetadata, isAuthError, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
988
+ export { Auth, type AuthErrorOptions, type AuthErrorResult, type AuthOptions, DEFAULT_TABLE_NAME, DEFAULT_TTL_SECONDS, Deprecated, DynamoDBSessionStore, type HTTPServerAuthOptions, type HTTPServerInput, type HTTPServerOptions, type ISessionStore, InMemorySessionStore, LeanMCPSessionProvider, type LeanMCPSessionProviderOptions, LogLevel, type LogPayload, Logger, type LoggerHandler, type LoggerOptions, MCPServer, type MCPServerConstructorOptions, type MCPServerFactory, type MCPServerOptions, MCPServerRuntime, Optional, Prompt, type PromptOptions, type ProtectedResourceMetadata, Render, Resource, type ResourceOptions, SchemaConstraint, type SecurityScheme, type SessionData, Tool, type ToolOptions, UI, UserEnvs, classToJsonSchema, classToJsonSchemaWithConstraints, clearTypeCache, createAuthError, createHTTPServer, createProtectedResourceMetadata, defaultLogger, extractBearerToken, getDecoratedMethods, getMethodMetadata, isAuthError, parseClassTypesSync, registerClassSource, startMCPServer, validateNonEmpty, validatePath, validatePort, validateServiceName, validateUrl };
package/dist/index.js CHANGED
@@ -185,6 +185,186 @@ var init_decorators = __esm({
185
185
  }
186
186
  });
187
187
 
188
+ // src/type-parser.ts
189
+ function loadTsMorph() {
190
+ if (tsMorphLoaded) {
191
+ return tsMorphProject !== null;
192
+ }
193
+ tsMorphLoaded = true;
194
+ try {
195
+ const customRequire = (0, import_node_module.createRequire)(import_meta.url);
196
+ const tsMorph = customRequire("ts-morph");
197
+ tsMorphProject = tsMorph.Project;
198
+ tsMorphNode = tsMorph.Node;
199
+ return true;
200
+ } catch (error) {
201
+ console.warn(`[type-parser] ts-morph not available: ${error.message}`);
202
+ return false;
203
+ }
204
+ }
205
+ function registerClassSource(className, sourceFile) {
206
+ classSourceMap.set(className, sourceFile);
207
+ }
208
+ function parseClassTypesSync(classConstructor, sourceFilePath) {
209
+ if (!loadTsMorph()) {
210
+ return /* @__PURE__ */ new Map();
211
+ }
212
+ const className = classConstructor.name;
213
+ let filePath = sourceFilePath || classSourceMap.get(className) || findSourceFile();
214
+ if (!filePath) {
215
+ return /* @__PURE__ */ new Map();
216
+ }
217
+ if (filePath.endsWith(".js")) {
218
+ const tsPath = filePath.replace(/\.js$/, ".ts");
219
+ if (import_fs.default.existsSync(tsPath)) {
220
+ filePath = tsPath;
221
+ }
222
+ }
223
+ const cacheKey = `${filePath}:${className}`;
224
+ if (typeCache.has(cacheKey)) {
225
+ return typeCache.get(cacheKey);
226
+ }
227
+ if (!import_fs.default.existsSync(filePath)) {
228
+ return /* @__PURE__ */ new Map();
229
+ }
230
+ try {
231
+ const project = new tsMorphProject({
232
+ skipAddingFilesFromTsConfig: true,
233
+ skipFileDependencyResolution: true,
234
+ useInMemoryFileSystem: false
235
+ });
236
+ const sourceFile = project.addSourceFileAtPath(filePath);
237
+ const classDecl = sourceFile.getClass(className);
238
+ if (!classDecl) {
239
+ return /* @__PURE__ */ new Map();
240
+ }
241
+ const propertyTypes = /* @__PURE__ */ new Map();
242
+ for (const prop of classDecl.getInstanceProperties()) {
243
+ if (tsMorphNode.isPropertyDeclaration(prop)) {
244
+ const propName = prop.getName();
245
+ const typeNode = prop.getTypeNode();
246
+ if (typeNode) {
247
+ const typeText = typeNode.getText();
248
+ const jsonSchemaType = typeTextToJsonSchema(typeText);
249
+ propertyTypes.set(propName, jsonSchemaType);
250
+ }
251
+ }
252
+ }
253
+ typeCache.set(cacheKey, propertyTypes);
254
+ return propertyTypes;
255
+ } catch (error) {
256
+ console.warn(`[type-parser] Failed to parse ${filePath}: ${error.message}`);
257
+ return /* @__PURE__ */ new Map();
258
+ }
259
+ }
260
+ function typeTextToJsonSchema(typeText) {
261
+ const type = typeText.trim().replace(/\?$/, "");
262
+ if (type.endsWith("[]")) {
263
+ const elementType = type.slice(0, -2);
264
+ return {
265
+ type: "array",
266
+ items: typeTextToJsonSchema(elementType)
267
+ };
268
+ }
269
+ const arrayMatch = type.match(/^Array<(.+)>$/);
270
+ if (arrayMatch) {
271
+ return {
272
+ type: "array",
273
+ items: typeTextToJsonSchema(arrayMatch[1])
274
+ };
275
+ }
276
+ const lowerType = type.toLowerCase();
277
+ switch (lowerType) {
278
+ case "string":
279
+ return {
280
+ type: "string"
281
+ };
282
+ case "number":
283
+ return {
284
+ type: "number"
285
+ };
286
+ case "integer":
287
+ return {
288
+ type: "integer"
289
+ };
290
+ case "boolean":
291
+ return {
292
+ type: "boolean"
293
+ };
294
+ case "object":
295
+ return {
296
+ type: "object"
297
+ };
298
+ case "any":
299
+ case "unknown":
300
+ return {};
301
+ // No constraints
302
+ case "null":
303
+ return {
304
+ type: "null"
305
+ };
306
+ }
307
+ if (type.includes("|")) {
308
+ const parts = type.split("|").map((p) => p.trim());
309
+ const nonNullParts = parts.filter((p) => p.toLowerCase() !== "null");
310
+ if (nonNullParts.length === 1) {
311
+ return typeTextToJsonSchema(nonNullParts[0]);
312
+ }
313
+ return {
314
+ anyOf: nonNullParts.map((p) => typeTextToJsonSchema(p))
315
+ };
316
+ }
317
+ return {
318
+ type: "object"
319
+ };
320
+ }
321
+ function findSourceFile() {
322
+ const originalPrepareStackTrace = Error.prepareStackTrace;
323
+ try {
324
+ Error.prepareStackTrace = (_, stack2) => stack2;
325
+ const err = new Error();
326
+ const stack = err.stack;
327
+ for (const site of stack) {
328
+ const fileName = site.getFileName();
329
+ if (fileName && !fileName.includes("node_modules") && !fileName.includes("type-parser") && !fileName.includes("schema-generator") && (fileName.endsWith(".ts") || fileName.endsWith(".js"))) {
330
+ if (fileName.endsWith(".js")) {
331
+ const tsPath = fileName.replace(/\.js$/, ".ts");
332
+ if (import_fs.default.existsSync(tsPath)) {
333
+ return tsPath;
334
+ }
335
+ }
336
+ return fileName;
337
+ }
338
+ }
339
+ } finally {
340
+ Error.prepareStackTrace = originalPrepareStackTrace;
341
+ }
342
+ return null;
343
+ }
344
+ function clearTypeCache() {
345
+ typeCache.clear();
346
+ }
347
+ var import_fs, import_node_module, import_meta, typeCache, classSourceMap, tsMorphProject, tsMorphNode, tsMorphLoaded;
348
+ var init_type_parser = __esm({
349
+ "src/type-parser.ts"() {
350
+ "use strict";
351
+ import_fs = __toESM(require("fs"));
352
+ import_node_module = require("module");
353
+ import_meta = {};
354
+ typeCache = /* @__PURE__ */ new Map();
355
+ classSourceMap = /* @__PURE__ */ new Map();
356
+ tsMorphProject = null;
357
+ tsMorphNode = null;
358
+ tsMorphLoaded = false;
359
+ __name(loadTsMorph, "loadTsMorph");
360
+ __name(registerClassSource, "registerClassSource");
361
+ __name(parseClassTypesSync, "parseClassTypesSync");
362
+ __name(typeTextToJsonSchema, "typeTextToJsonSchema");
363
+ __name(findSourceFile, "findSourceFile");
364
+ __name(clearTypeCache, "clearTypeCache");
365
+ }
366
+ });
367
+
188
368
  // src/schema-generator.ts
189
369
  function classToJsonSchema(classConstructor) {
190
370
  const instance = new classConstructor();
@@ -240,19 +420,44 @@ function Optional() {
240
420
  function SchemaConstraint(constraints) {
241
421
  return (target, propertyKey) => {
242
422
  Reflect.defineMetadata("schema:constraints", constraints, target, propertyKey);
423
+ const originalPrepareStackTrace = Error.prepareStackTrace;
424
+ try {
425
+ Error.prepareStackTrace = (_, stack2) => stack2;
426
+ const err = new Error();
427
+ const stack = err.stack;
428
+ for (const site of stack) {
429
+ const fileName = site.getFileName();
430
+ if (fileName && !fileName.includes("node_modules") && !fileName.includes("schema-generator") && (fileName.endsWith(".ts") || fileName.endsWith(".js"))) {
431
+ const className = target.constructor.name;
432
+ if (className && className !== "Object") {
433
+ registerClassSource(className, fileName);
434
+ }
435
+ break;
436
+ }
437
+ }
438
+ } finally {
439
+ Error.prepareStackTrace = originalPrepareStackTrace;
440
+ }
243
441
  };
244
442
  }
245
- function classToJsonSchemaWithConstraints(classConstructor) {
443
+ function classToJsonSchemaWithConstraints(classConstructor, sourceFilePath) {
246
444
  const instance = new classConstructor();
247
445
  const properties = {};
248
446
  const required = [];
447
+ const parsedTypes = parseClassTypesSync(classConstructor, sourceFilePath);
249
448
  const propertyNames = Object.keys(instance);
250
449
  for (const propertyName of propertyNames) {
251
450
  const propertyType = Reflect.getMetadata("design:type", instance, propertyName);
252
451
  const constraints = Reflect.getMetadata("schema:constraints", instance, propertyName);
253
452
  const isOptional = Reflect.getMetadata("optional", instance, propertyName);
254
- let jsonSchemaType = "string";
255
- if (propertyType) {
453
+ const parsedType = parsedTypes.get(propertyName);
454
+ let propertySchema;
455
+ if (parsedType && parsedType.type) {
456
+ propertySchema = {
457
+ ...parsedType
458
+ };
459
+ } else if (propertyType) {
460
+ let jsonSchemaType = "string";
256
461
  switch (propertyType.name) {
257
462
  case "String":
258
463
  jsonSchemaType = "string";
@@ -272,8 +477,16 @@ function classToJsonSchemaWithConstraints(classConstructor) {
272
477
  default:
273
478
  jsonSchemaType = "object";
274
479
  }
480
+ propertySchema = {
481
+ type: jsonSchemaType
482
+ };
275
483
  } else if (constraints) {
276
- if (constraints.minLength !== void 0 || constraints.maxLength !== void 0 || constraints.pattern) {
484
+ let jsonSchemaType = "string";
485
+ if (constraints.type) {
486
+ jsonSchemaType = constraints.type;
487
+ } else if (constraints.items) {
488
+ jsonSchemaType = "array";
489
+ } else if (constraints.minLength !== void 0 || constraints.maxLength !== void 0 || constraints.pattern) {
277
490
  jsonSchemaType = "string";
278
491
  } else if (constraints.minimum !== void 0 || constraints.maximum !== void 0) {
279
492
  jsonSchemaType = "number";
@@ -287,12 +500,25 @@ function classToJsonSchemaWithConstraints(classConstructor) {
287
500
  jsonSchemaType = "string";
288
501
  }
289
502
  }
503
+ propertySchema = {
504
+ type: jsonSchemaType
505
+ };
506
+ } else {
507
+ propertySchema = {
508
+ type: "string"
509
+ };
290
510
  }
291
- const propertySchema = {
292
- type: jsonSchemaType,
293
- ...constraints || {}
294
- };
295
- if (jsonSchemaType === "array" && !propertySchema.items) {
511
+ if (constraints) {
512
+ const parsedItems = propertySchema.items;
513
+ propertySchema = {
514
+ ...propertySchema,
515
+ ...constraints
516
+ };
517
+ if (parsedItems && !constraints.items) {
518
+ propertySchema.items = parsedItems;
519
+ }
520
+ }
521
+ if (propertySchema.type === "array" && !propertySchema.items) {
296
522
  propertySchema.items = {
297
523
  type: "string"
298
524
  };
@@ -313,6 +539,7 @@ var init_schema_generator = __esm({
313
539
  "src/schema-generator.ts"() {
314
540
  "use strict";
315
541
  import_reflect_metadata2 = require("reflect-metadata");
542
+ init_type_parser();
316
543
  __name(classToJsonSchema, "classToJsonSchema");
317
544
  __name(Optional, "Optional");
318
545
  __name(SchemaConstraint, "SchemaConstraint");
@@ -1617,6 +1844,7 @@ __export(index_exports, {
1617
1844
  UserEnvs: () => UserEnvs,
1618
1845
  classToJsonSchema: () => classToJsonSchema,
1619
1846
  classToJsonSchemaWithConstraints: () => classToJsonSchemaWithConstraints,
1847
+ clearTypeCache: () => clearTypeCache,
1620
1848
  createAuthError: () => createAuthError,
1621
1849
  createHTTPServer: () => createHTTPServer,
1622
1850
  createProtectedResourceMetadata: () => createProtectedResourceMetadata,
@@ -1625,6 +1853,8 @@ __export(index_exports, {
1625
1853
  getDecoratedMethods: () => getDecoratedMethods,
1626
1854
  getMethodMetadata: () => getMethodMetadata,
1627
1855
  isAuthError: () => isAuthError,
1856
+ parseClassTypesSync: () => parseClassTypesSync,
1857
+ registerClassSource: () => registerClassSource,
1628
1858
  startMCPServer: () => startMCPServer,
1629
1859
  validateNonEmpty: () => validateNonEmpty,
1630
1860
  validatePath: () => validatePath,
@@ -1638,11 +1868,11 @@ async function startMCPServer(options) {
1638
1868
  await runtime.start();
1639
1869
  return runtime;
1640
1870
  }
1641
- var import_reflect_metadata3, import_fs, import_path, import_url, import_server, import_stdio, import_types, import_ajv, ajv, MCPServer, MCPServerRuntime;
1871
+ var import_reflect_metadata3, import_fs2, import_path, import_url, import_server, import_stdio, import_types, import_ajv, ajv, MCPServer, MCPServerRuntime;
1642
1872
  var init_index = __esm({
1643
1873
  "src/index.ts"() {
1644
1874
  import_reflect_metadata3 = require("reflect-metadata");
1645
- import_fs = __toESM(require("fs"));
1875
+ import_fs2 = __toESM(require("fs"));
1646
1876
  import_path = __toESM(require("path"));
1647
1877
  import_url = require("url");
1648
1878
  import_server = require("@modelcontextprotocol/sdk/server/index.js");
@@ -1651,6 +1881,7 @@ var init_index = __esm({
1651
1881
  import_ajv = __toESM(require("ajv"));
1652
1882
  init_decorators();
1653
1883
  init_schema_generator();
1884
+ init_type_parser();
1654
1885
  init_http_server();
1655
1886
  init_logger();
1656
1887
  init_validation();
@@ -1739,7 +1970,7 @@ var init_index = __esm({
1739
1970
  mcpDir = import_path.default.join(process.cwd(), "mcp");
1740
1971
  }
1741
1972
  }
1742
- if (import_fs.default.existsSync(mcpDir)) {
1973
+ if (import_fs2.default.existsSync(mcpDir)) {
1743
1974
  this.logger.debug(`Auto-discovering services from: ${mcpDir}`);
1744
1975
  await this.autoRegisterServices(mcpDir, serviceFactories);
1745
1976
  } else {
@@ -1989,7 +2220,7 @@ var init_index = __esm({
1989
2220
  */
1990
2221
  async autoRegisterServices(mcpDir, serviceFactories) {
1991
2222
  this.logger.debug(`Auto-registering services from: ${mcpDir}`);
1992
- if (!import_fs.default.existsSync(mcpDir)) {
2223
+ if (!import_fs2.default.existsSync(mcpDir)) {
1993
2224
  this.logger.warn(`MCP directory not found: ${mcpDir}`);
1994
2225
  return;
1995
2226
  }
@@ -2008,7 +2239,7 @@ var init_index = __esm({
2008
2239
  */
2009
2240
  findServiceFiles(dir) {
2010
2241
  const files = [];
2011
- const entries = import_fs.default.readdirSync(dir, {
2242
+ const entries = import_fs2.default.readdirSync(dir, {
2012
2243
  withFileTypes: true
2013
2244
  });
2014
2245
  for (const entry of entries) {
@@ -2143,7 +2374,7 @@ var init_index = __esm({
2143
2374
  }
2144
2375
  try {
2145
2376
  const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
2146
- if (!import_fs.default.existsSync(manifestPath)) {
2377
+ if (!import_fs2.default.existsSync(manifestPath)) {
2147
2378
  return;
2148
2379
  }
2149
2380
  if (this.logging) {
@@ -2186,7 +2417,7 @@ var init_index = __esm({
2186
2417
  async reloadUIManifest() {
2187
2418
  try {
2188
2419
  const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
2189
- if (!import_fs.default.existsSync(manifestPath)) {
2420
+ if (!import_fs2.default.existsSync(manifestPath)) {
2190
2421
  const uiResourceUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
2191
2422
  for (const uri of uiResourceUris) {
2192
2423
  this.resources.delete(uri);
@@ -2196,7 +2427,7 @@ var init_index = __esm({
2196
2427
  }
2197
2428
  return;
2198
2429
  }
2199
- const manifest = JSON.parse(import_fs.default.readFileSync(manifestPath, "utf-8"));
2430
+ const manifest = JSON.parse(import_fs2.default.readFileSync(manifestPath, "utf-8"));
2200
2431
  const currentUIUris = new Set(Object.keys(manifest));
2201
2432
  const registeredUIUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
2202
2433
  for (const uri of registeredUIUris) {
@@ -2212,7 +2443,7 @@ var init_index = __esm({
2212
2443
  const htmlPath = isString ? entry : entry.htmlPath;
2213
2444
  const isGPTApp = !isString && entry.isGPTApp;
2214
2445
  const gptMeta = !isString ? entry.gptMeta : void 0;
2215
- if (!import_fs.default.existsSync(htmlPath)) {
2446
+ if (!import_fs2.default.existsSync(htmlPath)) {
2216
2447
  if (this.logging) {
2217
2448
  this.logger.warn(`UI HTML file not found: ${htmlPath}`);
2218
2449
  }
@@ -2233,8 +2464,8 @@ var init_index = __esm({
2233
2464
  mimeType,
2234
2465
  inputSchema: void 0,
2235
2466
  method: /* @__PURE__ */ __name(async () => {
2236
- if (import_fs.default.existsSync(htmlPath)) {
2237
- const html = import_fs.default.readFileSync(htmlPath, "utf-8");
2467
+ if (import_fs2.default.existsSync(htmlPath)) {
2468
+ const html = import_fs2.default.readFileSync(htmlPath, "utf-8");
2238
2469
  return {
2239
2470
  text: html,
2240
2471
  _meta: Object.keys(_meta).length > 0 ? _meta : void 0
@@ -2263,10 +2494,10 @@ var init_index = __esm({
2263
2494
  async loadUIManifest() {
2264
2495
  try {
2265
2496
  const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
2266
- if (!import_fs.default.existsSync(manifestPath)) {
2497
+ if (!import_fs2.default.existsSync(manifestPath)) {
2267
2498
  return;
2268
2499
  }
2269
- const manifest = JSON.parse(import_fs.default.readFileSync(manifestPath, "utf-8"));
2500
+ const manifest = JSON.parse(import_fs2.default.readFileSync(manifestPath, "utf-8"));
2270
2501
  for (const [uri, entry] of Object.entries(manifest)) {
2271
2502
  const isString = typeof entry === "string";
2272
2503
  const htmlPath = isString ? entry : entry.htmlPath;
@@ -2278,13 +2509,13 @@ var init_index = __esm({
2278
2509
  }
2279
2510
  continue;
2280
2511
  }
2281
- if (!import_fs.default.existsSync(htmlPath)) {
2512
+ if (!import_fs2.default.existsSync(htmlPath)) {
2282
2513
  if (this.logging) {
2283
2514
  this.logger.warn(`UI HTML file not found: ${htmlPath}`);
2284
2515
  }
2285
2516
  continue;
2286
2517
  }
2287
- const html = import_fs.default.readFileSync(htmlPath, "utf-8");
2518
+ const html = import_fs2.default.readFileSync(htmlPath, "utf-8");
2288
2519
  const mimeType = isGPTApp ? "text/html+skybridge" : "text/html;profile=mcp-app";
2289
2520
  const _meta = {};
2290
2521
  if (isGPTApp) {
@@ -2536,18 +2767,18 @@ var init_index = __esm({
2536
2767
  }
2537
2768
  async loadServices() {
2538
2769
  const absPath = import_path.default.resolve(this.options.servicesDir);
2539
- if (!import_fs.default.existsSync(absPath)) {
2770
+ if (!import_fs2.default.existsSync(absPath)) {
2540
2771
  this.logger.error(`Services directory not found: ${absPath}`);
2541
2772
  return;
2542
2773
  }
2543
- const files = import_fs.default.readdirSync(absPath);
2774
+ const files = import_fs2.default.readdirSync(absPath);
2544
2775
  let toolCount = 0;
2545
2776
  let promptCount = 0;
2546
2777
  let resourceCount = 0;
2547
2778
  for (const dir of files) {
2548
2779
  const modulePath = import_path.default.join(absPath, dir, "index.ts");
2549
2780
  const modulePathJs = import_path.default.join(absPath, dir, "index.js");
2550
- const finalPath = import_fs.default.existsSync(modulePath) ? modulePath : import_fs.default.existsSync(modulePathJs) ? modulePathJs : null;
2781
+ const finalPath = import_fs2.default.existsSync(modulePath) ? modulePath : import_fs2.default.existsSync(modulePathJs) ? modulePathJs : null;
2551
2782
  if (finalPath) {
2552
2783
  try {
2553
2784
  const fileUrl = (0, import_url.pathToFileURL)(finalPath).href;
@@ -2681,6 +2912,7 @@ init_index();
2681
2912
  UserEnvs,
2682
2913
  classToJsonSchema,
2683
2914
  classToJsonSchemaWithConstraints,
2915
+ clearTypeCache,
2684
2916
  createAuthError,
2685
2917
  createHTTPServer,
2686
2918
  createProtectedResourceMetadata,
@@ -2689,6 +2921,8 @@ init_index();
2689
2921
  getDecoratedMethods,
2690
2922
  getMethodMetadata,
2691
2923
  isAuthError,
2924
+ parseClassTypesSync,
2925
+ registerClassSource,
2692
2926
  startMCPServer,
2693
2927
  validateNonEmpty,
2694
2928
  validatePath,
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import {
10
10
 
11
11
  // src/index.ts
12
12
  import "reflect-metadata";
13
- import fs from "fs";
13
+ import fs2 from "fs";
14
14
  import path from "path";
15
15
  import { pathToFileURL } from "url";
16
16
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -168,6 +168,181 @@ __name(getDecoratedMethods, "getDecoratedMethods");
168
168
 
169
169
  // src/schema-generator.ts
170
170
  import "reflect-metadata";
171
+
172
+ // src/type-parser.ts
173
+ import fs from "fs";
174
+ import { createRequire } from "module";
175
+ var typeCache = /* @__PURE__ */ new Map();
176
+ var classSourceMap = /* @__PURE__ */ new Map();
177
+ var tsMorphProject = null;
178
+ var tsMorphNode = null;
179
+ var tsMorphLoaded = false;
180
+ function loadTsMorph() {
181
+ if (tsMorphLoaded) {
182
+ return tsMorphProject !== null;
183
+ }
184
+ tsMorphLoaded = true;
185
+ try {
186
+ const customRequire = createRequire(import.meta.url);
187
+ const tsMorph = customRequire("ts-morph");
188
+ tsMorphProject = tsMorph.Project;
189
+ tsMorphNode = tsMorph.Node;
190
+ return true;
191
+ } catch (error) {
192
+ console.warn(`[type-parser] ts-morph not available: ${error.message}`);
193
+ return false;
194
+ }
195
+ }
196
+ __name(loadTsMorph, "loadTsMorph");
197
+ function registerClassSource(className, sourceFile) {
198
+ classSourceMap.set(className, sourceFile);
199
+ }
200
+ __name(registerClassSource, "registerClassSource");
201
+ function parseClassTypesSync(classConstructor, sourceFilePath) {
202
+ if (!loadTsMorph()) {
203
+ return /* @__PURE__ */ new Map();
204
+ }
205
+ const className = classConstructor.name;
206
+ let filePath = sourceFilePath || classSourceMap.get(className) || findSourceFile();
207
+ if (!filePath) {
208
+ return /* @__PURE__ */ new Map();
209
+ }
210
+ if (filePath.endsWith(".js")) {
211
+ const tsPath = filePath.replace(/\.js$/, ".ts");
212
+ if (fs.existsSync(tsPath)) {
213
+ filePath = tsPath;
214
+ }
215
+ }
216
+ const cacheKey = `${filePath}:${className}`;
217
+ if (typeCache.has(cacheKey)) {
218
+ return typeCache.get(cacheKey);
219
+ }
220
+ if (!fs.existsSync(filePath)) {
221
+ return /* @__PURE__ */ new Map();
222
+ }
223
+ try {
224
+ const project = new tsMorphProject({
225
+ skipAddingFilesFromTsConfig: true,
226
+ skipFileDependencyResolution: true,
227
+ useInMemoryFileSystem: false
228
+ });
229
+ const sourceFile = project.addSourceFileAtPath(filePath);
230
+ const classDecl = sourceFile.getClass(className);
231
+ if (!classDecl) {
232
+ return /* @__PURE__ */ new Map();
233
+ }
234
+ const propertyTypes = /* @__PURE__ */ new Map();
235
+ for (const prop of classDecl.getInstanceProperties()) {
236
+ if (tsMorphNode.isPropertyDeclaration(prop)) {
237
+ const propName = prop.getName();
238
+ const typeNode = prop.getTypeNode();
239
+ if (typeNode) {
240
+ const typeText = typeNode.getText();
241
+ const jsonSchemaType = typeTextToJsonSchema(typeText);
242
+ propertyTypes.set(propName, jsonSchemaType);
243
+ }
244
+ }
245
+ }
246
+ typeCache.set(cacheKey, propertyTypes);
247
+ return propertyTypes;
248
+ } catch (error) {
249
+ console.warn(`[type-parser] Failed to parse ${filePath}: ${error.message}`);
250
+ return /* @__PURE__ */ new Map();
251
+ }
252
+ }
253
+ __name(parseClassTypesSync, "parseClassTypesSync");
254
+ function typeTextToJsonSchema(typeText) {
255
+ const type = typeText.trim().replace(/\?$/, "");
256
+ if (type.endsWith("[]")) {
257
+ const elementType = type.slice(0, -2);
258
+ return {
259
+ type: "array",
260
+ items: typeTextToJsonSchema(elementType)
261
+ };
262
+ }
263
+ const arrayMatch = type.match(/^Array<(.+)>$/);
264
+ if (arrayMatch) {
265
+ return {
266
+ type: "array",
267
+ items: typeTextToJsonSchema(arrayMatch[1])
268
+ };
269
+ }
270
+ const lowerType = type.toLowerCase();
271
+ switch (lowerType) {
272
+ case "string":
273
+ return {
274
+ type: "string"
275
+ };
276
+ case "number":
277
+ return {
278
+ type: "number"
279
+ };
280
+ case "integer":
281
+ return {
282
+ type: "integer"
283
+ };
284
+ case "boolean":
285
+ return {
286
+ type: "boolean"
287
+ };
288
+ case "object":
289
+ return {
290
+ type: "object"
291
+ };
292
+ case "any":
293
+ case "unknown":
294
+ return {};
295
+ // No constraints
296
+ case "null":
297
+ return {
298
+ type: "null"
299
+ };
300
+ }
301
+ if (type.includes("|")) {
302
+ const parts = type.split("|").map((p) => p.trim());
303
+ const nonNullParts = parts.filter((p) => p.toLowerCase() !== "null");
304
+ if (nonNullParts.length === 1) {
305
+ return typeTextToJsonSchema(nonNullParts[0]);
306
+ }
307
+ return {
308
+ anyOf: nonNullParts.map((p) => typeTextToJsonSchema(p))
309
+ };
310
+ }
311
+ return {
312
+ type: "object"
313
+ };
314
+ }
315
+ __name(typeTextToJsonSchema, "typeTextToJsonSchema");
316
+ function findSourceFile() {
317
+ const originalPrepareStackTrace = Error.prepareStackTrace;
318
+ try {
319
+ Error.prepareStackTrace = (_, stack2) => stack2;
320
+ const err = new Error();
321
+ const stack = err.stack;
322
+ for (const site of stack) {
323
+ const fileName = site.getFileName();
324
+ if (fileName && !fileName.includes("node_modules") && !fileName.includes("type-parser") && !fileName.includes("schema-generator") && (fileName.endsWith(".ts") || fileName.endsWith(".js"))) {
325
+ if (fileName.endsWith(".js")) {
326
+ const tsPath = fileName.replace(/\.js$/, ".ts");
327
+ if (fs.existsSync(tsPath)) {
328
+ return tsPath;
329
+ }
330
+ }
331
+ return fileName;
332
+ }
333
+ }
334
+ } finally {
335
+ Error.prepareStackTrace = originalPrepareStackTrace;
336
+ }
337
+ return null;
338
+ }
339
+ __name(findSourceFile, "findSourceFile");
340
+ function clearTypeCache() {
341
+ typeCache.clear();
342
+ }
343
+ __name(clearTypeCache, "clearTypeCache");
344
+
345
+ // src/schema-generator.ts
171
346
  function classToJsonSchema(classConstructor) {
172
347
  const instance = new classConstructor();
173
348
  const properties = {};
@@ -224,20 +399,45 @@ __name(Optional, "Optional");
224
399
  function SchemaConstraint(constraints) {
225
400
  return (target, propertyKey) => {
226
401
  Reflect.defineMetadata("schema:constraints", constraints, target, propertyKey);
402
+ const originalPrepareStackTrace = Error.prepareStackTrace;
403
+ try {
404
+ Error.prepareStackTrace = (_, stack2) => stack2;
405
+ const err = new Error();
406
+ const stack = err.stack;
407
+ for (const site of stack) {
408
+ const fileName = site.getFileName();
409
+ if (fileName && !fileName.includes("node_modules") && !fileName.includes("schema-generator") && (fileName.endsWith(".ts") || fileName.endsWith(".js"))) {
410
+ const className = target.constructor.name;
411
+ if (className && className !== "Object") {
412
+ registerClassSource(className, fileName);
413
+ }
414
+ break;
415
+ }
416
+ }
417
+ } finally {
418
+ Error.prepareStackTrace = originalPrepareStackTrace;
419
+ }
227
420
  };
228
421
  }
229
422
  __name(SchemaConstraint, "SchemaConstraint");
230
- function classToJsonSchemaWithConstraints(classConstructor) {
423
+ function classToJsonSchemaWithConstraints(classConstructor, sourceFilePath) {
231
424
  const instance = new classConstructor();
232
425
  const properties = {};
233
426
  const required = [];
427
+ const parsedTypes = parseClassTypesSync(classConstructor, sourceFilePath);
234
428
  const propertyNames = Object.keys(instance);
235
429
  for (const propertyName of propertyNames) {
236
430
  const propertyType = Reflect.getMetadata("design:type", instance, propertyName);
237
431
  const constraints = Reflect.getMetadata("schema:constraints", instance, propertyName);
238
432
  const isOptional = Reflect.getMetadata("optional", instance, propertyName);
239
- let jsonSchemaType = "string";
240
- if (propertyType) {
433
+ const parsedType = parsedTypes.get(propertyName);
434
+ let propertySchema;
435
+ if (parsedType && parsedType.type) {
436
+ propertySchema = {
437
+ ...parsedType
438
+ };
439
+ } else if (propertyType) {
440
+ let jsonSchemaType = "string";
241
441
  switch (propertyType.name) {
242
442
  case "String":
243
443
  jsonSchemaType = "string";
@@ -257,8 +457,16 @@ function classToJsonSchemaWithConstraints(classConstructor) {
257
457
  default:
258
458
  jsonSchemaType = "object";
259
459
  }
460
+ propertySchema = {
461
+ type: jsonSchemaType
462
+ };
260
463
  } else if (constraints) {
261
- if (constraints.minLength !== void 0 || constraints.maxLength !== void 0 || constraints.pattern) {
464
+ let jsonSchemaType = "string";
465
+ if (constraints.type) {
466
+ jsonSchemaType = constraints.type;
467
+ } else if (constraints.items) {
468
+ jsonSchemaType = "array";
469
+ } else if (constraints.minLength !== void 0 || constraints.maxLength !== void 0 || constraints.pattern) {
262
470
  jsonSchemaType = "string";
263
471
  } else if (constraints.minimum !== void 0 || constraints.maximum !== void 0) {
264
472
  jsonSchemaType = "number";
@@ -272,12 +480,25 @@ function classToJsonSchemaWithConstraints(classConstructor) {
272
480
  jsonSchemaType = "string";
273
481
  }
274
482
  }
483
+ propertySchema = {
484
+ type: jsonSchemaType
485
+ };
486
+ } else {
487
+ propertySchema = {
488
+ type: "string"
489
+ };
490
+ }
491
+ if (constraints) {
492
+ const parsedItems = propertySchema.items;
493
+ propertySchema = {
494
+ ...propertySchema,
495
+ ...constraints
496
+ };
497
+ if (parsedItems && !constraints.items) {
498
+ propertySchema.items = parsedItems;
499
+ }
275
500
  }
276
- const propertySchema = {
277
- type: jsonSchemaType,
278
- ...constraints || {}
279
- };
280
- if (jsonSchemaType === "array" && !propertySchema.items) {
501
+ if (propertySchema.type === "array" && !propertySchema.items) {
281
502
  propertySchema.items = {
282
503
  type: "string"
283
504
  };
@@ -1334,7 +1555,7 @@ var MCPServer = class {
1334
1555
  mcpDir = path.join(process.cwd(), "mcp");
1335
1556
  }
1336
1557
  }
1337
- if (fs.existsSync(mcpDir)) {
1558
+ if (fs2.existsSync(mcpDir)) {
1338
1559
  this.logger.debug(`Auto-discovering services from: ${mcpDir}`);
1339
1560
  await this.autoRegisterServices(mcpDir, serviceFactories);
1340
1561
  } else {
@@ -1584,7 +1805,7 @@ var MCPServer = class {
1584
1805
  */
1585
1806
  async autoRegisterServices(mcpDir, serviceFactories) {
1586
1807
  this.logger.debug(`Auto-registering services from: ${mcpDir}`);
1587
- if (!fs.existsSync(mcpDir)) {
1808
+ if (!fs2.existsSync(mcpDir)) {
1588
1809
  this.logger.warn(`MCP directory not found: ${mcpDir}`);
1589
1810
  return;
1590
1811
  }
@@ -1603,7 +1824,7 @@ var MCPServer = class {
1603
1824
  */
1604
1825
  findServiceFiles(dir) {
1605
1826
  const files = [];
1606
- const entries = fs.readdirSync(dir, {
1827
+ const entries = fs2.readdirSync(dir, {
1607
1828
  withFileTypes: true
1608
1829
  });
1609
1830
  for (const entry of entries) {
@@ -1738,7 +1959,7 @@ var MCPServer = class {
1738
1959
  }
1739
1960
  try {
1740
1961
  const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
1741
- if (!fs.existsSync(manifestPath)) {
1962
+ if (!fs2.existsSync(manifestPath)) {
1742
1963
  return;
1743
1964
  }
1744
1965
  if (this.logging) {
@@ -1781,7 +2002,7 @@ var MCPServer = class {
1781
2002
  async reloadUIManifest() {
1782
2003
  try {
1783
2004
  const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
1784
- if (!fs.existsSync(manifestPath)) {
2005
+ if (!fs2.existsSync(manifestPath)) {
1785
2006
  const uiResourceUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
1786
2007
  for (const uri of uiResourceUris) {
1787
2008
  this.resources.delete(uri);
@@ -1791,7 +2012,7 @@ var MCPServer = class {
1791
2012
  }
1792
2013
  return;
1793
2014
  }
1794
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
2015
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
1795
2016
  const currentUIUris = new Set(Object.keys(manifest));
1796
2017
  const registeredUIUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
1797
2018
  for (const uri of registeredUIUris) {
@@ -1807,7 +2028,7 @@ var MCPServer = class {
1807
2028
  const htmlPath = isString ? entry : entry.htmlPath;
1808
2029
  const isGPTApp = !isString && entry.isGPTApp;
1809
2030
  const gptMeta = !isString ? entry.gptMeta : void 0;
1810
- if (!fs.existsSync(htmlPath)) {
2031
+ if (!fs2.existsSync(htmlPath)) {
1811
2032
  if (this.logging) {
1812
2033
  this.logger.warn(`UI HTML file not found: ${htmlPath}`);
1813
2034
  }
@@ -1828,8 +2049,8 @@ var MCPServer = class {
1828
2049
  mimeType,
1829
2050
  inputSchema: void 0,
1830
2051
  method: /* @__PURE__ */ __name(async () => {
1831
- if (fs.existsSync(htmlPath)) {
1832
- const html = fs.readFileSync(htmlPath, "utf-8");
2052
+ if (fs2.existsSync(htmlPath)) {
2053
+ const html = fs2.readFileSync(htmlPath, "utf-8");
1833
2054
  return {
1834
2055
  text: html,
1835
2056
  _meta: Object.keys(_meta).length > 0 ? _meta : void 0
@@ -1858,10 +2079,10 @@ var MCPServer = class {
1858
2079
  async loadUIManifest() {
1859
2080
  try {
1860
2081
  const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
1861
- if (!fs.existsSync(manifestPath)) {
2082
+ if (!fs2.existsSync(manifestPath)) {
1862
2083
  return;
1863
2084
  }
1864
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
2085
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
1865
2086
  for (const [uri, entry] of Object.entries(manifest)) {
1866
2087
  const isString = typeof entry === "string";
1867
2088
  const htmlPath = isString ? entry : entry.htmlPath;
@@ -1873,13 +2094,13 @@ var MCPServer = class {
1873
2094
  }
1874
2095
  continue;
1875
2096
  }
1876
- if (!fs.existsSync(htmlPath)) {
2097
+ if (!fs2.existsSync(htmlPath)) {
1877
2098
  if (this.logging) {
1878
2099
  this.logger.warn(`UI HTML file not found: ${htmlPath}`);
1879
2100
  }
1880
2101
  continue;
1881
2102
  }
1882
- const html = fs.readFileSync(htmlPath, "utf-8");
2103
+ const html = fs2.readFileSync(htmlPath, "utf-8");
1883
2104
  const mimeType = isGPTApp ? "text/html+skybridge" : "text/html;profile=mcp-app";
1884
2105
  const _meta = {};
1885
2106
  if (isGPTApp) {
@@ -2131,18 +2352,18 @@ var MCPServerRuntime = class {
2131
2352
  }
2132
2353
  async loadServices() {
2133
2354
  const absPath = path.resolve(this.options.servicesDir);
2134
- if (!fs.existsSync(absPath)) {
2355
+ if (!fs2.existsSync(absPath)) {
2135
2356
  this.logger.error(`Services directory not found: ${absPath}`);
2136
2357
  return;
2137
2358
  }
2138
- const files = fs.readdirSync(absPath);
2359
+ const files = fs2.readdirSync(absPath);
2139
2360
  let toolCount = 0;
2140
2361
  let promptCount = 0;
2141
2362
  let resourceCount = 0;
2142
2363
  for (const dir of files) {
2143
2364
  const modulePath = path.join(absPath, dir, "index.ts");
2144
2365
  const modulePathJs = path.join(absPath, dir, "index.js");
2145
- const finalPath = fs.existsSync(modulePath) ? modulePath : fs.existsSync(modulePathJs) ? modulePathJs : null;
2366
+ const finalPath = fs2.existsSync(modulePath) ? modulePath : fs2.existsSync(modulePathJs) ? modulePathJs : null;
2146
2367
  if (finalPath) {
2147
2368
  try {
2148
2369
  const fileUrl = pathToFileURL(finalPath).href;
@@ -2277,6 +2498,7 @@ export {
2277
2498
  UserEnvs,
2278
2499
  classToJsonSchema,
2279
2500
  classToJsonSchemaWithConstraints,
2501
+ clearTypeCache,
2280
2502
  createAuthError,
2281
2503
  createHTTPServer,
2282
2504
  createProtectedResourceMetadata,
@@ -2285,6 +2507,8 @@ export {
2285
2507
  getDecoratedMethods,
2286
2508
  getMethodMetadata,
2287
2509
  isAuthError,
2510
+ parseClassTypesSync,
2511
+ registerClassSource,
2288
2512
  startMCPServer,
2289
2513
  validateNonEmpty,
2290
2514
  validatePath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/core",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Core library implementing decorators, reflection, and MCP runtime server",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -30,7 +30,8 @@
30
30
  "ajv": "^8.12.0",
31
31
  "chokidar": "^4.0.0",
32
32
  "dotenv": "^16.3.1",
33
- "reflect-metadata": "^0.2.1"
33
+ "reflect-metadata": "^0.2.1",
34
+ "ts-morph": "^24.0.0"
34
35
  },
35
36
  "peerDependencies": {
36
37
  "cors": "^2.8.5",