@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
234 lines • 7.84 kB
JavaScript
/**
* MCP Logger wrapper that implements the LoggerInterface
* Wraps the existing mcp-logger implementation for feature flag compatibility
*/
import { createLogger, createLoggerSync, } from "@toolprint/mcp-logger";
import { join } from "path";
import { homedir } from "os";
import { APP_TECHNICAL_NAME, BRAND_NAME } from "../../config/appConfig.js";
export const DEFAULT_MCP_LOGGING_CONFIG = {
level: process.env.NODE_ENV === "development" ? "debug" : "info",
enableConsole: true,
enableFile: true,
serverName: APP_TECHNICAL_NAME,
format: "pretty",
};
/**
* Wrapper class that adapts mcp-logger to the LoggerInterface
*/
export class McpLoggerWrapper {
mcpLogger;
config;
runtimeOptions;
constructor(config = {}, runtimeOptions) {
this.config = { ...DEFAULT_MCP_LOGGING_CONFIG, ...config };
this.runtimeOptions = runtimeOptions;
// Determine format from environment variable or config
if (process.env.LOG_FORMAT === "json") {
this.config.format = "json";
}
// Override level if provided in runtime options
if (runtimeOptions?.logLevel) {
this.config.level = runtimeOptions.logLevel;
}
// Start with sync logger for immediate availability, then upgrade to async
const options = this.configToLoggerOptions(this.config, this.runtimeOptions);
this.mcpLogger = createLoggerSync(options);
// Upgrade to async logger in background for full file transport functionality
this.initializeAsync().catch(() => {
// Continue with sync logger if async fails
});
}
/**
* Initialize with async logger for full functionality
*/
async initializeAsync() {
try {
const options = this.configToLoggerOptions(this.config, this.runtimeOptions);
this.mcpLogger = await createLogger(options);
}
catch (error) {
// Continue with sync logger if async initialization fails
this.mcpLogger.warn("Failed to initialize async logger, file transport may not work", {
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
});
}
}
/**
* Convert LoggingConfig to mcp-logger LoggerOptions with transport-aware mode
*/
configToLoggerOptions(config, runtimeOptions) {
const transports = [];
// Determine ServerMode based on runtime transport
const mode = runtimeOptions?.transport === "stdio" ? "local" : "remote";
// Console/stderr transport - use stderr for stdio mode, console for http mode
if (config.enableConsole) {
if (mode === "local") {
// stdio mode: use stderr to avoid protocol corruption
transports.push({
type: "stderr",
enabled: true,
});
}
else {
// http mode: use console for visibility
transports.push({
type: "console",
enabled: true,
});
}
}
// File transport
if (config.enableFile) {
transports.push({
type: "file",
enabled: true,
options: {
path: this.getLogFilePath(config.serverName),
mkdir: true,
append: true,
},
});
}
return {
mode,
level: config.level,
format: config.format,
transports,
};
}
/**
* Get the log file path for a given server name
*/
getLogFilePath(serverName) {
const logDir = join(homedir(), `.${BRAND_NAME.toLowerCase()}`, serverName, "logs");
return join(logDir, `${serverName}.log`);
}
/**
* Format context for log messages with Error handling
*/
formatContext(context) {
if (context === undefined || context === null) {
return undefined;
}
if (context instanceof Error) {
return {
error: {
name: context.name,
message: context.message,
stack: context.stack,
},
};
}
if (typeof context === "object") {
return context;
}
// For primitive types, wrap in a context object
return { context };
}
// Public logging methods
fatal(message, context) {
// mcp-logger doesn't have fatal, use error with [FATAL] prefix
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.error(`[FATAL] ${message}`, formattedContext);
}
else {
this.mcpLogger.error(`[FATAL] ${message}`);
}
}
error(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.error(message, formattedContext);
}
else {
this.mcpLogger.error(message);
}
}
warn(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.warn(message, formattedContext);
}
else {
this.mcpLogger.warn(message);
}
}
info(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.info(message, formattedContext);
}
else {
this.mcpLogger.info(message);
}
}
debug(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.debug(message, formattedContext);
}
else {
this.mcpLogger.debug(message);
}
}
trace(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.trace(message, formattedContext);
}
else {
this.mcpLogger.trace(message);
}
}
/**
* Create child logger using native mcp-logger functionality
*/
child(bindings) {
const module = bindings.module;
const otherBindings = { ...bindings };
delete otherBindings.module;
// Create child logger with module context
// mcp-logger 0.0.7+ handles caching natively to prevent memory leaks
const childMcpLogger = this.mcpLogger.child({
context: {
module,
...otherBindings,
},
});
// Create new wrapper instance for the child
const childLogger = new McpLoggerWrapper(this.config, this.runtimeOptions);
childLogger.mcpLogger = childMcpLogger;
return childLogger;
}
/**
* Update configuration and reinitialize
*/
updateConfig(config) {
this.config = { ...this.config, ...config };
const options = this.configToLoggerOptions(this.config, this.runtimeOptions);
this.mcpLogger = createLoggerSync(options);
// Try to upgrade to async logger in background
this.initializeAsync().catch(() => {
// Ignore errors, sync logger will continue to work
});
}
/**
* Legacy compatibility - access to underlying mcp logger as pino
*/
get pino() {
return this.mcpLogger;
}
/**
* Access to underlying mcp logger
*/
get mcp() {
return this.mcpLogger;
}
}
//# sourceMappingURL=mcpLoggerWrapper.js.map