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 +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +104 -4
- package/dist/mcp-server/transports/httpTransport.js +19 -5
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/llm-providers/index.d.ts +1 -0
- package/dist/services/llm-providers/index.js +1 -0
- package/dist/services/{openRouterProvider.d.ts → llm-providers/openRouterProvider.d.ts} +1 -1
- package/dist/services/{openRouterProvider.js → llm-providers/openRouterProvider.js} +7 -7
- package/dist/utils/internal/logger.d.ts +8 -0
- package/dist/utils/internal/logger.js +59 -55
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
4
|
[](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
5
5
|
[](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/changelog.mdx)
|
|
6
|
-
[](./CHANGELOG.md)
|
|
7
7
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
8
8
|
[](https://github.com/cyanheads/mcp-ts-template/issues)
|
|
9
9
|
[](https://github.com/cyanheads/mcp-ts-template)
|
package/dist/config/index.d.ts
CHANGED
|
@@ -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". */
|
package/dist/config/index.js
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
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).
|
|
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).
|
|
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
|
-
|
|
437
|
-
|
|
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: ${
|
|
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 "
|
|
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 "
|
|
10
|
-
import { BaseErrorCode, McpError } from "
|
|
11
|
-
import { ErrorHandler } from "
|
|
12
|
-
import { logger } from "
|
|
13
|
-
import { requestContextService, } from "
|
|
14
|
-
import { rateLimiter } from "
|
|
15
|
-
import { sanitization } from "
|
|
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
|
-
|
|
40
|
-
const
|
|
41
|
-
//
|
|
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
|
-
|
|
120
|
-
|
|
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 = `
|
|
120
|
+
logsDirCreatedMessage = `Re-created logs directory (should have been created by config): ${resolvedLogsDir}`;
|
|
123
121
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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 (
|
|
198
|
-
this.info(
|
|
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: ${
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
262
|
+
message = "Console logging disabled (level not debug or stdout not TTY).";
|
|
260
263
|
}
|
|
261
|
-
|
|
262
|
-
|
|
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,
|
|
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
|
+
"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.
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.11.5",
|
|
35
36
|
"@types/jsonwebtoken": "^9.0.9",
|
|
36
|
-
"@types/node": "^22.15.
|
|
37
|
+
"@types/node": "^22.15.21",
|
|
37
38
|
"@types/sanitize-html": "^2.16.0",
|
|
38
|
-
"@types/validator": "13.15.
|
|
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.
|
|
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.
|
|
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",
|