mcp-ts-template 1.1.6 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-^5.8.3-blue.svg)](https://www.typescriptlang.org/)
4
4
  [![Model Context Protocol SDK](https://img.shields.io/badge/MCP%20SDK-1.11.0-green.svg)](https://github.com/modelcontextprotocol/typescript-sdk)
5
5
  [![MCP Spec Version](https://img.shields.io/badge/MCP%20Spec-2025--03--26-lightgrey.svg)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/changelog.mdx)
6
- [![Version](https://img.shields.io/badge/Version-1.1.5-blue.svg)](./CHANGELOG.md)
6
+ [![Version](https://img.shields.io/badge/Version-1.1.7-blue.svg)](./CHANGELOG.md)
7
7
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
8
8
  [![Status](https://img.shields.io/badge/Status-Stable-green.svg)](https://github.com/cyanheads/mcp-ts-template/issues)
9
9
  [![GitHub](https://img.shields.io/github/stars/cyanheads/mcp-ts-template?style=social)](https://github.com/cyanheads/mcp-ts-template)
@@ -17,7 +17,7 @@ try {
17
17
  catch (error) {
18
18
  // Silently use default pkg info if reading fails.
19
19
  // Consider adding logging here if robust error handling is needed.
20
- if (process.stdout.isTTY) {
20
+ if (process.stdout.isTTY) { // Guarded console.error
21
21
  console.error("Warning: Could not read package.json for default config values.", error);
22
22
  }
23
23
  }
@@ -163,7 +163,6 @@ export async function connectMcpClient(serverName, parentContext) {
163
163
  operation: `connecting to MCP server ${serverName}`,
164
164
  context: operationContext,
165
165
  errorCode: BaseErrorCode.INTERNAL_ERROR, // Use INTERNAL_ERROR as fallback for connection issues
166
- rethrow: true, // Rethrow the McpError for the caller to handle
167
166
  });
168
167
  }
169
168
  /**
@@ -401,8 +401,10 @@ export async function startHttpTransport(createServerInstanceFn, context) {
401
401
  // Determine protocol for logging (basic assumption based on HSTS possibility)
402
402
  const protocol = config.environment === 'production' ? 'https' : 'http';
403
403
  const serverAddress = `${protocol}://${config.mcpHttpHost}:${actualPort}${MCP_ENDPOINT_PATH}`;
404
- // Use console.log for prominent startup message.
405
- console.log(`\nšŸš€ MCP Server running in HTTP mode at: ${serverAddress}\n (MCP Spec: 2025-03-26 Streamable HTTP Transport)\n`);
404
+ // Use console.log for prominent startup message only if TTY.
405
+ if (process.stdout.isTTY) {
406
+ console.log(`\nšŸš€ MCP Server running in HTTP mode at: ${serverAddress}\n (MCP Spec: 2025-03-26 Streamable HTTP Transport)\n`);
407
+ }
406
408
  }
407
409
  catch (err) {
408
410
  logger.fatal('HTTP server failed to start after multiple port retries.', { ...transportContext, error: err instanceof Error ? err.message : String(err) });
@@ -1,90 +1,172 @@
1
+ /**
2
+ * @file errorHandler.ts
3
+ * @description This module provides utilities for creating and managing request contexts.
4
+ * A request context is a data structure associated with a specific request or operation,
5
+ * carrying a unique ID, timestamp, and other relevant information for logging, tracing,
6
+ * and processing.
7
+ */
1
8
  import { BaseErrorCode } from '../../types-global/errors.js';
2
9
  /**
3
- * Generic error context interface
10
+ * @interface ErrorContext
11
+ * @description Defines a generic structure for providing context with errors.
12
+ * This context can include identifiers like `requestId` or any other relevant
13
+ * key-value pairs that aid in debugging or understanding the error's circumstances.
4
14
  */
5
15
  export interface ErrorContext {
6
- /** Unique request or operation identifier */
16
+ /**
17
+ * A unique identifier for the request or operation during which the error occurred.
18
+ * Useful for tracing errors through logs and distributed systems.
19
+ * @type {string}
20
+ * @optional
21
+ */
7
22
  requestId?: string;
8
- /** Any additional context information */
23
+ /**
24
+ * Allows for arbitrary additional context information.
25
+ * Keys are strings, and values can be of any type.
26
+ */
9
27
  [key: string]: unknown;
10
28
  }
11
29
  /**
12
- * Error handler options
30
+ * @interface ErrorHandlerOptions
31
+ * @description Configuration options for the `ErrorHandler.handleError` method.
32
+ * These options control how an error is processed, logged, and whether it's rethrown.
13
33
  */
14
34
  export interface ErrorHandlerOptions {
15
- /** The context of the operation that caused the error */
35
+ /**
36
+ * The context of the operation that caused the error.
37
+ * This can include `requestId` and other relevant debugging information.
38
+ * @type {ErrorContext}
39
+ * @optional
40
+ */
16
41
  context?: ErrorContext;
17
- /** The name of the operation being performed */
42
+ /**
43
+ * A descriptive name of the operation being performed when the error occurred.
44
+ * This helps in identifying the source or nature of the error in logs.
45
+ * Example: "UserLogin", "ProcessPayment", "FetchUserProfile".
46
+ * @type {string}
47
+ * @required
48
+ */
18
49
  operation: string;
19
- /** The input that caused the error */
50
+ /**
51
+ * The input data or parameters that were being processed when the error occurred.
52
+ * This input will be sanitized before logging to prevent sensitive data exposure.
53
+ * @type {unknown}
54
+ * @optional
55
+ */
20
56
  input?: unknown;
21
- /** Whether to rethrow the error after handling */
57
+ /**
58
+ * If true, the (potentially transformed) error will be rethrown after handling.
59
+ * Defaults to `false`.
60
+ * @type {boolean}
61
+ * @optional
62
+ */
22
63
  rethrow?: boolean;
23
- /** Custom error code to use when creating an McpError */
64
+ /**
65
+ * A specific `BaseErrorCode` to assign to the error, overriding any
66
+ * automatically determined error code.
67
+ * @type {BaseErrorCode}
68
+ * @optional
69
+ */
24
70
  errorCode?: BaseErrorCode;
25
- /** Custom error mapper function */
71
+ /**
72
+ * A custom function to map or transform the original error into a new `Error` instance.
73
+ * If provided, this function is used instead of the default `McpError` creation.
74
+ * @param {unknown} error - The original error that occurred.
75
+ * @returns {Error} The transformed error.
76
+ * @optional
77
+ */
26
78
  errorMapper?: (error: unknown) => Error;
27
- /** Whether to include stack traces in logs */
79
+ /**
80
+ * If true, stack traces will be included in the logs.
81
+ * Defaults to `true`.
82
+ * @type {boolean}
83
+ * @optional
84
+ */
28
85
  includeStack?: boolean;
29
- /** Whether this is a critical error that should abort operations */
86
+ /**
87
+ * If true, indicates that the error is critical and might require immediate attention
88
+ * or could lead to system instability. This is primarily for logging and alerting.
89
+ * Defaults to `false`.
90
+ * @type {boolean}
91
+ * @optional
92
+ */
30
93
  critical?: boolean;
31
94
  }
32
95
  /**
33
- * Base error mapping rule
96
+ * @interface BaseErrorMapping
97
+ * @description Defines a basic rule for mapping errors based on patterns.
98
+ * Used internally by `COMMON_ERROR_PATTERNS` and as a base for `ErrorMapping`.
34
99
  */
35
100
  export interface BaseErrorMapping {
36
- /** Pattern to match in the error message */
101
+ /**
102
+ * A string or regular expression to match against the error message.
103
+ * If a string is provided, it's typically used for substring matching (case-insensitive).
104
+ * @type {string | RegExp}
105
+ * @required
106
+ */
37
107
  pattern: string | RegExp;
38
- /** Error code for mapped errors */
108
+ /**
109
+ * The `BaseErrorCode` to assign if the pattern matches.
110
+ * @type {BaseErrorCode}
111
+ * @required
112
+ */
39
113
  errorCode: BaseErrorCode;
40
- /** Custom error message template */
114
+ /**
115
+ * An optional custom message template for the mapped error.
116
+ * (Note: This property is defined but not directly used by `ErrorHandler.determineErrorCode`
117
+ * which focuses on `errorCode`. It's more relevant for custom mapping logic.)
118
+ * @type {string}
119
+ * @optional
120
+ */
41
121
  messageTemplate?: string;
42
122
  }
43
123
  /**
44
- * Error mapping configuration
124
+ * @interface ErrorMapping
125
+ * @template T - The type of `Error` this mapping will produce, defaults to `Error`.
126
+ * @description Extends `BaseErrorMapping` to include a factory function for creating
127
+ * specific error instances and additional context for the mapping.
128
+ * Used by `ErrorHandler.mapError`.
45
129
  */
46
130
  export interface ErrorMapping<T extends Error = Error> extends BaseErrorMapping {
47
- /** Factory function to create the mapped error */
131
+ /**
132
+ * A factory function that creates and returns an instance of the mapped error type `T`.
133
+ * @param {unknown} error - The original error that occurred.
134
+ * @param {Record<string, unknown>} [context] - Optional additional context provided in the mapping rule.
135
+ * @returns {T} The newly created error instance.
136
+ * @required
137
+ */
48
138
  factory: (error: unknown, context?: Record<string, unknown>) => T;
49
- /** Additional context to merge with error context */
139
+ /**
140
+ * Additional static context to be merged or passed to the `factory` function
141
+ * when this mapping rule is applied.
142
+ * @type {Record<string, unknown>}
143
+ * @optional
144
+ */
50
145
  additionalContext?: Record<string, unknown>;
51
146
  }
52
147
  /**
53
- * Error handler utility class with various error handling methods
148
+ * @class ErrorHandler
149
+ * @description A utility class providing static methods for comprehensive error handling.
54
150
  */
55
151
  export declare class ErrorHandler {
56
152
  /**
57
- * Determine the appropriate error code for an error based on patterns and type
58
- * @param error The error to classify
59
- * @returns The appropriate error code
153
+ * Determines an appropriate `BaseErrorCode` for a given error.
60
154
  */
61
155
  static determineErrorCode(error: unknown): BaseErrorCode;
62
156
  /**
63
- * Handle operation errors with consistent logging and transformation
64
- * @param error The error that occurred
65
- * @param options Error handling options
66
- * @returns The transformed error
157
+ * Handles an error with consistent logging and optional transformation.
67
158
  */
68
159
  static handleError(error: unknown, options: ErrorHandlerOptions): Error;
69
160
  /**
70
- * Map an error to a specific error type based on error message patterns
71
- * @param error The error to map
72
- * @param mappings Array of pattern and factory mappings
73
- * @param defaultFactory Default factory function if no pattern matches
74
- * @returns The mapped error
161
+ * Maps an error to a specific error type `T` based on a list of `ErrorMapping` rules.
75
162
  */
76
- static mapError<T extends Error>(error: unknown, mappings: ErrorMapping<T>[], defaultFactory?: (error: unknown, context?: Record<string, unknown>) => T): T | Error;
163
+ static mapError<T extends Error>(error: unknown, mappings: ReadonlyArray<ErrorMapping<T>>, defaultFactory?: (error: unknown, context?: Record<string, unknown>) => T): T | Error;
77
164
  /**
78
- * Format an error for consistent response structure
79
- * @param error The error to format
80
- * @returns Formatted error object
165
+ * Formats an error into a consistent object structure, typically for API responses.
81
166
  */
82
167
  static formatError(error: unknown): Record<string, unknown>;
83
168
  /**
84
- * Safely execute a function and handle any errors
85
- * @param fn Function to execute
86
- * @param options Error handling options
87
- * @returns The result of the function or error
169
+ * Safely executes a function and handles any errors using `ErrorHandler.handleError`.
88
170
  */
89
- static tryCatch<T>(fn: () => Promise<T> | T, options: ErrorHandlerOptions): Promise<T>;
171
+ static tryCatch<T>(fn: () => Promise<T> | T, options: Omit<ErrorHandlerOptions, 'rethrow'>): Promise<T>;
90
172
  }
@@ -1,8 +1,18 @@
1
- import { BaseErrorCode, McpError } from '../../types-global/errors.js'; // Corrected path
1
+ /**
2
+ * @file errorHandler.ts
3
+ * @description This module provides utilities for creating and managing request contexts.
4
+ * A request context is a data structure associated with a specific request or operation,
5
+ * carrying a unique ID, timestamp, and other relevant information for logging, tracing,
6
+ * and processing.
7
+ */
8
+ import { BaseErrorCode, McpError } from '../../types-global/errors.js';
9
+ import { sanitizeInputForLogging } from '../index.js';
2
10
  import { logger } from './logger.js';
3
- import { sanitizeInputForLogging } from '../index.js'; // Import from main barrel file
4
11
  /**
5
- * Simple mapper that maps error types to error codes
12
+ * @constant ERROR_TYPE_MAPPINGS
13
+ * @description Maps standard JavaScript error constructor names to `BaseErrorCode` values.
14
+ * This allows for quick classification of common built-in error types.
15
+ * @readonly
6
16
  */
7
17
  const ERROR_TYPE_MAPPINGS = {
8
18
  'SyntaxError': BaseErrorCode.VALIDATION_ERROR,
@@ -10,235 +20,245 @@ const ERROR_TYPE_MAPPINGS = {
10
20
  'ReferenceError': BaseErrorCode.INTERNAL_ERROR,
11
21
  'RangeError': BaseErrorCode.VALIDATION_ERROR,
12
22
  'URIError': BaseErrorCode.VALIDATION_ERROR,
13
- 'EvalError': BaseErrorCode.INTERNAL_ERROR
23
+ 'EvalError': BaseErrorCode.INTERNAL_ERROR,
14
24
  };
15
25
  /**
16
- * Common error patterns for automatic classification
26
+ * @constant COMMON_ERROR_PATTERNS
27
+ * @description An array of `BaseErrorMapping` rules used to automatically classify
28
+ * errors based on keywords or patterns found in their messages or names.
29
+ * These patterns are typically case-insensitive.
30
+ * **IMPORTANT**: The order of patterns matters. More specific patterns should generally come
31
+ * before more generic ones if there's a possibility of overlap, as the first match is used.
32
+ * @readonly
17
33
  */
18
34
  const COMMON_ERROR_PATTERNS = [
19
- // Authentication related errors
20
35
  { pattern: /auth|unauthorized|unauthenticated|not.*logged.*in|invalid.*token|expired.*token/i, errorCode: BaseErrorCode.UNAUTHORIZED },
21
- // Permission related errors
22
36
  { pattern: /permission|forbidden|access.*denied|not.*allowed/i, errorCode: BaseErrorCode.FORBIDDEN },
23
- // Not found errors
24
37
  { pattern: /not.*found|missing|no.*such|doesn't.*exist|couldn't.*find/i, errorCode: BaseErrorCode.NOT_FOUND },
25
- // Validation errors
26
- { pattern: /invalid|validation|malformed|bad request|wrong format/i, errorCode: BaseErrorCode.VALIDATION_ERROR },
27
- // Conflict errors
38
+ { pattern: /invalid|validation|malformed|bad request|wrong format|missing.*required/i, errorCode: BaseErrorCode.VALIDATION_ERROR },
28
39
  { pattern: /conflict|already.*exists|duplicate|unique.*constraint/i, errorCode: BaseErrorCode.CONFLICT },
29
- // Rate limiting
30
40
  { pattern: /rate.*limit|too.*many.*requests|throttled/i, errorCode: BaseErrorCode.RATE_LIMITED },
31
- // Timeout errors
32
41
  { pattern: /timeout|timed.*out|deadline.*exceeded/i, errorCode: BaseErrorCode.TIMEOUT },
33
- // External service errors
34
- { pattern: /service.*unavailable|bad.*gateway|gateway.*timeout/i, errorCode: BaseErrorCode.SERVICE_UNAVAILABLE }
42
+ { pattern: /service.*unavailable|bad.*gateway|gateway.*timeout|upstream.*error/i, errorCode: BaseErrorCode.SERVICE_UNAVAILABLE },
35
43
  ];
36
44
  /**
37
- * Get a readable name for an error
38
- * @param error Error to get name for
39
- * @returns User-friendly error name
45
+ * Creates a "safe" RegExp for testing error messages.
46
+ * Ensures case-insensitivity if not already specified and removes the global flag.
47
+ * @param pattern - The string or RegExp pattern.
48
+ * @returns A new RegExp instance.
49
+ */
50
+ function createSafeRegex(pattern) {
51
+ if (pattern instanceof RegExp) {
52
+ let flags = pattern.flags.replace('g', ''); // Remove global flag
53
+ if (!flags.includes('i')) {
54
+ flags += 'i'; // Add case-insensitive if not present
55
+ }
56
+ return new RegExp(pattern.source, flags);
57
+ }
58
+ return new RegExp(pattern, 'i'); // Default to case-insensitive for string patterns
59
+ }
60
+ /**
61
+ * Retrieves a descriptive name for an error object or value.
62
+ * Handles various types including `Error` instances, `null`, `undefined`, and other primitives/objects.
63
+ *
64
+ * @param error - The error object or value.
65
+ * @returns A string representing the error's name or type.
40
66
  */
41
67
  function getErrorName(error) {
42
68
  if (error instanceof Error) {
43
69
  return error.name || 'Error';
44
70
  }
45
71
  if (error === null) {
46
- return 'NullError';
72
+ return 'NullValueEncountered';
47
73
  }
48
74
  if (error === undefined) {
49
- return 'UndefinedError';
75
+ return 'UndefinedValueEncountered';
50
76
  }
51
- return typeof error === 'object'
52
- ? 'ObjectError'
53
- : 'UnknownError';
77
+ if (typeof error === 'object' && error !== null && error.constructor && typeof error.constructor.name === 'string' && error.constructor.name !== 'Object') {
78
+ return `${error.constructor.name}Encountered`;
79
+ }
80
+ return `${typeof error}Encountered`;
54
81
  }
55
82
  /**
56
- * Get a message from an error
57
- * @param error Error to get message from
58
- * @returns Error message
83
+ * Extracts a message string from an error object or value.
84
+ * Handles `Error` instances, primitives, and converts other types to a string representation.
85
+ *
86
+ * @param error - The error object or value.
87
+ * @returns The error message string.
59
88
  */
60
89
  function getErrorMessage(error) {
61
90
  if (error instanceof Error) {
62
91
  return error.message;
63
92
  }
64
93
  if (error === null) {
65
- return 'Null error occurred';
94
+ return 'Null value encountered as error';
66
95
  }
67
96
  if (error === undefined) {
68
- return 'Undefined error occurred';
97
+ return 'Undefined value encountered as error';
98
+ }
99
+ if (typeof error === 'string') {
100
+ return error;
101
+ }
102
+ try {
103
+ const str = String(error);
104
+ if (str === '[object Object]' && error !== null) {
105
+ try {
106
+ return `Non-Error object encountered: ${JSON.stringify(error)}`;
107
+ }
108
+ catch (stringifyError) {
109
+ return `Unstringifyable non-Error object encountered (constructor: ${error.constructor?.name || 'Unknown'})`;
110
+ }
111
+ }
112
+ return str;
113
+ }
114
+ catch (e) {
115
+ return `Error converting error to string: ${e instanceof Error ? e.message : 'Unknown conversion error'}`;
69
116
  }
70
- return typeof error === 'string'
71
- ? error
72
- : String(error);
73
117
  }
74
118
  /**
75
- * Error handler utility class with various error handling methods
119
+ * @class ErrorHandler
120
+ * @description A utility class providing static methods for comprehensive error handling.
76
121
  */
77
122
  export class ErrorHandler {
78
123
  /**
79
- * Determine the appropriate error code for an error based on patterns and type
80
- * @param error The error to classify
81
- * @returns The appropriate error code
124
+ * Determines an appropriate `BaseErrorCode` for a given error.
82
125
  */
83
126
  static determineErrorCode(error) {
84
- // If it's already an McpError, use its code
85
127
  if (error instanceof McpError) {
86
128
  return error.code;
87
129
  }
88
130
  const errorName = getErrorName(error);
89
131
  const errorMessage = getErrorMessage(error);
90
- // Check if the error type has a direct mapping
91
132
  if (errorName in ERROR_TYPE_MAPPINGS) {
92
133
  return ERROR_TYPE_MAPPINGS[errorName];
93
134
  }
94
- // Check for common error patterns
95
- for (const pattern of COMMON_ERROR_PATTERNS) {
96
- const regex = pattern.pattern instanceof RegExp
97
- ? pattern.pattern
98
- : new RegExp(pattern.pattern, 'i');
135
+ for (const mapping of COMMON_ERROR_PATTERNS) {
136
+ const regex = createSafeRegex(mapping.pattern);
99
137
  if (regex.test(errorMessage) || regex.test(errorName)) {
100
- return pattern.errorCode;
138
+ return mapping.errorCode;
101
139
  }
102
140
  }
103
- // Default to internal error if no pattern matches
104
141
  return BaseErrorCode.INTERNAL_ERROR;
105
142
  }
106
143
  /**
107
- * Handle operation errors with consistent logging and transformation
108
- * @param error The error that occurred
109
- * @param options Error handling options
110
- * @returns The transformed error
144
+ * Handles an error with consistent logging and optional transformation.
111
145
  */
112
146
  static handleError(error, options) {
113
- const { context, operation, input, rethrow = false, errorCode: explicitErrorCode, includeStack = true, critical = false } = options;
114
- // If it's already an McpError, use it directly but apply additional context
147
+ const { context = {}, operation, input, rethrow = false, errorCode: explicitErrorCode, includeStack = true, critical = false, errorMapper, } = options;
148
+ const sanitizedInput = input !== undefined ? sanitizeInputForLogging(input) : undefined;
149
+ const originalErrorName = getErrorName(error);
150
+ const originalErrorMessage = getErrorMessage(error);
151
+ const originalStack = error instanceof Error ? error.stack : undefined;
152
+ let finalError;
153
+ let loggedErrorCode;
154
+ // Consolidate details for McpError and logging
155
+ const errorDetailsSeed = error instanceof McpError && typeof error.details === 'object' && error.details !== null
156
+ ? { ...error.details } // Clone to avoid mutating original
157
+ : {};
158
+ const consolidatedDetails = {
159
+ ...errorDetailsSeed,
160
+ ...context, // Operation context takes precedence over seed details if keys overlap
161
+ originalErrorName,
162
+ originalMessage: originalErrorMessage,
163
+ };
164
+ if (originalStack && !(error instanceof McpError && error.details?.originalStack)) { // Avoid duplicating if already there
165
+ consolidatedDetails.originalStack = originalStack;
166
+ }
115
167
  if (error instanceof McpError) {
116
- // Add any additional context
117
- if (context && Object.keys(context).length > 0) {
118
- // Ensure details is an object before spreading
119
- const existingDetails = typeof error.details === 'object' && error.details !== null ? error.details : {};
120
- error.details = { ...existingDetails, ...context };
168
+ loggedErrorCode = error.code;
169
+ finalError = errorMapper ? errorMapper(error) : new McpError(error.code, error.message, consolidatedDetails);
170
+ if (finalError instanceof McpError && !finalError.details) {
171
+ finalError.details = consolidatedDetails;
121
172
  }
122
- // Log the error with sanitized input
123
- logger.error(`Error ${operation}: ${error.message}`, {
124
- errorCode: error.code,
125
- requestId: context?.requestId,
126
- input: input ? sanitizeInputForLogging(input) : undefined,
127
- stack: includeStack ? error.stack : undefined,
128
- critical,
129
- ...context
130
- });
131
- if (rethrow) {
132
- throw error;
173
+ }
174
+ else {
175
+ loggedErrorCode = explicitErrorCode || ErrorHandler.determineErrorCode(error);
176
+ const message = `Error in ${operation}: ${originalErrorMessage}`;
177
+ finalError = errorMapper ? errorMapper(error) : new McpError(loggedErrorCode, message, consolidatedDetails);
178
+ if (finalError instanceof McpError && !finalError.details) {
179
+ finalError.details = consolidatedDetails;
133
180
  }
134
- // Ensure the function returns an Error type
135
- return error;
136
- }
137
- // Sanitize input for logging
138
- const sanitizedInput = input ? sanitizeInputForLogging(input) : undefined;
139
- // Log the error with consistent format
140
- logger.error(`Error ${operation}`, {
141
- error: getErrorMessage(error), // Use helper function
142
- errorType: getErrorName(error),
181
+ }
182
+ // Preserve original stack if the finalError is a new Error instance and original was an Error
183
+ if (finalError !== error && error instanceof Error && finalError instanceof Error && !finalError.stack && error.stack) {
184
+ finalError.stack = error.stack;
185
+ }
186
+ // Prepare log payload
187
+ const logPayload = {
188
+ operation,
189
+ requestId: context.requestId,
143
190
  input: sanitizedInput,
144
- requestId: context?.requestId,
145
- stack: includeStack && error instanceof Error ? error.stack : undefined,
146
191
  critical,
147
- ...context
148
- });
149
- // Choose the error code (explicit > determined > default)
150
- const errorCode = explicitErrorCode ||
151
- ErrorHandler.determineErrorCode(error) ||
152
- BaseErrorCode.INTERNAL_ERROR;
153
- // Transform to appropriate error type
154
- let transformedError;
155
- if (options.errorMapper) {
156
- transformedError = options.errorMapper(error);
192
+ errorCode: loggedErrorCode,
193
+ originalErrorType: originalErrorName, // Retain for clarity
194
+ finalErrorType: getErrorName(finalError),
195
+ };
196
+ if (finalError instanceof McpError && finalError.details) {
197
+ logPayload.errorDetails = finalError.details;
157
198
  }
158
199
  else {
159
- transformedError = new McpError(errorCode, `Error ${operation}: ${getErrorMessage(error)}`, // Use helper function
160
- {
161
- originalError: getErrorName(error),
162
- ...context
163
- });
200
+ // For non-McpError or McpError without details, use the consolidatedDetails we built
201
+ logPayload.errorDetails = consolidatedDetails;
164
202
  }
165
- // Rethrow if requested
203
+ if (includeStack && finalError instanceof Error && finalError.stack) {
204
+ logPayload.stack = finalError.stack;
205
+ }
206
+ else if (includeStack && originalStack && !logPayload.stack) { // Fallback to original stack if finalError lost it
207
+ logPayload.stack = originalStack;
208
+ }
209
+ logger.error(`Error in ${operation}: ${finalError.message || originalErrorMessage}`, logPayload);
166
210
  if (rethrow) {
167
- throw transformedError;
211
+ throw finalError;
168
212
  }
169
- // Ensure the function returns an Error type
170
- return transformedError;
213
+ return finalError;
171
214
  }
172
215
  /**
173
- * Map an error to a specific error type based on error message patterns
174
- * @param error The error to map
175
- * @param mappings Array of pattern and factory mappings
176
- * @param defaultFactory Default factory function if no pattern matches
177
- * @returns The mapped error
216
+ * Maps an error to a specific error type `T` based on a list of `ErrorMapping` rules.
178
217
  */
179
218
  static mapError(error, mappings, defaultFactory) {
180
- // If it's already the target type and we have a default factory to check against, return it
181
- if (defaultFactory && error instanceof Error) {
182
- const defaultInstance = defaultFactory(error);
183
- if (error.constructor === defaultInstance.constructor) {
184
- return error;
185
- }
186
- }
187
219
  const errorMessage = getErrorMessage(error);
188
- // Check each pattern and return the first match
220
+ const errorName = getErrorName(error);
189
221
  for (const mapping of mappings) {
190
- const matches = mapping.pattern instanceof RegExp
191
- ? mapping.pattern.test(errorMessage)
192
- : errorMessage.includes(mapping.pattern);
193
- if (matches) {
222
+ const regex = createSafeRegex(mapping.pattern);
223
+ if (regex.test(errorMessage) || regex.test(errorName)) {
194
224
  return mapping.factory(error, mapping.additionalContext);
195
225
  }
196
226
  }
197
- // Return default or original error
198
227
  if (defaultFactory) {
199
228
  return defaultFactory(error);
200
229
  }
201
- return error instanceof Error
202
- ? error
203
- : new Error(String(error));
230
+ return error instanceof Error ? error : new Error(String(error));
204
231
  }
205
- // Removed createErrorMapper method for simplification
206
232
  /**
207
- * Format an error for consistent response structure
208
- * @param error The error to format
209
- * @returns Formatted error object
233
+ * Formats an error into a consistent object structure, typically for API responses.
210
234
  */
211
235
  static formatError(error) {
212
236
  if (error instanceof McpError) {
213
237
  return {
214
238
  code: error.code,
215
239
  message: error.message,
216
- // Ensure details is an object
217
- details: typeof error.details === 'object' && error.details !== null ? error.details : {}
240
+ details: typeof error.details === 'object' && error.details !== null ? error.details : {},
218
241
  };
219
242
  }
220
243
  if (error instanceof Error) {
221
244
  return {
222
245
  code: ErrorHandler.determineErrorCode(error),
223
246
  message: error.message,
224
- details: { errorType: error.name }
247
+ details: { errorType: error.name || 'Error' },
225
248
  };
226
249
  }
227
250
  return {
228
251
  code: BaseErrorCode.UNKNOWN_ERROR,
229
- message: String(error),
230
- details: { errorType: typeof error }
252
+ message: getErrorMessage(error),
253
+ details: { errorType: getErrorName(error) },
231
254
  };
232
255
  }
233
256
  /**
234
- * Safely execute a function and handle any errors
235
- * @param fn Function to execute
236
- * @param options Error handling options
237
- * @returns The result of the function or error
257
+ * Safely executes a function and handles any errors using `ErrorHandler.handleError`.
238
258
  */
239
259
  static async tryCatch(fn, options) {
240
260
  try {
241
- return await fn();
261
+ return await Promise.resolve(fn());
242
262
  }
243
263
  catch (error) {
244
264
  throw ErrorHandler.handleError(error, { ...options, rethrow: true });
@@ -31,8 +31,12 @@ const logsDir = path.join(projectRoot, 'logs');
31
31
  const resolvedLogsDir = path.resolve(logsDir);
32
32
  const isLogsDirSafe = resolvedLogsDir === projectRoot || resolvedLogsDir.startsWith(projectRoot + path.sep);
33
33
  if (!isLogsDirSafe) {
34
- // Use console.error here as logger might not be initialized or safe
35
- console.error(`FATAL: logs directory "${resolvedLogsDir}" is outside project root "${projectRoot}". File logging disabled.`);
34
+ if (process.stdout.isTTY) {
35
+ // Use console.error here as logger might not be initialized or safe, but only if TTY
36
+ console.error(`FATAL: logs directory "${resolvedLogsDir}" is outside project root "${projectRoot}". File logging disabled.`);
37
+ }
38
+ // If not TTY, this critical error will be logged to a file if possible by the initialized logger later,
39
+ // or an MCP notification if configured. Suppressing direct console output here for stdio clients.
36
40
  }
37
41
  /**
38
42
  * Singleton Logger wrapping Winston, adapted for MCP.
@@ -51,10 +55,11 @@ class Logger {
51
55
  */
52
56
  async initialize(level = 'info') {
53
57
  if (this.initialized) {
54
- // Avoid console.warn in stdio mode, this will be logged via this.info later if needed.
55
58
  if (process.stdout.isTTY) {
59
+ // This warning is only relevant for interactive sessions.
56
60
  console.warn('Logger already initialized.');
57
61
  }
62
+ // If already initialized, subsequent info log will indicate this.
58
63
  return;
59
64
  }
60
65
  this.currentMcpLevel = level;
@@ -71,7 +76,10 @@ class Logger {
71
76
  }
72
77
  }
73
78
  catch (err) {
74
- console.error(`Error creating logs directory at ${resolvedLogsDir}: ${err.message}. File logging disabled.`);
79
+ if (process.stdout.isTTY) {
80
+ console.error(`Error creating logs directory at ${resolvedLogsDir}: ${err.message}. File logging disabled.`);
81
+ }
82
+ // This error will be logged to file/MCP by the logger instance if it initializes successfully.
75
83
  }
76
84
  }
77
85
  // Common format for files
@@ -149,7 +157,9 @@ class Logger {
149
157
  */
150
158
  setLevel(newLevel) {
151
159
  if (!this.ensureInitialized()) {
152
- console.error("Cannot set level: Logger not initialized.");
160
+ if (process.stdout.isTTY) {
161
+ console.error("Cannot set level: Logger not initialized.");
162
+ }
153
163
  return;
154
164
  }
155
165
  if (!(newLevel in mcpLevelSeverity)) {
@@ -1,47 +1,79 @@
1
+ /**
2
+ * @file requestContext.ts
3
+ * @description Utilities for creating and managing request contexts.
4
+ * A request context is an object carrying a unique ID, timestamp, and other
5
+ * relevant data for logging, tracing, and processing.
6
+ */
1
7
  /**
2
8
  * Defines the structure for context information associated with a request or operation.
3
9
  */
4
10
  export interface RequestContext {
5
- /** Unique identifier generated for the request context instance. */
11
+ /**
12
+ * Unique ID for the context instance.
13
+ * Used for log correlation and request tracing.
14
+ */
6
15
  requestId: string;
7
- /** ISO 8601 timestamp indicating when the context was created. */
16
+ /**
17
+ * ISO 8601 timestamp indicating when the context was created.
18
+ */
8
19
  timestamp: string;
9
- /** Allows for additional, arbitrary key-value pairs for specific context needs. */
10
- [key: string]: any;
20
+ /**
21
+ * Allows arbitrary key-value pairs for specific context needs.
22
+ * Using `unknown` promotes type-safe access.
23
+ * Consumers must type-check/assert when accessing extended properties.
24
+ */
25
+ [key: string]: unknown;
11
26
  }
12
27
  /**
13
- * Configuration interface for request context utilities
28
+ * Configuration interface for the request context service.
29
+ * Extensible for future configuration.
30
+ * Placeholder for potential service-wide settings.
14
31
  */
15
32
  export interface ContextConfig {
16
- /** Custom configuration properties */
33
+ /** Custom configuration properties. Allows for arbitrary key-value pairs. */
17
34
  [key: string]: unknown;
18
35
  }
19
36
  /**
20
- * Operation context with request data
37
+ * Represents a broader operation context, optionally including
38
+ * a `RequestContext` and custom properties.
39
+ * Often used to pass contextual information for an operation or task.
21
40
  */
22
41
  export interface OperationContext {
23
- /** Request context data */
42
+ /** Optional request context data, adhering to the `RequestContext` structure. */
24
43
  requestContext?: RequestContext;
25
- /** Custom context properties */
44
+ /** Allows for additional, custom properties specific to the operation. */
26
45
  [key: string]: unknown;
27
46
  }
47
+ /**
48
+ * Primary export for request context functionalities.
49
+ * Provides methods to create and manage request contexts.
50
+ */
28
51
  export declare const requestContextService: {
52
+ /**
53
+ * Internal configuration store.
54
+ * Initialized empty, updatable via `configure`.
55
+ */
29
56
  config: ContextConfig;
30
57
  /**
31
- * Configure service settings
32
- * @param config New configuration
33
- * @returns Updated configuration
58
+ * Configures the service with new settings, merging with existing config.
59
+ *
60
+ * @param config - A partial `ContextConfig` object containing settings to update.
61
+ * @returns A shallow copy of the updated configuration.
34
62
  */
35
63
  configure(config: Partial<ContextConfig>): ContextConfig;
36
64
  /**
37
- * Get current configuration
38
- * @returns Current configuration
65
+ * Retrieves a shallow copy of the current service configuration.
66
+ *
67
+ * @returns A shallow copy of the current `ContextConfig`.
39
68
  */
40
69
  getConfig(): ContextConfig;
41
70
  /**
42
- * Create a request context with unique ID and timestamp
43
- * @param additionalContext Additional context properties
44
- * @returns Request context object
71
+ * Creates a new request context with a unique `requestId` and `timestamp`.
72
+ * Custom properties can be added via `additionalContext`.
73
+ *
74
+ * @param additionalContext - An optional record of key-value pairs to be
75
+ * included in the request context. Defaults to an empty object.
76
+ * @returns A `RequestContext` object.
45
77
  */
46
78
  createRequestContext(additionalContext?: Record<string, unknown>): RequestContext;
47
79
  };
@@ -1,48 +1,68 @@
1
+ /**
2
+ * @file requestContext.ts
3
+ * @description Utilities for creating and managing request contexts.
4
+ * A request context is an object carrying a unique ID, timestamp, and other
5
+ * relevant data for logging, tracing, and processing.
6
+ */
1
7
  import { logger } from './logger.js';
2
8
  // Import utils from the main barrel file (generateUUID from ../security/idGenerator.js)
3
9
  import { generateUUID } from '../index.js';
4
- // Direct instance for request context utilities
10
+ /**
11
+ * Service instance for managing request context operations.
12
+ * Singleton-like object to configure the service and create contexts.
13
+ */
5
14
  const requestContextServiceInstance = {
15
+ /**
16
+ * Internal configuration store.
17
+ * Initialized empty, updatable via `configure`.
18
+ */
6
19
  config: {},
7
20
  /**
8
- * Configure service settings
9
- * @param config New configuration
10
- * @returns Updated configuration
21
+ * Configures the service with new settings, merging with existing config.
22
+ *
23
+ * @param config - A partial `ContextConfig` object containing settings to update.
24
+ * @returns A shallow copy of the updated configuration.
11
25
  */
12
26
  configure(config) {
13
27
  this.config = {
14
28
  ...this.config,
15
- ...config
29
+ ...config,
16
30
  };
17
- logger.debug('RequestContext configuration updated', { config: this.config });
18
- return { ...this.config };
31
+ logger.debug('RequestContextService configuration updated', { newConfig: this.config });
32
+ return { ...this.config }; // Return a copy to prevent direct mutation
19
33
  },
20
34
  /**
21
- * Get current configuration
22
- * @returns Current configuration
35
+ * Retrieves a shallow copy of the current service configuration.
36
+ *
37
+ * @returns A shallow copy of the current `ContextConfig`.
23
38
  */
24
39
  getConfig() {
25
- return { ...this.config };
40
+ return { ...this.config }; // Return a copy
26
41
  },
27
42
  /**
28
- * Create a request context with unique ID and timestamp
29
- * @param additionalContext Additional context properties
30
- * @returns Request context object
43
+ * Creates a new request context with a unique `requestId` and `timestamp`.
44
+ * Custom properties can be added via `additionalContext`.
45
+ *
46
+ * @param additionalContext - An optional record of key-value pairs to be
47
+ * included in the request context. Defaults to an empty object.
48
+ * @returns A `RequestContext` object.
31
49
  */
32
50
  createRequestContext(additionalContext = {}) {
33
- const requestId = generateUUID(); // Use imported generateUUID
51
+ const requestId = generateUUID();
34
52
  const timestamp = new Date().toISOString();
35
- return {
53
+ const context = {
36
54
  requestId,
37
55
  timestamp,
38
- ...additionalContext
56
+ ...additionalContext,
39
57
  };
58
+ // logger.debug('Request context created', { requestId }); // Optional: log context creation
59
+ return context;
40
60
  },
41
- // generateSecureRandomString function removed as it was unused and redundant
61
+ // generateSecureRandomString function was previously here but removed as it was unused and redundant.
62
+ // Its functionality, if needed for secure random strings, should be sourced from a dedicated crypto/security module.
42
63
  };
43
- // Export the instance directly
64
+ /**
65
+ * Primary export for request context functionalities.
66
+ * Provides methods to create and manage request contexts.
67
+ */
44
68
  export const requestContextService = requestContextServiceInstance;
45
- // Removed delegate functions and default export for simplicity.
46
- // Users should import and use `requestContextService` directly.
47
- // e.g., import { requestContextService } from './requestContext.js';
48
- // requestContextService.createRequestContext();
@@ -31,7 +31,6 @@ export async function countTokens(text, context) {
31
31
  context: context,
32
32
  input: { textSample: text.substring(0, 50) + '...' }, // Log sanitized input
33
33
  errorCode: BaseErrorCode.INTERNAL_ERROR, // Use INTERNAL_ERROR for external lib issues
34
- rethrow: true // Rethrow as McpError
35
34
  // Removed onErrorReturn as we now rethrow
36
35
  });
37
36
  }
@@ -118,7 +117,6 @@ context) {
118
117
  context: context,
119
118
  input: { messageCount: messages.length }, // Log sanitized input
120
119
  errorCode: BaseErrorCode.INTERNAL_ERROR, // Use INTERNAL_ERROR
121
- rethrow: true // Rethrow as McpError
122
120
  // Removed onErrorReturn
123
121
  });
124
122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-ts-template",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "TypeScript template for building Model Context Protocol (MCP) servers & clients. Features production-ready utilities, stdio/HTTP transports (with JWT auth), examples, and type safety. Ideal starting point for creating MCP-based applications.",
5
5
  "main": "dist/index.js",
6
6
  "files": [