UNPKG

@toolprint/hypertool-mcp

Version:

HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers

234 lines • 7.84 kB
/** * 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