mcp-ts-template 1.2.3 → 1.2.5

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.4-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.2.3-blue.svg)](./CHANGELOG.md)
6
+ [![Version](https://img.shields.io/badge/Version-1.2.5-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)
@@ -25,6 +25,8 @@ export declare const config: {
25
25
  mcpServerVersion: string;
26
26
  /** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
27
27
  logLevel: string;
28
+ /** Absolute path to the logs directory. From `LOGS_DIR` env var. */
29
+ logsPath: string;
28
30
  /** Runtime environment. From `NODE_ENV` env var. Default: "development". */
29
31
  environment: string;
30
32
  /** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
@@ -15,13 +15,48 @@
15
15
  * @module src/config/index
16
16
  */
17
17
  import dotenv from "dotenv";
18
- import { readFileSync } from "fs";
19
- import { dirname, join } from "path";
18
+ import { existsSync, mkdirSync, readFileSync, statSync } from "fs";
19
+ import path, { dirname, join } from "path";
20
20
  import { fileURLToPath } from "url";
21
21
  import { z } from "zod";
22
22
  dotenv.config();
23
- const __dirname = dirname(fileURLToPath(import.meta.url));
24
- const pkgPath = join(__dirname, "../../package.json");
23
+ // --- Determine Project Root ---
24
+ /**
25
+ * Finds the project root directory by searching upwards for package.json.
26
+ * @param startDir The directory to start searching from.
27
+ * @returns The absolute path to the project root, or throws an error if not found.
28
+ */
29
+ const findProjectRoot = (startDir) => {
30
+ let currentDir = startDir;
31
+ while (true) {
32
+ const packageJsonPath = join(currentDir, 'package.json');
33
+ if (existsSync(packageJsonPath)) {
34
+ return currentDir;
35
+ }
36
+ const parentDir = dirname(currentDir);
37
+ if (parentDir === currentDir) {
38
+ // Reached the root of the filesystem without finding package.json
39
+ throw new Error(`Could not find project root (package.json) starting from ${startDir}`);
40
+ }
41
+ currentDir = parentDir;
42
+ }
43
+ };
44
+ let projectRoot;
45
+ try {
46
+ // For ESM, __dirname is not available directly.
47
+ // import.meta.url gives the URL of the current module.
48
+ const currentModuleDir = dirname(fileURLToPath(import.meta.url));
49
+ projectRoot = findProjectRoot(currentModuleDir);
50
+ }
51
+ catch (error) {
52
+ console.error(`FATAL: Error determining project root: ${error.message}`);
53
+ // Fallback to process.cwd() if project root cannot be determined.
54
+ // This might happen in unusual execution environments.
55
+ projectRoot = process.cwd();
56
+ console.warn(`Warning: Using process.cwd() (${projectRoot}) as fallback project root.`);
57
+ }
58
+ // --- End Determine Project Root ---
59
+ const pkgPath = join(projectRoot, "package.json"); // Use determined projectRoot
25
60
  let pkg = { name: "mcp-ts-template", version: "0.0.0" };
26
61
  try {
27
62
  pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
@@ -43,6 +78,8 @@ const EnvSchema = z.object({
43
78
  MCP_SERVER_VERSION: z.string().optional(),
44
79
  /** Minimum logging level. See `McpLogLevel` in logger utility. Default: "debug". */
45
80
  MCP_LOG_LEVEL: z.string().default("debug"),
81
+ /** Directory for log files. Defaults to "logs" in project root. */
82
+ LOGS_DIR: z.string().default(path.join(projectRoot, "logs")),
46
83
  /** Runtime environment (e.g., "development", "production"). Default: "development". */
47
84
  NODE_ENV: z.string().default("development"),
48
85
  /** MCP communication transport ("stdio" or "http"). Default: "stdio". */
@@ -117,6 +154,67 @@ if (!parsedEnv.success) {
117
154
  // Consider throwing an error in production for critical misconfigurations.
118
155
  }
119
156
  const env = parsedEnv.success ? parsedEnv.data : EnvSchema.parse({});
157
+ // --- Directory Ensurance Function ---
158
+ /**
159
+ * Ensures a directory exists and is within the project root.
160
+ * @param dirPath The desired path for the directory (can be relative or absolute).
161
+ * @param rootDir The root directory of the project to contain the directory.
162
+ * @param dirName The name of the directory type for logging (e.g., "logs").
163
+ * @returns The validated, absolute path to the directory, or null if invalid.
164
+ */
165
+ const ensureDirectory = (dirPath, rootDir, dirName) => {
166
+ const resolvedDirPath = path.isAbsolute(dirPath) ? dirPath : path.resolve(rootDir, dirPath);
167
+ // Ensure the resolved path is within the project root boundary
168
+ if (!resolvedDirPath.startsWith(rootDir + path.sep) && resolvedDirPath !== rootDir) {
169
+ if (process.stdout.isTTY) {
170
+ console.error(`Error: ${dirName} path "${dirPath}" resolves to "${resolvedDirPath}", which is outside the project boundary "${rootDir}".`);
171
+ }
172
+ return null;
173
+ }
174
+ if (!existsSync(resolvedDirPath)) {
175
+ try {
176
+ mkdirSync(resolvedDirPath, { recursive: true });
177
+ if (process.stdout.isTTY) {
178
+ console.log(`Created ${dirName} directory: ${resolvedDirPath}`);
179
+ }
180
+ }
181
+ catch (err) {
182
+ const errorMessage = err instanceof Error ? err.message : String(err);
183
+ if (process.stdout.isTTY) {
184
+ console.error(`Error creating ${dirName} directory at ${resolvedDirPath}: ${errorMessage}`);
185
+ }
186
+ return null;
187
+ }
188
+ }
189
+ else {
190
+ try {
191
+ const stats = statSync(resolvedDirPath);
192
+ if (!stats.isDirectory()) {
193
+ if (process.stdout.isTTY) {
194
+ console.error(`Error: ${dirName} path ${resolvedDirPath} exists but is not a directory.`);
195
+ }
196
+ return null;
197
+ }
198
+ }
199
+ catch (statError) {
200
+ if (process.stdout.isTTY) {
201
+ console.error(`Error accessing ${dirName} path ${resolvedDirPath}: ${statError.message}`);
202
+ }
203
+ return null;
204
+ }
205
+ }
206
+ return resolvedDirPath;
207
+ };
208
+ // --- End Directory Ensurance Function ---
209
+ // --- Logs Directory Handling ---
210
+ const validatedLogsPath = ensureDirectory(env.LOGS_DIR, projectRoot, "logs");
211
+ if (!validatedLogsPath) {
212
+ if (process.stdout.isTTY) {
213
+ console.error("FATAL: Logs directory configuration is invalid or could not be created. Please check permissions and path. Exiting.");
214
+ }
215
+ process.exit(1); // Exit if logs directory is not usable
216
+ }
217
+ // --- End Logs Directory Handling ---
120
218
  /**
121
219
  * Main application configuration object.
122
220
  * Aggregates settings from validated environment variables and `package.json`.
@@ -128,6 +226,8 @@ export const config = {
128
226
  mcpServerVersion: env.MCP_SERVER_VERSION || pkg.version,
129
227
  /** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
130
228
  logLevel: env.MCP_LOG_LEVEL,
229
+ /** Absolute path to the logs directory. From `LOGS_DIR` env var. */
230
+ logsPath: validatedLogsPath,
131
231
  /** Runtime environment. From `NODE_ENV` env var. Default: "development". */
132
232
  environment: env.NODE_ENV,
133
233
  /** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
@@ -406,7 +406,11 @@ export async function startHttpTransport(createServerInstanceFn, parentContext)
406
406
  ...baseSessionReqContext,
407
407
  sessionId,
408
408
  });
409
- res.status(404).send("Session not found or expired");
409
+ res.status(404).json({
410
+ jsonrpc: "2.0",
411
+ error: { code: -32004, message: "Session not found or expired" },
412
+ id: null // Or a relevant request identifier if available from context
413
+ });
410
414
  return;
411
415
  }
412
416
  try {
@@ -422,7 +426,11 @@ export async function startHttpTransport(createServerInstanceFn, parentContext)
422
426
  stack: err instanceof Error ? err.stack : undefined,
423
427
  });
424
428
  if (!res.headersSent) {
425
- res.status(500).send("Internal Server Error");
429
+ res.status(500).json({
430
+ jsonrpc: "2.0",
431
+ error: { code: -32603, message: "Internal Server Error" },
432
+ id: null // Or a relevant request identifier
433
+ });
426
434
  }
427
435
  }
428
436
  };
@@ -433,10 +441,16 @@ export async function startHttpTransport(createServerInstanceFn, parentContext)
433
441
  try {
434
442
  logger.debug("Attempting to start HTTP server with retry logic...", transportContext);
435
443
  const actualPort = await startHttpServerWithRetry(serverInstance, config.mcpHttpPort, config.mcpHttpHost, MAX_PORT_RETRIES, transportContext);
436
- const protocol = config.environment === "production" ? "https" : "http";
437
- const serverAddress = `${protocol}://${config.mcpHttpHost}:${actualPort}${MCP_ENDPOINT_PATH}`;
444
+ let serverAddressLog = `http://${config.mcpHttpHost}:${actualPort}${MCP_ENDPOINT_PATH}`;
445
+ let productionNote = "";
446
+ if (config.environment === "production") {
447
+ // The server itself runs HTTP, but it's expected to be behind an HTTPS proxy in production.
448
+ // The log reflects the effective public-facing URL.
449
+ serverAddressLog = `https://${config.mcpHttpHost}:${actualPort}${MCP_ENDPOINT_PATH}`;
450
+ productionNote = ` (via HTTPS, ensure reverse proxy is configured)`;
451
+ }
438
452
  if (process.stdout.isTTY) {
439
- console.log(`\n🚀 MCP Server running in HTTP mode at: ${serverAddress}\n (MCP Spec: 2025-03-26 Streamable HTTP Transport)\n`);
453
+ console.log(`\n🚀 MCP Server running in HTTP mode at: ${serverAddressLog}${productionNote}\n (MCP Spec: 2025-03-26 Streamable HTTP Transport)\n`);
440
454
  }
441
455
  }
442
456
  catch (err) {
@@ -0,0 +1 @@
1
+ export * from './llm-providers';
@@ -0,0 +1 @@
1
+ export * from './llm-providers';
@@ -0,0 +1 @@
1
+ export * from './openRouterProvider';
@@ -0,0 +1 @@
1
+ export * from './openRouterProvider';
@@ -1,6 +1,6 @@
1
1
  import { ChatCompletion, ChatCompletionChunk, ChatCompletionCreateParamsNonStreaming, ChatCompletionCreateParamsStreaming } from "openai/resources/chat/completions";
2
2
  import { Stream } from "openai/streaming";
3
- import { OperationContext, RequestContext } from "../utils/internal/requestContext.js";
3
+ import { OperationContext, RequestContext } from "../../utils/internal/requestContext.js";
4
4
  /**
5
5
  * Defines the parameters for an OpenRouter chat completion request.
6
6
  * This type extends standard OpenAI chat completion parameters and includes
@@ -6,13 +6,13 @@
6
6
  * @module src/services/openRouterProvider
7
7
  */
8
8
  import OpenAI from "openai";
9
- import { config } from "../config/index.js";
10
- import { BaseErrorCode, McpError } from "../types-global/errors.js";
11
- import { ErrorHandler } from "../utils/internal/errorHandler.js";
12
- import { logger } from "../utils/internal/logger.js";
13
- import { requestContextService, } from "../utils/internal/requestContext.js";
14
- import { rateLimiter } from "../utils/security/rateLimiter.js";
15
- import { sanitization } from "../utils/security/sanitization.js";
9
+ import { config } from "../../config/index.js";
10
+ import { BaseErrorCode, McpError } from "../../types-global/errors.js";
11
+ import { ErrorHandler } from "../../utils/internal/errorHandler.js";
12
+ import { logger } from "../../utils/internal/logger.js";
13
+ import { requestContextService, } from "../../utils/internal/requestContext.js";
14
+ import { rateLimiter } from "../../utils/security/rateLimiter.js";
15
+ import { sanitization } from "../../utils/security/sanitization.js";
16
16
  const YOUR_SITE_URL = config.openrouterAppUrl;
17
17
  const YOUR_SITE_NAME = config.openrouterAppName;
18
18
  /**
@@ -42,6 +42,7 @@ export declare class Logger {
42
42
  private mcpNotificationSender?;
43
43
  private currentMcpLevel;
44
44
  private currentWinstonLevel;
45
+ private readonly MCP_NOTIFICATION_STACK_TRACE_MAX_LENGTH;
45
46
  private readonly LOG_FILE_MAX_SIZE;
46
47
  private readonly LOG_MAX_FILES;
47
48
  /** @private */
@@ -62,6 +63,13 @@ export declare class Logger {
62
63
  * @param newLevel - The new minimum MCP log level to set.
63
64
  */
64
65
  setLevel(newLevel: McpLogLevel): void;
66
+ /**
67
+ * Configures the console transport based on the current log level and TTY status.
68
+ * Adds or removes the console transport as needed.
69
+ * @returns {{ enabled: boolean, message: string | null }} Status of console logging.
70
+ * @private
71
+ */
72
+ private _configureConsoleTransport;
65
73
  /**
66
74
  * Gets the singleton instance of the Logger.
67
75
  * @returns The singleton Logger instance.
@@ -36,19 +36,9 @@ const mcpToWinstonLevel = {
36
36
  alert: "error",
37
37
  emerg: "error",
38
38
  };
39
- const projectRoot = process.cwd(); // Use current working directory as project root
40
- const logsDir = path.join(projectRoot, "logs");
41
- // Security check for the logs directory path
42
- const resolvedLogsDir = path.resolve(logsDir); // Should be projectRoot/logs
43
- const isLogsDirSafe = resolvedLogsDir.startsWith(projectRoot + path.sep) &&
44
- resolvedLogsDir !== projectRoot;
45
- if (!isLogsDirSafe) {
46
- // This case should ideally not be hit if logsDir is simply projectRoot + /logs
47
- // But it's a safeguard if path.join or path.resolve behaves unexpectedly or logsDir is manipulated.
48
- if (process.stdout.isTTY) {
49
- console.error(`FATAL: Resolved logs directory "${resolvedLogsDir}" is not safely within project root "${projectRoot}". File logging will be disabled.`);
50
- }
51
- }
39
+ // The logsPath from config is already resolved and validated by src/config/index.ts
40
+ const resolvedLogsDir = config.logsPath;
41
+ const isLogsDirSafe = !!resolvedLogsDir; // If logsPath is set, it's considered safe by config logic.
52
42
  /**
53
43
  * Creates the Winston console log format.
54
44
  * @returns The Winston log format for console output.
@@ -95,6 +85,7 @@ export class Logger {
95
85
  this.initialized = false;
96
86
  this.currentMcpLevel = "info";
97
87
  this.currentWinstonLevel = "info";
88
+ this.MCP_NOTIFICATION_STACK_TRACE_MAX_LENGTH = 1024;
98
89
  this.LOG_FILE_MAX_SIZE = 5 * 1024 * 1024; // 5MB
99
90
  this.LOG_MAX_FILES = 5;
100
91
  }
@@ -114,21 +105,27 @@ export class Logger {
114
105
  }
115
106
  this.currentMcpLevel = level;
116
107
  this.currentWinstonLevel = mcpToWinstonLevel[level];
117
- let logsDirCreatedMessage = null;
108
+ let logsDirCreatedMessage = null; // This message is now informational as creation is handled by config
118
109
  if (isLogsDirSafe) {
119
- try {
120
- if (!fs.existsSync(resolvedLogsDir)) {
110
+ // Directory creation is handled by config/index.ts ensureDirectory.
111
+ // We can log if it was newly created by checking if it existed before config ran,
112
+ // but that's complex. For now, we assume config handled it.
113
+ // If resolvedLogsDir is set, config ensures it exists.
114
+ if (!fs.existsSync(resolvedLogsDir)) {
115
+ // This case should ideally not be hit if config.logsPath is correctly set up and validated.
116
+ // However, if it somehow occurs (e.g. dir deleted after config init but before logger init),
117
+ // we attempt to create it.
118
+ try {
121
119
  await fs.promises.mkdir(resolvedLogsDir, { recursive: true });
122
- logsDirCreatedMessage = `Created logs directory: ${resolvedLogsDir}`;
120
+ logsDirCreatedMessage = `Re-created logs directory (should have been created by config): ${resolvedLogsDir}`;
123
121
  }
124
- }
125
- catch (err) {
126
- if (process.stdout.isTTY) {
127
- const errorMessage = err instanceof Error ? err.message : String(err);
128
- console.error(`Error creating logs directory at ${resolvedLogsDir}: ${errorMessage}. File logging disabled.`);
122
+ catch (err) {
123
+ if (process.stdout.isTTY) {
124
+ const errorMessage = err instanceof Error ? err.message : String(err);
125
+ console.error(`Error creating logs directory at ${resolvedLogsDir}: ${errorMessage}. File logging disabled.`);
126
+ }
127
+ throw err; // Critical if logs dir cannot be ensured
129
128
  }
130
- // Rethrow the error to ensure startup fails if logs directory cannot be created
131
- throw err;
132
129
  }
133
130
  }
134
131
  const fileFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json());
@@ -163,48 +160,33 @@ export class Logger {
163
160
  }
164
161
  else {
165
162
  if (process.stdout.isTTY) {
166
- console.warn("File logging disabled due to unsafe logs directory path.");
163
+ console.warn("File logging disabled as logsPath is not configured or invalid.");
167
164
  }
168
165
  }
169
- let consoleLoggingEnabledMessage = null;
170
- let consoleLoggingSkippedMessage = null;
171
- if (this.currentMcpLevel === "debug" && process.stdout.isTTY) {
172
- const consoleFormat = createWinstonConsoleFormat();
173
- transports.push(new winston.transports.Console({
174
- level: "debug",
175
- format: consoleFormat,
176
- }));
177
- consoleLoggingEnabledMessage =
178
- "Console logging enabled at level: debug (stdout is TTY)";
179
- }
180
- else if (this.currentMcpLevel === "debug" && !process.stdout.isTTY) {
181
- consoleLoggingSkippedMessage =
182
- "Console logging skipped: Level is debug, but stdout is not a TTY (likely stdio transport).";
183
- }
184
166
  this.winstonLogger = winston.createLogger({
185
167
  level: this.currentWinstonLevel,
186
168
  transports,
187
169
  exitOnError: false,
188
170
  });
171
+ // Configure console transport after Winston logger is created
172
+ const consoleStatus = this._configureConsoleTransport();
189
173
  const initialContext = {
190
174
  loggerSetup: true,
191
175
  requestId: "logger-init-deferred",
192
176
  timestamp: new Date().toISOString(),
193
177
  };
194
- if (logsDirCreatedMessage) {
178
+ if (logsDirCreatedMessage) { // Log if we had to re-create it
195
179
  this.info(logsDirCreatedMessage, initialContext);
196
180
  }
197
- if (consoleLoggingEnabledMessage) {
198
- this.info(consoleLoggingEnabledMessage, initialContext);
199
- }
200
- if (consoleLoggingSkippedMessage) {
201
- this.info(consoleLoggingSkippedMessage, initialContext);
181
+ if (consoleStatus.message) {
182
+ this.info(consoleStatus.message, initialContext);
202
183
  }
203
184
  this.initialized = true;
204
- this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${process.stdout.isTTY && this.currentMcpLevel === "debug" ? "enabled" : "disabled"}`, {
185
+ this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${consoleStatus.enabled ? "enabled" : "disabled"}`, {
205
186
  loggerSetup: true,
206
187
  requestId: "logger-post-init",
207
188
  timestamp: new Date().toISOString(),
189
+ logsPathUsed: resolvedLogsDir,
208
190
  });
209
191
  }
210
192
  /**
@@ -243,24 +225,46 @@ export class Logger {
243
225
  const oldLevel = this.currentMcpLevel;
244
226
  this.currentMcpLevel = newLevel;
245
227
  this.currentWinstonLevel = mcpToWinstonLevel[newLevel];
246
- this.winstonLogger.level = this.currentWinstonLevel;
228
+ if (this.winstonLogger) { // Ensure winstonLogger is defined
229
+ this.winstonLogger.level = this.currentWinstonLevel;
230
+ }
231
+ const consoleStatus = this._configureConsoleTransport();
232
+ if (oldLevel !== newLevel) {
233
+ this.info(`Log level changed. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${consoleStatus.enabled ? "enabled" : "disabled"}`, setLevelContext);
234
+ if (consoleStatus.message && consoleStatus.message !== "Console logging status unchanged.") {
235
+ this.info(consoleStatus.message, setLevelContext);
236
+ }
237
+ }
238
+ }
239
+ /**
240
+ * Configures the console transport based on the current log level and TTY status.
241
+ * Adds or removes the console transport as needed.
242
+ * @returns {{ enabled: boolean, message: string | null }} Status of console logging.
243
+ * @private
244
+ */
245
+ _configureConsoleTransport() {
246
+ if (!this.winstonLogger) {
247
+ return { enabled: false, message: "Cannot configure console: Winston logger not initialized." };
248
+ }
247
249
  const consoleTransport = this.winstonLogger.transports.find((t) => t instanceof winston.transports.Console);
248
- const shouldHaveConsole = newLevel === "debug" && process.stdout.isTTY;
250
+ const shouldHaveConsole = this.currentMcpLevel === "debug" && process.stdout.isTTY;
251
+ let message = null;
249
252
  if (shouldHaveConsole && !consoleTransport) {
250
253
  const consoleFormat = createWinstonConsoleFormat();
251
254
  this.winstonLogger.add(new winston.transports.Console({
252
- level: "debug",
255
+ level: "debug", // Console always logs debug if enabled
253
256
  format: consoleFormat,
254
257
  }));
255
- this.info("Console logging dynamically enabled.", setLevelContext);
258
+ message = "Console logging enabled (level: debug, stdout is TTY).";
256
259
  }
257
260
  else if (!shouldHaveConsole && consoleTransport) {
258
261
  this.winstonLogger.remove(consoleTransport);
259
- this.info("Console logging dynamically disabled.", setLevelContext);
262
+ message = "Console logging disabled (level not debug or stdout not TTY).";
260
263
  }
261
- if (oldLevel !== newLevel) {
262
- this.info(`Log level changed. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${shouldHaveConsole ? "enabled" : "disabled"}`, setLevelContext);
264
+ else {
265
+ message = "Console logging status unchanged.";
263
266
  }
267
+ return { enabled: shouldHaveConsole, message };
264
268
  }
265
269
  /**
266
270
  * Gets the singleton instance of the Logger.
@@ -316,7 +320,7 @@ export class Logger {
316
320
  mcpDataPayload.error = { message: error.message };
317
321
  // Include stack trace in debug mode for MCP notifications, truncated for brevity
318
322
  if (this.currentMcpLevel === "debug" && error.stack) {
319
- mcpDataPayload.error.stack = error.stack.substring(0, 500);
323
+ mcpDataPayload.error.stack = error.stack.substring(0, this.MCP_NOTIFICATION_STACK_TRACE_MAX_LENGTH);
320
324
  }
321
325
  }
322
326
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-ts-template",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
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": [
@@ -9,6 +9,7 @@
9
9
  "bin": {
10
10
  "mcp-ts-template": "./dist/index.js"
11
11
  },
12
+ "exports": "./dist/index.js",
12
13
  "type": "module",
13
14
  "repository": {
14
15
  "type": "git",
@@ -31,17 +32,17 @@
31
32
  "inspector": "mcp-inspector --config mcp.json --server mcp-ts-template"
32
33
  },
33
34
  "dependencies": {
34
- "@modelcontextprotocol/sdk": "^1.11.4",
35
+ "@modelcontextprotocol/sdk": "^1.11.5",
35
36
  "@types/jsonwebtoken": "^9.0.9",
36
- "@types/node": "^22.15.18",
37
+ "@types/node": "^22.15.21",
37
38
  "@types/sanitize-html": "^2.16.0",
38
- "@types/validator": "13.15.0",
39
+ "@types/validator": "13.15.1",
39
40
  "chrono-node": "^2.8.0",
40
41
  "dotenv": "^16.5.0",
41
42
  "express": "^5.1.0",
42
43
  "ignore": "^7.0.4",
43
44
  "jsonwebtoken": "^9.0.2",
44
- "openai": "^4.100.0",
45
+ "openai": "^4.102.0",
45
46
  "partial-json": "^0.1.7",
46
47
  "sanitize-html": "^2.17.0",
47
48
  "tiktoken": "^1.0.21",
@@ -51,7 +52,7 @@
51
52
  "winston": "^3.17.0",
52
53
  "winston-daily-rotate-file": "^5.0.0",
53
54
  "yargs": "^17.7.2",
54
- "zod": "^3.24.4"
55
+ "zod": "^3.25.20"
55
56
  },
56
57
  "keywords": [
57
58
  "typescript",
@@ -68,8 +69,11 @@
68
69
  "jwt",
69
70
  "authentication"
70
71
  ],
71
- "author": "Casey Hand @cyanheads",
72
+ "author": "Casey Hand <casey@caseyjhand.com> (https://github.com/cyanheads/mcp-ts-template#readme)",
72
73
  "license": "Apache-2.0",
74
+ "engines": {
75
+ "node": ">=16.0.0"
76
+ },
73
77
  "devDependencies": {
74
78
  "@types/express": "^5.0.2",
75
79
  "@types/js-yaml": "^4.0.9",