@leanmcp/core 0.4.4 → 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,11 +382,31 @@ declare function SchemaConstraint(constraints: {
382
382
  enum?: any[];
383
383
  description?: string;
384
384
  default?: any;
385
+ type?: string;
385
386
  }): PropertyDecorator;
386
387
  /**
387
- * 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[]`).
388
391
  */
389
- declare function classToJsonSchemaWithConstraints(classConstructor: new () => any): any;
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)
408
+ */
409
+ declare function clearTypeCache(): void;
390
410
 
391
411
  /**
392
412
  * Input validation utilities for LeanMCP
@@ -965,4 +985,4 @@ declare class MCPServerRuntime {
965
985
  */
966
986
  declare function startMCPServer(options: MCPServerOptions): Promise<MCPServerRuntime>;
967
987
 
968
- 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,11 +382,31 @@ declare function SchemaConstraint(constraints: {
382
382
  enum?: any[];
383
383
  description?: string;
384
384
  default?: any;
385
+ type?: string;
385
386
  }): PropertyDecorator;
386
387
  /**
387
- * 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[]`).
388
391
  */
389
- declare function classToJsonSchemaWithConstraints(classConstructor: new () => any): any;
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)
408
+ */
409
+ declare function clearTypeCache(): void;
390
410
 
391
411
  /**
392
412
  * Input validation utilities for LeanMCP
@@ -965,4 +985,4 @@ declare class MCPServerRuntime {
965
985
  */
966
986
  declare function startMCPServer(options: MCPServerOptions): Promise<MCPServerRuntime>;
967
987
 
968
- 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,11 +500,30 @@ 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
- properties[propertyName] = {
292
- type: jsonSchemaType,
293
- ...constraints || {}
294
- };
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) {
522
+ propertySchema.items = {
523
+ type: "string"
524
+ };
525
+ }
526
+ properties[propertyName] = propertySchema;
295
527
  if (!isOptional) {
296
528
  required.push(propertyName);
297
529
  }
@@ -307,6 +539,7 @@ var init_schema_generator = __esm({
307
539
  "src/schema-generator.ts"() {
308
540
  "use strict";
309
541
  import_reflect_metadata2 = require("reflect-metadata");
542
+ init_type_parser();
310
543
  __name(classToJsonSchema, "classToJsonSchema");
311
544
  __name(Optional, "Optional");
312
545
  __name(SchemaConstraint, "SchemaConstraint");
@@ -1611,6 +1844,7 @@ __export(index_exports, {
1611
1844
  UserEnvs: () => UserEnvs,
1612
1845
  classToJsonSchema: () => classToJsonSchema,
1613
1846
  classToJsonSchemaWithConstraints: () => classToJsonSchemaWithConstraints,
1847
+ clearTypeCache: () => clearTypeCache,
1614
1848
  createAuthError: () => createAuthError,
1615
1849
  createHTTPServer: () => createHTTPServer,
1616
1850
  createProtectedResourceMetadata: () => createProtectedResourceMetadata,
@@ -1619,6 +1853,8 @@ __export(index_exports, {
1619
1853
  getDecoratedMethods: () => getDecoratedMethods,
1620
1854
  getMethodMetadata: () => getMethodMetadata,
1621
1855
  isAuthError: () => isAuthError,
1856
+ parseClassTypesSync: () => parseClassTypesSync,
1857
+ registerClassSource: () => registerClassSource,
1622
1858
  startMCPServer: () => startMCPServer,
1623
1859
  validateNonEmpty: () => validateNonEmpty,
1624
1860
  validatePath: () => validatePath,
@@ -1632,11 +1868,11 @@ async function startMCPServer(options) {
1632
1868
  await runtime.start();
1633
1869
  return runtime;
1634
1870
  }
1635
- 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;
1636
1872
  var init_index = __esm({
1637
1873
  "src/index.ts"() {
1638
1874
  import_reflect_metadata3 = require("reflect-metadata");
1639
- import_fs = __toESM(require("fs"));
1875
+ import_fs2 = __toESM(require("fs"));
1640
1876
  import_path = __toESM(require("path"));
1641
1877
  import_url = require("url");
1642
1878
  import_server = require("@modelcontextprotocol/sdk/server/index.js");
@@ -1645,6 +1881,7 @@ var init_index = __esm({
1645
1881
  import_ajv = __toESM(require("ajv"));
1646
1882
  init_decorators();
1647
1883
  init_schema_generator();
1884
+ init_type_parser();
1648
1885
  init_http_server();
1649
1886
  init_logger();
1650
1887
  init_validation();
@@ -1733,7 +1970,7 @@ var init_index = __esm({
1733
1970
  mcpDir = import_path.default.join(process.cwd(), "mcp");
1734
1971
  }
1735
1972
  }
1736
- if (import_fs.default.existsSync(mcpDir)) {
1973
+ if (import_fs2.default.existsSync(mcpDir)) {
1737
1974
  this.logger.debug(`Auto-discovering services from: ${mcpDir}`);
1738
1975
  await this.autoRegisterServices(mcpDir, serviceFactories);
1739
1976
  } else {
@@ -1983,7 +2220,7 @@ var init_index = __esm({
1983
2220
  */
1984
2221
  async autoRegisterServices(mcpDir, serviceFactories) {
1985
2222
  this.logger.debug(`Auto-registering services from: ${mcpDir}`);
1986
- if (!import_fs.default.existsSync(mcpDir)) {
2223
+ if (!import_fs2.default.existsSync(mcpDir)) {
1987
2224
  this.logger.warn(`MCP directory not found: ${mcpDir}`);
1988
2225
  return;
1989
2226
  }
@@ -2002,7 +2239,7 @@ var init_index = __esm({
2002
2239
  */
2003
2240
  findServiceFiles(dir) {
2004
2241
  const files = [];
2005
- const entries = import_fs.default.readdirSync(dir, {
2242
+ const entries = import_fs2.default.readdirSync(dir, {
2006
2243
  withFileTypes: true
2007
2244
  });
2008
2245
  for (const entry of entries) {
@@ -2137,7 +2374,7 @@ var init_index = __esm({
2137
2374
  }
2138
2375
  try {
2139
2376
  const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
2140
- if (!import_fs.default.existsSync(manifestPath)) {
2377
+ if (!import_fs2.default.existsSync(manifestPath)) {
2141
2378
  return;
2142
2379
  }
2143
2380
  if (this.logging) {
@@ -2180,7 +2417,7 @@ var init_index = __esm({
2180
2417
  async reloadUIManifest() {
2181
2418
  try {
2182
2419
  const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
2183
- if (!import_fs.default.existsSync(manifestPath)) {
2420
+ if (!import_fs2.default.existsSync(manifestPath)) {
2184
2421
  const uiResourceUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
2185
2422
  for (const uri of uiResourceUris) {
2186
2423
  this.resources.delete(uri);
@@ -2190,7 +2427,7 @@ var init_index = __esm({
2190
2427
  }
2191
2428
  return;
2192
2429
  }
2193
- const manifest = JSON.parse(import_fs.default.readFileSync(manifestPath, "utf-8"));
2430
+ const manifest = JSON.parse(import_fs2.default.readFileSync(manifestPath, "utf-8"));
2194
2431
  const currentUIUris = new Set(Object.keys(manifest));
2195
2432
  const registeredUIUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
2196
2433
  for (const uri of registeredUIUris) {
@@ -2206,7 +2443,7 @@ var init_index = __esm({
2206
2443
  const htmlPath = isString ? entry : entry.htmlPath;
2207
2444
  const isGPTApp = !isString && entry.isGPTApp;
2208
2445
  const gptMeta = !isString ? entry.gptMeta : void 0;
2209
- if (!import_fs.default.existsSync(htmlPath)) {
2446
+ if (!import_fs2.default.existsSync(htmlPath)) {
2210
2447
  if (this.logging) {
2211
2448
  this.logger.warn(`UI HTML file not found: ${htmlPath}`);
2212
2449
  }
@@ -2227,8 +2464,8 @@ var init_index = __esm({
2227
2464
  mimeType,
2228
2465
  inputSchema: void 0,
2229
2466
  method: /* @__PURE__ */ __name(async () => {
2230
- if (import_fs.default.existsSync(htmlPath)) {
2231
- 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");
2232
2469
  return {
2233
2470
  text: html,
2234
2471
  _meta: Object.keys(_meta).length > 0 ? _meta : void 0
@@ -2257,10 +2494,10 @@ var init_index = __esm({
2257
2494
  async loadUIManifest() {
2258
2495
  try {
2259
2496
  const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
2260
- if (!import_fs.default.existsSync(manifestPath)) {
2497
+ if (!import_fs2.default.existsSync(manifestPath)) {
2261
2498
  return;
2262
2499
  }
2263
- const manifest = JSON.parse(import_fs.default.readFileSync(manifestPath, "utf-8"));
2500
+ const manifest = JSON.parse(import_fs2.default.readFileSync(manifestPath, "utf-8"));
2264
2501
  for (const [uri, entry] of Object.entries(manifest)) {
2265
2502
  const isString = typeof entry === "string";
2266
2503
  const htmlPath = isString ? entry : entry.htmlPath;
@@ -2272,13 +2509,13 @@ var init_index = __esm({
2272
2509
  }
2273
2510
  continue;
2274
2511
  }
2275
- if (!import_fs.default.existsSync(htmlPath)) {
2512
+ if (!import_fs2.default.existsSync(htmlPath)) {
2276
2513
  if (this.logging) {
2277
2514
  this.logger.warn(`UI HTML file not found: ${htmlPath}`);
2278
2515
  }
2279
2516
  continue;
2280
2517
  }
2281
- const html = import_fs.default.readFileSync(htmlPath, "utf-8");
2518
+ const html = import_fs2.default.readFileSync(htmlPath, "utf-8");
2282
2519
  const mimeType = isGPTApp ? "text/html+skybridge" : "text/html;profile=mcp-app";
2283
2520
  const _meta = {};
2284
2521
  if (isGPTApp) {
@@ -2530,18 +2767,18 @@ var init_index = __esm({
2530
2767
  }
2531
2768
  async loadServices() {
2532
2769
  const absPath = import_path.default.resolve(this.options.servicesDir);
2533
- if (!import_fs.default.existsSync(absPath)) {
2770
+ if (!import_fs2.default.existsSync(absPath)) {
2534
2771
  this.logger.error(`Services directory not found: ${absPath}`);
2535
2772
  return;
2536
2773
  }
2537
- const files = import_fs.default.readdirSync(absPath);
2774
+ const files = import_fs2.default.readdirSync(absPath);
2538
2775
  let toolCount = 0;
2539
2776
  let promptCount = 0;
2540
2777
  let resourceCount = 0;
2541
2778
  for (const dir of files) {
2542
2779
  const modulePath = import_path.default.join(absPath, dir, "index.ts");
2543
2780
  const modulePathJs = import_path.default.join(absPath, dir, "index.js");
2544
- 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;
2545
2782
  if (finalPath) {
2546
2783
  try {
2547
2784
  const fileUrl = (0, import_url.pathToFileURL)(finalPath).href;
@@ -2675,6 +2912,7 @@ init_index();
2675
2912
  UserEnvs,
2676
2913
  classToJsonSchema,
2677
2914
  classToJsonSchemaWithConstraints,
2915
+ clearTypeCache,
2678
2916
  createAuthError,
2679
2917
  createHTTPServer,
2680
2918
  createProtectedResourceMetadata,
@@ -2683,6 +2921,8 @@ init_index();
2683
2921
  getDecoratedMethods,
2684
2922
  getMethodMetadata,
2685
2923
  isAuthError,
2924
+ parseClassTypesSync,
2925
+ registerClassSource,
2686
2926
  startMCPServer,
2687
2927
  validateNonEmpty,
2688
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,11 +480,30 @@ 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
+ };
275
490
  }
276
- properties[propertyName] = {
277
- type: jsonSchemaType,
278
- ...constraints || {}
279
- };
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
+ }
500
+ }
501
+ if (propertySchema.type === "array" && !propertySchema.items) {
502
+ propertySchema.items = {
503
+ type: "string"
504
+ };
505
+ }
506
+ properties[propertyName] = propertySchema;
280
507
  if (!isOptional) {
281
508
  required.push(propertyName);
282
509
  }
@@ -1328,7 +1555,7 @@ var MCPServer = class {
1328
1555
  mcpDir = path.join(process.cwd(), "mcp");
1329
1556
  }
1330
1557
  }
1331
- if (fs.existsSync(mcpDir)) {
1558
+ if (fs2.existsSync(mcpDir)) {
1332
1559
  this.logger.debug(`Auto-discovering services from: ${mcpDir}`);
1333
1560
  await this.autoRegisterServices(mcpDir, serviceFactories);
1334
1561
  } else {
@@ -1578,7 +1805,7 @@ var MCPServer = class {
1578
1805
  */
1579
1806
  async autoRegisterServices(mcpDir, serviceFactories) {
1580
1807
  this.logger.debug(`Auto-registering services from: ${mcpDir}`);
1581
- if (!fs.existsSync(mcpDir)) {
1808
+ if (!fs2.existsSync(mcpDir)) {
1582
1809
  this.logger.warn(`MCP directory not found: ${mcpDir}`);
1583
1810
  return;
1584
1811
  }
@@ -1597,7 +1824,7 @@ var MCPServer = class {
1597
1824
  */
1598
1825
  findServiceFiles(dir) {
1599
1826
  const files = [];
1600
- const entries = fs.readdirSync(dir, {
1827
+ const entries = fs2.readdirSync(dir, {
1601
1828
  withFileTypes: true
1602
1829
  });
1603
1830
  for (const entry of entries) {
@@ -1732,7 +1959,7 @@ var MCPServer = class {
1732
1959
  }
1733
1960
  try {
1734
1961
  const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
1735
- if (!fs.existsSync(manifestPath)) {
1962
+ if (!fs2.existsSync(manifestPath)) {
1736
1963
  return;
1737
1964
  }
1738
1965
  if (this.logging) {
@@ -1775,7 +2002,7 @@ var MCPServer = class {
1775
2002
  async reloadUIManifest() {
1776
2003
  try {
1777
2004
  const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
1778
- if (!fs.existsSync(manifestPath)) {
2005
+ if (!fs2.existsSync(manifestPath)) {
1779
2006
  const uiResourceUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
1780
2007
  for (const uri of uiResourceUris) {
1781
2008
  this.resources.delete(uri);
@@ -1785,7 +2012,7 @@ var MCPServer = class {
1785
2012
  }
1786
2013
  return;
1787
2014
  }
1788
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
2015
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
1789
2016
  const currentUIUris = new Set(Object.keys(manifest));
1790
2017
  const registeredUIUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
1791
2018
  for (const uri of registeredUIUris) {
@@ -1801,7 +2028,7 @@ var MCPServer = class {
1801
2028
  const htmlPath = isString ? entry : entry.htmlPath;
1802
2029
  const isGPTApp = !isString && entry.isGPTApp;
1803
2030
  const gptMeta = !isString ? entry.gptMeta : void 0;
1804
- if (!fs.existsSync(htmlPath)) {
2031
+ if (!fs2.existsSync(htmlPath)) {
1805
2032
  if (this.logging) {
1806
2033
  this.logger.warn(`UI HTML file not found: ${htmlPath}`);
1807
2034
  }
@@ -1822,8 +2049,8 @@ var MCPServer = class {
1822
2049
  mimeType,
1823
2050
  inputSchema: void 0,
1824
2051
  method: /* @__PURE__ */ __name(async () => {
1825
- if (fs.existsSync(htmlPath)) {
1826
- const html = fs.readFileSync(htmlPath, "utf-8");
2052
+ if (fs2.existsSync(htmlPath)) {
2053
+ const html = fs2.readFileSync(htmlPath, "utf-8");
1827
2054
  return {
1828
2055
  text: html,
1829
2056
  _meta: Object.keys(_meta).length > 0 ? _meta : void 0
@@ -1852,10 +2079,10 @@ var MCPServer = class {
1852
2079
  async loadUIManifest() {
1853
2080
  try {
1854
2081
  const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
1855
- if (!fs.existsSync(manifestPath)) {
2082
+ if (!fs2.existsSync(manifestPath)) {
1856
2083
  return;
1857
2084
  }
1858
- const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
2085
+ const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
1859
2086
  for (const [uri, entry] of Object.entries(manifest)) {
1860
2087
  const isString = typeof entry === "string";
1861
2088
  const htmlPath = isString ? entry : entry.htmlPath;
@@ -1867,13 +2094,13 @@ var MCPServer = class {
1867
2094
  }
1868
2095
  continue;
1869
2096
  }
1870
- if (!fs.existsSync(htmlPath)) {
2097
+ if (!fs2.existsSync(htmlPath)) {
1871
2098
  if (this.logging) {
1872
2099
  this.logger.warn(`UI HTML file not found: ${htmlPath}`);
1873
2100
  }
1874
2101
  continue;
1875
2102
  }
1876
- const html = fs.readFileSync(htmlPath, "utf-8");
2103
+ const html = fs2.readFileSync(htmlPath, "utf-8");
1877
2104
  const mimeType = isGPTApp ? "text/html+skybridge" : "text/html;profile=mcp-app";
1878
2105
  const _meta = {};
1879
2106
  if (isGPTApp) {
@@ -2125,18 +2352,18 @@ var MCPServerRuntime = class {
2125
2352
  }
2126
2353
  async loadServices() {
2127
2354
  const absPath = path.resolve(this.options.servicesDir);
2128
- if (!fs.existsSync(absPath)) {
2355
+ if (!fs2.existsSync(absPath)) {
2129
2356
  this.logger.error(`Services directory not found: ${absPath}`);
2130
2357
  return;
2131
2358
  }
2132
- const files = fs.readdirSync(absPath);
2359
+ const files = fs2.readdirSync(absPath);
2133
2360
  let toolCount = 0;
2134
2361
  let promptCount = 0;
2135
2362
  let resourceCount = 0;
2136
2363
  for (const dir of files) {
2137
2364
  const modulePath = path.join(absPath, dir, "index.ts");
2138
2365
  const modulePathJs = path.join(absPath, dir, "index.js");
2139
- const finalPath = fs.existsSync(modulePath) ? modulePath : fs.existsSync(modulePathJs) ? modulePathJs : null;
2366
+ const finalPath = fs2.existsSync(modulePath) ? modulePath : fs2.existsSync(modulePathJs) ? modulePathJs : null;
2140
2367
  if (finalPath) {
2141
2368
  try {
2142
2369
  const fileUrl = pathToFileURL(finalPath).href;
@@ -2271,6 +2498,7 @@ export {
2271
2498
  UserEnvs,
2272
2499
  classToJsonSchema,
2273
2500
  classToJsonSchemaWithConstraints,
2501
+ clearTypeCache,
2274
2502
  createAuthError,
2275
2503
  createHTTPServer,
2276
2504
  createProtectedResourceMetadata,
@@ -2279,6 +2507,8 @@ export {
2279
2507
  getDecoratedMethods,
2280
2508
  getMethodMetadata,
2281
2509
  isAuthError,
2510
+ parseClassTypesSync,
2511
+ registerClassSource,
2282
2512
  startMCPServer,
2283
2513
  validateNonEmpty,
2284
2514
  validatePath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/core",
3
- "version": "0.4.4",
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",
@@ -75,4 +76,4 @@
75
76
  "publishConfig": {
76
77
  "access": "public"
77
78
  }
78
- }
79
+ }