obsidian-mcp-server 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,30 @@
1
1
  import { PropertyManager } from "../properties/manager.js";
2
2
  import { BaseToolHandler } from "../base.js";
3
- import { createLogger } from "../../utils/logging.js";
3
+ import { createLogger, ErrorCategoryType } from "../../utils/logging.js";
4
4
  // Create a logger for complex search operations
5
5
  const logger = createLogger('ComplexSearchTools');
6
+ /**
7
+ * Helper function to safely convert any error to an object
8
+ */
9
+ function errorToObject(error) {
10
+ if (error instanceof Error) {
11
+ return {
12
+ name: error.name,
13
+ message: error.message,
14
+ stack: error.stack,
15
+ errorCategory: ErrorCategoryType.CATEGORY_SYSTEM
16
+ };
17
+ }
18
+ else if (typeof error === 'object' && error !== null) {
19
+ return error;
20
+ }
21
+ else {
22
+ return {
23
+ error: String(error),
24
+ errorCategory: ErrorCategoryType.CATEGORY_UNKNOWN
25
+ };
26
+ }
27
+ }
6
28
  /**
7
29
  * Tool name for complex search
8
30
  */
@@ -49,8 +71,11 @@ export class ComplexSearchToolHandler extends BaseToolHandler {
49
71
  };
50
72
  }
51
73
  async runTool(args) {
74
+ logger.startTimer('complex_search');
52
75
  try {
53
- logger.debug(`Executing complex search with query: ${JSON.stringify(args.query)}`);
76
+ logger.debug(`Executing complex search with query: ${JSON.stringify(args.query)}`, {
77
+ queryType: Object.keys(args.query)[0]
78
+ });
54
79
  // Perform search
55
80
  const results = await this.client.searchJson(args.query);
56
81
  // Format response based on result type
@@ -71,6 +96,11 @@ export class ComplexSearchToolHandler extends BaseToolHandler {
71
96
  };
72
97
  }
73
98
  });
99
+ const elapsedMs = logger.endTimer('complex_search');
100
+ logger.logOperationResult(true, 'complex_search', elapsedMs, {
101
+ resultCount: results.length,
102
+ queryType: Object.keys(args.query)[0]
103
+ });
74
104
  logger.debug(`Complex search found ${results.length} results`);
75
105
  return this.createResponse({
76
106
  message: `Found ${results.length} result(s)`,
@@ -78,7 +108,11 @@ export class ComplexSearchToolHandler extends BaseToolHandler {
78
108
  });
79
109
  }
80
110
  catch (error) {
81
- logger.error(`Complex search error: ${error}`);
111
+ const elapsedMs = logger.endTimer('complex_search');
112
+ logger.logOperationResult(false, 'complex_search', elapsedMs, {
113
+ queryType: Object.keys(args.query)[0]
114
+ });
115
+ logger.error(`Complex search error`, errorToObject(error));
82
116
  return this.handleError(error);
83
117
  }
84
118
  }
@@ -142,6 +176,8 @@ export class GetTagsToolHandler extends BaseToolHandler {
142
176
  };
143
177
  }
144
178
  async runTool(args) {
179
+ const operationId = `get_tags_${args.path || 'vault'}`;
180
+ logger.startTimer(operationId);
145
181
  try {
146
182
  logger.debug(`Getting tags${args.path ? ` in path: ${args.path}` : ' in whole vault'}`);
147
183
  const tagMap = new Map();
@@ -151,6 +187,7 @@ export class GetTagsToolHandler extends BaseToolHandler {
151
187
  : { "glob": ["**/*.md", { "var": "path" }] };
152
188
  const results = await this.client.searchJson(query);
153
189
  let scannedFiles = 0;
190
+ let processedSuccessfully = 0;
154
191
  // Process each file to extract tags from frontmatter
155
192
  for (const result of results) {
156
193
  if (!('filename' in result))
@@ -171,14 +208,23 @@ export class GetTagsToolHandler extends BaseToolHandler {
171
208
  tagMap.get(cleanTag).add(result.filename);
172
209
  }
173
210
  }
211
+ processedSuccessfully++;
174
212
  }
175
213
  catch (error) {
176
- logger.error(`Failed to process file ${result.filename}:`, error);
214
+ logger.error(`Failed to process file ${result.filename}:`, errorToObject(error));
177
215
  }
178
216
  }
179
217
  // Calculate total occurrences
180
218
  const totalOccurrences = Array.from(tagMap.values())
181
219
  .reduce((sum, files) => sum + files.size, 0);
220
+ const elapsedMs = logger.endTimer(operationId);
221
+ logger.logOperationResult(true, 'get_tags', elapsedMs, {
222
+ uniqueTags: tagMap.size,
223
+ totalOccurrences,
224
+ scannedFiles,
225
+ processedSuccessfully,
226
+ searchPath: args.path || 'vault'
227
+ });
182
228
  return this.createResponse({
183
229
  tags: Array.from(tagMap.entries())
184
230
  .map(([name, files]) => ({
@@ -196,6 +242,11 @@ export class GetTagsToolHandler extends BaseToolHandler {
196
242
  });
197
243
  }
198
244
  catch (error) {
245
+ const elapsedMs = logger.endTimer(operationId);
246
+ logger.logOperationResult(false, 'get_tags', elapsedMs, {
247
+ searchPath: args.path || 'vault',
248
+ error: errorToObject(error)
249
+ });
199
250
  return this.handleError(error);
200
251
  }
201
252
  }
@@ -1,17 +1,66 @@
1
1
  /**
2
- * Logging utilities for the Obsidian MCP Server
2
+ * Standardized Logging System for the Obsidian MCP Server
3
+ *
4
+ * This module provides structured logging capabilities aligned with MCP best practices.
5
+ * It supports categorized errors, severity levels, context tracking, performance
6
+ * monitoring, and secure logging patterns.
7
+ *
8
+ * @module utils/logging
3
9
  */
10
+ import fs from "fs";
11
+ import path from "path";
12
+ import process from "process";
13
+ import winston from "winston";
4
14
  /**
5
- * Log levels
15
+ * Error categories following MCP standards
16
+ */
17
+ export var ErrorCategoryType;
18
+ (function (ErrorCategoryType) {
19
+ ErrorCategoryType["CATEGORY_VALIDATION"] = "VALIDATION";
20
+ ErrorCategoryType["CATEGORY_AUTHENTICATION"] = "AUTHENTICATION";
21
+ ErrorCategoryType["CATEGORY_AUTHORIZATION"] = "AUTHORIZATION";
22
+ ErrorCategoryType["CATEGORY_BUSINESS_LOGIC"] = "BUSINESS_LOGIC";
23
+ ErrorCategoryType["CATEGORY_DATA_ACCESS"] = "DATA_ACCESS";
24
+ ErrorCategoryType["CATEGORY_EXTERNAL_SERVICE"] = "EXTERNAL_SERVICE";
25
+ ErrorCategoryType["CATEGORY_SYSTEM"] = "SYSTEM";
26
+ ErrorCategoryType["CATEGORY_UNKNOWN"] = "UNKNOWN";
27
+ })(ErrorCategoryType || (ErrorCategoryType = {}));
28
+ /**
29
+ * Log levels aligned with MCP's ErrorSeverityLevel
6
30
  */
7
31
  export var LogLevel;
8
32
  (function (LogLevel) {
33
+ /** Critical errors that require immediate attention */
9
34
  LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
35
+ /** Potentially harmful situations that should be reviewed */
10
36
  LogLevel[LogLevel["WARN"] = 1] = "WARN";
37
+ /** General informational messages about system operation */
11
38
  LogLevel[LogLevel["INFO"] = 2] = "INFO";
39
+ /** Detailed information for debugging purposes */
12
40
  LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
41
+ /** Highly detailed tracing information */
13
42
  LogLevel[LogLevel["TRACE"] = 4] = "TRACE";
14
43
  })(LogLevel || (LogLevel = {}));
44
+ /**
45
+ * Maps LogLevel to MCP severity level for integration with MCP error handling
46
+ */
47
+ export const logLevelToMcpSeverity = {
48
+ [LogLevel.ERROR]: 3, // SEVERITY_ERROR
49
+ [LogLevel.WARN]: 2, // SEVERITY_WARN
50
+ [LogLevel.INFO]: 1, // SEVERITY_INFO
51
+ [LogLevel.DEBUG]: 0, // SEVERITY_DEBUG
52
+ [LogLevel.TRACE]: 0 // Also maps to SEVERITY_DEBUG
53
+ };
54
+ /**
55
+ * Winston-compatible log levels
56
+ */
57
+ const winstonLogLevels = {
58
+ error: 0,
59
+ warn: 1,
60
+ info: 2,
61
+ debug: 3,
62
+ trace: 4
63
+ };
15
64
  /**
16
65
  * Default logger configuration
17
66
  */
@@ -20,100 +69,349 @@ const DEFAULT_CONFIG = {
20
69
  ? LogLevel.INFO
21
70
  : LogLevel.DEBUG,
22
71
  includeTimestamps: true,
23
- includeLevel: true
72
+ includeLevel: true,
73
+ maskSensitiveData: true,
74
+ sensitiveFields: ['password', 'token', 'secret', 'key', 'auth', 'credential'],
75
+ logDir: "logs",
76
+ files: true,
77
+ fileNames: {
78
+ combined: "combined.log",
79
+ error: "error.log",
80
+ warn: "warn.log",
81
+ info: "info.log",
82
+ debug: "debug.log"
83
+ }
24
84
  };
25
85
  /**
26
- * Simple logger for MCP server operations
86
+ * Enhanced logger for MCP server operations with structured logging support
87
+ * Implements file-based logging with zero console output
27
88
  */
28
89
  export class Logger {
29
90
  name;
30
91
  config;
92
+ timers = new Map();
93
+ logger;
94
+ /**
95
+ * Creates a new logger instance
96
+ *
97
+ * @param name - Component name for this logger
98
+ * @param config - Optional configuration overrides
99
+ */
31
100
  constructor(name, config = {}) {
32
101
  this.name = name;
33
- this.config = { ...DEFAULT_CONFIG, ...config };
102
+ this.config = {
103
+ ...DEFAULT_CONFIG,
104
+ ...config,
105
+ fileNames: {
106
+ ...DEFAULT_CONFIG.fileNames,
107
+ ...config.fileNames
108
+ }
109
+ };
110
+ // Initialize logger with silent transport as fallback
111
+ this.logger = winston.createLogger({
112
+ levels: winstonLogLevels,
113
+ defaultMeta: { component: this.name },
114
+ transports: [
115
+ // Silent transport to prevent "no transports" warning
116
+ new winston.transports.Console({
117
+ silent: true
118
+ })
119
+ ]
120
+ });
121
+ this.initializeLogger();
34
122
  }
35
123
  /**
36
- * Internal method to format and output a log message
124
+ * Initialize or reinitialize the Winston logger
37
125
  */
38
- log(level, message, ...args) {
39
- if (level > this.config.level)
40
- return;
41
- const parts = [];
42
- // Add timestamp if configured
43
- if (this.config.includeTimestamps) {
44
- parts.push(`[${new Date().toISOString()}]`);
126
+ initializeLogger() {
127
+ if (this.config.files && this.config.logDir) {
128
+ if (!fs.existsSync(this.config.logDir)) {
129
+ try {
130
+ fs.mkdirSync(this.config.logDir, { recursive: true });
131
+ }
132
+ catch (error) {
133
+ // Silently continue - we have a silent transport as fallback
134
+ }
135
+ }
45
136
  }
46
- // Add level if configured
47
- if (this.config.includeLevel) {
48
- const levelStr = LogLevel[level] || 'UNKNOWN';
49
- parts.push(`[${levelStr}]`);
137
+ // Create log format
138
+ const logFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json());
139
+ // Initialize transports array with a silent transport to avoid "no transports" warning
140
+ const transports = [
141
+ new winston.transports.Console({
142
+ silent: true, // Silent transport that doesn't output logs
143
+ format: logFormat
144
+ })
145
+ ];
146
+ // Add file transports if enabled
147
+ if (this.config.files && this.config.logDir) {
148
+ try {
149
+ const fileNames = this.config.fileNames || DEFAULT_CONFIG.fileNames;
150
+ // Combined log file
151
+ if (fileNames?.combined) {
152
+ transports.push(new winston.transports.File({
153
+ filename: path.join(this.config.logDir, fileNames.combined),
154
+ format: logFormat
155
+ }));
156
+ }
157
+ // Level-specific log files
158
+ if (fileNames?.error) {
159
+ transports.push(new winston.transports.File({
160
+ filename: path.join(this.config.logDir, fileNames.error),
161
+ level: 'error',
162
+ format: logFormat
163
+ }));
164
+ }
165
+ if (fileNames?.warn) {
166
+ transports.push(new winston.transports.File({
167
+ filename: path.join(this.config.logDir, fileNames.warn),
168
+ level: 'warn',
169
+ format: logFormat
170
+ }));
171
+ }
172
+ if (fileNames?.info) {
173
+ transports.push(new winston.transports.File({
174
+ filename: path.join(this.config.logDir, fileNames.info),
175
+ level: 'info',
176
+ format: logFormat
177
+ }));
178
+ }
179
+ if (fileNames?.debug) {
180
+ transports.push(new winston.transports.File({
181
+ filename: path.join(this.config.logDir, fileNames.debug),
182
+ level: 'debug',
183
+ format: logFormat
184
+ }));
185
+ }
186
+ }
187
+ catch (error) {
188
+ // Silently continue with just the silent transport if file transports fail
189
+ }
50
190
  }
51
- // Add component name
52
- parts.push(`[${this.name}]`);
53
- // Add message
54
- parts.push(message);
55
- // Output to console
56
- const output = parts.join(' ');
57
- switch (level) {
58
- case LogLevel.ERROR:
59
- console.error(output, ...args);
60
- break;
61
- case LogLevel.WARN:
62
- console.warn(output, ...args);
63
- break;
64
- case LogLevel.INFO:
65
- console.info(output, ...args);
66
- break;
67
- case LogLevel.DEBUG:
68
- case LogLevel.TRACE:
69
- default:
70
- console.debug(output, ...args);
71
- break;
191
+ // Replace logger configuration
192
+ this.logger.configure({
193
+ levels: winstonLogLevels,
194
+ level: LogLevel[this.config.level].toLowerCase(),
195
+ defaultMeta: { component: this.name },
196
+ transports
197
+ });
198
+ }
199
+ /**
200
+ * Maps internal LogLevel to Winston log level
201
+ */
202
+ levelToWinstonLevel(level) {
203
+ const levelName = LogLevel[level].toLowerCase();
204
+ return levelName === 'trace' ? 'debug' : levelName;
205
+ }
206
+ /**
207
+ * Creates a standardized error object for MCP compatibility
208
+ *
209
+ * @param message - Human-readable error description
210
+ * @param code - Machine-readable error identifier
211
+ * @param category - Type of error
212
+ * @param context - Additional error context
213
+ * @returns Standardized error object
214
+ */
215
+ createStandardizedError(message, code, category = ErrorCategoryType.CATEGORY_UNKNOWN, context) {
216
+ return {
217
+ errorMessage: message,
218
+ errorCode: code,
219
+ errorCategory: category,
220
+ errorSeverity: LogLevel.ERROR,
221
+ errorTimestamp: new Date().toISOString(),
222
+ errorContext: context ? this.processSensitiveData(context) : undefined,
223
+ errorStack: new Error().stack
224
+ };
225
+ }
226
+ /**
227
+ * Wraps an exception as a standardized error
228
+ *
229
+ * @param error - Original error object
230
+ * @param message - Optional override message
231
+ * @param category - Error category
232
+ * @returns Standardized error object
233
+ */
234
+ wrapExceptionAsStandardizedError(error, message, category = ErrorCategoryType.CATEGORY_UNKNOWN) {
235
+ const errorObject = error instanceof Error ? error : new Error(String(error));
236
+ return {
237
+ errorMessage: message || errorObject.message,
238
+ errorCode: errorObject.name || 'UNKNOWN_ERROR',
239
+ errorCategory: category,
240
+ errorSeverity: LogLevel.ERROR,
241
+ errorTimestamp: new Date().toISOString(),
242
+ errorStack: errorObject.stack
243
+ };
244
+ }
245
+ /**
246
+ * Masks sensitive data in objects before logging
247
+ *
248
+ * @param data - Data object to process
249
+ * @returns Processed data with sensitive fields masked
250
+ */
251
+ processSensitiveData(data) {
252
+ if (!this.config.maskSensitiveData)
253
+ return data;
254
+ const result = {};
255
+ const sensitiveFields = this.config.sensitiveFields;
256
+ for (const [key, value] of Object.entries(data)) {
257
+ // Check if key contains any sensitive field name
258
+ const isSensitive = sensitiveFields.some(field => key.toLowerCase().includes(field.toLowerCase()));
259
+ if (isSensitive) {
260
+ // Mask sensitive values
261
+ result[key] = '********';
262
+ }
263
+ else if (value && typeof value === 'object' && !Array.isArray(value)) {
264
+ // Recursively process nested objects
265
+ result[key] = this.processSensitiveData(value);
266
+ }
267
+ else {
268
+ // Pass through non-sensitive values
269
+ result[key] = value;
270
+ }
72
271
  }
272
+ return result;
73
273
  }
74
274
  /**
75
- * Log an error message
275
+ * Internal method to log a message using Winston
276
+ *
277
+ * @param level - Log level
278
+ * @param message - Log message
279
+ * @param context - Optional context data
76
280
  */
77
- error(message, ...args) {
78
- this.log(LogLevel.ERROR, message, ...args);
281
+ log(level, message, context) {
282
+ if (level > this.config.level)
283
+ return;
284
+ const winstonLevel = this.levelToWinstonLevel(level);
285
+ const processedContext = context ? this.processSensitiveData(context) : undefined;
286
+ this.logger.log(winstonLevel, message, { context: processedContext });
287
+ }
288
+ /**
289
+ * Log an error message with optional error object and context
290
+ *
291
+ * @param message - Error message
292
+ * @param errorOrContext - Error object or context
293
+ * @param context - Additional context
294
+ */
295
+ error(message, errorOrContext, context) {
296
+ if (errorOrContext instanceof Error) {
297
+ const errorObj = this.wrapExceptionAsStandardizedError(errorOrContext);
298
+ const combinedContext = {
299
+ ...(context || {}),
300
+ error: errorObj
301
+ };
302
+ this.log(LogLevel.ERROR, message, combinedContext);
303
+ }
304
+ else {
305
+ this.log(LogLevel.ERROR, message, errorOrContext || context);
306
+ }
79
307
  }
80
308
  /**
81
309
  * Log a warning message
310
+ *
311
+ * @param message - Warning message
312
+ * @param context - Optional context data
82
313
  */
83
- warn(message, ...args) {
84
- this.log(LogLevel.WARN, message, ...args);
314
+ warn(message, context) {
315
+ this.log(LogLevel.WARN, message, context);
85
316
  }
86
317
  /**
87
318
  * Log an info message
319
+ *
320
+ * @param message - Info message
321
+ * @param context - Optional context data
88
322
  */
89
- info(message, ...args) {
90
- this.log(LogLevel.INFO, message, ...args);
323
+ info(message, context) {
324
+ this.log(LogLevel.INFO, message, context);
91
325
  }
92
326
  /**
93
327
  * Log a debug message
328
+ *
329
+ * @param message - Debug message
330
+ * @param context - Optional context data
94
331
  */
95
- debug(message, ...args) {
96
- this.log(LogLevel.DEBUG, message, ...args);
332
+ debug(message, context) {
333
+ this.log(LogLevel.DEBUG, message, context);
97
334
  }
98
335
  /**
99
336
  * Log a trace message
337
+ *
338
+ * @param message - Trace message
339
+ * @param context - Optional context data
100
340
  */
101
- trace(message, ...args) {
102
- this.log(LogLevel.TRACE, message, ...args);
341
+ trace(message, context) {
342
+ this.log(LogLevel.TRACE, message, context);
103
343
  }
104
344
  /**
105
345
  * Set the log level
346
+ *
347
+ * @param level - New log level
106
348
  */
107
349
  setLevel(level) {
108
350
  this.config.level = level;
351
+ this.logger.level = this.levelToWinstonLevel(level);
352
+ }
353
+ /**
354
+ * Start a timer for performance tracking
355
+ *
356
+ * @param id - Timer identifier
357
+ */
358
+ startTimer(id) {
359
+ this.timers.set(id, performance.now());
360
+ }
361
+ /**
362
+ * End a timer and log the elapsed time
363
+ *
364
+ * @param id - Timer identifier
365
+ * @param message - Log message prefix
366
+ * @param level - Log level for the timing message
367
+ */
368
+ endTimer(id, message = 'Operation completed', level = LogLevel.DEBUG) {
369
+ const startTime = this.timers.get(id);
370
+ if (startTime === undefined) {
371
+ this.warn(`Timer "${id}" does not exist`, { action: 'endTimer' });
372
+ return 0;
373
+ }
374
+ const endTime = performance.now();
375
+ const elapsedMs = endTime - startTime;
376
+ this.log(level, `${message} in ${elapsedMs.toFixed(2)}ms`, {
377
+ timerId: id,
378
+ processingTimeMs: elapsedMs
379
+ });
380
+ this.timers.delete(id);
381
+ return elapsedMs;
382
+ }
383
+ /**
384
+ * Log the result of an operation with timing information
385
+ *
386
+ * @param success - Whether the operation was successful
387
+ * @param operation - Operation name
388
+ * @param elapsedMs - Processing time in milliseconds
389
+ * @param context - Optional operation context
390
+ */
391
+ logOperationResult(success, operation, elapsedMs, context) {
392
+ const level = success ? LogLevel.INFO : LogLevel.ERROR;
393
+ const status = success ? 'succeeded' : 'failed';
394
+ this.log(level, `Operation ${operation} ${status}`, {
395
+ ...(context || {}),
396
+ operation,
397
+ success,
398
+ processingTimeMs: elapsedMs
399
+ });
109
400
  }
110
401
  }
111
402
  /**
112
403
  * Create and return a namespaced logger
404
+ *
405
+ * @param name - Component name for the logger
406
+ * @param config - Optional configuration overrides
407
+ * @returns Configured logger instance
113
408
  */
114
409
  export function createLogger(name, config) {
115
410
  return new Logger(name, config);
116
411
  }
117
412
  // Create a global root logger
118
- export const rootLogger = createLogger('mcp-server');
413
+ export const rootLogger = createLogger('obsidian-mcp-server', {
414
+ files: true,
415
+ logDir: "logs"
416
+ });
119
417
  //# sourceMappingURL=logging.js.map
package/debug.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Debug script to run the server with verbose logging
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { spawn } from 'child_process';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname } from 'path';
9
+
10
+ // Get current directory from import.meta
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ // Create logs directory if it doesn't exist
15
+ const logsDir = path.join(__dirname, 'logs');
16
+ if (!fs.existsSync(logsDir)) {
17
+ fs.mkdirSync(logsDir, { recursive: true });
18
+ }
19
+
20
+ // Create log file stream
21
+ const logFile = path.join(logsDir, `debug-server-${new Date().toISOString().replace(/[:.]/g, '-')}.log`);
22
+ const logStream = fs.createWriteStream(logFile, { flags: 'a' });
23
+
24
+ console.log(`Starting Obsidian MCP Server in debug mode...`);
25
+ console.log(`Logs will be written to: ${logFile}`);
26
+
27
+ // Environment variables from MCP settings
28
+ const env = {
29
+ ...process.env,
30
+ DEBUG_JSONRPC: 'true',
31
+ NODE_ENV: 'development',
32
+ // Required environment variables from MCP settings
33
+ OBSIDIAN_API_KEY: '9bde5e9fb07951daa75819a1704ce75fd597955205141d3b39e5e1423ddbc97c',
34
+ VERIFY_SSL: 'false',
35
+ OBSIDIAN_PROTOCOL: 'https',
36
+ OBSIDIAN_HOST: '127.0.0.1',
37
+ OBSIDIAN_PORT: '27124',
38
+ REQUEST_TIMEOUT: '5000',
39
+ MAX_CONTENT_LENGTH: String(50 * 1024 * 1024),
40
+ MAX_BODY_LENGTH: String(50 * 1024 * 1024),
41
+ RATE_LIMIT_WINDOW_MS: '900000',
42
+ RATE_LIMIT_MAX_REQUESTS: '200',
43
+ TOOL_TIMEOUT_MS: '60000'
44
+ };
45
+
46
+ // Start the server process
47
+ const serverProcess = spawn('node', ['build/index.js'], {
48
+ env,
49
+ stdio: ['pipe', 'pipe', 'pipe']
50
+ });
51
+
52
+ // Log process output
53
+ serverProcess.stdout.on('data', (data) => {
54
+ const output = data.toString();
55
+ process.stdout.write(output);
56
+ logStream.write(`[STDOUT] ${output}`);
57
+ });
58
+
59
+ serverProcess.stderr.on('data', (data) => {
60
+ const output = data.toString();
61
+ process.stderr.write(output);
62
+ logStream.write(`[STDERR] ${output}`);
63
+ });
64
+
65
+ // Handle process exit
66
+ serverProcess.on('exit', (code) => {
67
+ const message = `Server process exited with code ${code}`;
68
+ console.log(message);
69
+ logStream.write(`${message}\n`);
70
+ logStream.end();
71
+ });
72
+
73
+ // Handle errors
74
+ serverProcess.on('error', (err) => {
75
+ const message = `Error starting server: ${err.message}`;
76
+ console.error(message);
77
+ logStream.write(`[ERROR] ${message}\n`);
78
+ logStream.end();
79
+ });
80
+
81
+ // Handle uncaught exceptions
82
+ process.on('uncaughtException', (err) => {
83
+ const message = `Uncaught exception: ${err.message}\n${err.stack}`;
84
+ console.error(message);
85
+ logStream.write(`[UNCAUGHT] ${message}\n`);
86
+ logStream.end();
87
+ process.exit(1);
88
+ });
89
+
90
+ // Set up cleanup
91
+ const cleanup = () => {
92
+ if (serverProcess && !serverProcess.killed) {
93
+ serverProcess.kill();
94
+ }
95
+ logStream.end();
96
+ };
97
+
98
+ process.on('SIGINT', cleanup);
99
+ process.on('SIGTERM', cleanup);