Skip to main content
Glama

hypertool-mcp

logging-backup.tsโ€ข10.4 kB
/** * Streamlined logging framework using @toolprint/mcp-logger 0.0.5 native features */ import { createLogger, createLoggerSync, Logger as McpLogger, LoggerOptions, TransportConfig, ServerMode, InternalLogLevel, } from "@toolprint/mcp-logger"; import { join } from "path"; import { homedir } from "os"; import { APP_TECHNICAL_NAME, BRAND_NAME } from "../config/appConfig.js"; import { RuntimeOptions } from "../types/runtime.js"; // Type for log context - can be any serializable object, Error, or unknown value type LogContext = | Record<string, unknown> | string | number | boolean | null | undefined | Error | unknown; export interface LoggingConfig { level: InternalLogLevel; enableConsole: boolean; enableFile: boolean; serverName: string; format: "json" | "pretty"; } export const DEFAULT_LOGGING_CONFIG: LoggingConfig = { level: process.env.NODE_ENV === "development" ? "debug" : "info", enableConsole: true, enableFile: true, serverName: APP_TECHNICAL_NAME, format: "pretty", }; /** * Create logging configuration based on runtime options and environment */ export function createLoggingConfig( runtimeOptions?: RuntimeOptions ): LoggingConfig { // Determine format from environment variable or default to pretty const format = (process.env.LOG_FORMAT === "json" ? "json" : "pretty") as | "json" | "pretty"; return { ...DEFAULT_LOGGING_CONFIG, format, // Override level if provided in runtime options level: (runtimeOptions?.logLevel as InternalLogLevel) || DEFAULT_LOGGING_CONFIG.level, }; } /** * Get the log file path for a given server name */ function getLogFilePath(serverName: string): string { const logDir = join( homedir(), `.${BRAND_NAME.toLowerCase()}`, serverName, "logs" ); return join(logDir, `${serverName}.log`); } /** * Convert LoggingConfig to mcp-logger LoggerOptions with transport-aware mode */ function configToLoggerOptions( config: LoggingConfig, runtimeOptions?: RuntimeOptions ): LoggerOptions { const transports: TransportConfig[] = []; // Determine ServerMode based on runtime transport const mode: ServerMode = 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: getLogFilePath(config.serverName), mkdir: true, append: true, }, }); } return { mode, level: config.level, format: config.format, transports, }; } /** * Logger class that wraps mcp-logger with our API compatibility layer */ export class Logger { private mcpLogger: McpLogger; private config: LoggingConfig; private runtimeOptions?: RuntimeOptions; constructor( config: Partial<LoggingConfig> = {}, runtimeOptions?: RuntimeOptions ) { this.config = { ...DEFAULT_LOGGING_CONFIG, ...config }; this.runtimeOptions = runtimeOptions; // Start with sync logger for immediate availability, then upgrade to async const options = 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 * This enables file transport which only works with async logger in mcp-logger 0.0.6 */ private async initializeAsync(): Promise<void> { try { const options = configToLoggerOptions(this.config, this.runtimeOptions); this.mcpLogger = await createLogger(options); } catch (error) { // Continue with sync logger if async initialization fails // File transport may not work, but console/stderr will still function // Log this through the sync logger (stderr/console will still work) this.mcpLogger.warn( "Failed to initialize async logger, file transport may not work", { error: { name: (error as Error).name, message: (error as Error).message, stack: (error as Error).stack, }, } ); } } // Helper method to format context for log messages private formatContext( context?: LogContext ): Record<string, unknown> | undefined { 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 as Record<string, unknown>; } // For primitive types, wrap in a context object return { context }; } // Public logging methods fatal(message: string, context?: LogContext): void { // mcp-logger doesn't have fatal, use error const formattedContext = this.formatContext(context); if (formattedContext) { this.mcpLogger.error(`[FATAL] ${message}`, formattedContext); } else { this.mcpLogger.error(`[FATAL] ${message}`); } } error(message: string, context?: LogContext): void { const formattedContext = this.formatContext(context); if (formattedContext) { this.mcpLogger.error(message, formattedContext); } else { this.mcpLogger.error(message); } } warn(message: string, context?: LogContext): void { const formattedContext = this.formatContext(context); if (formattedContext) { this.mcpLogger.warn(message, formattedContext); } else { this.mcpLogger.warn(message); } } info(message: string, context?: LogContext): void { const formattedContext = this.formatContext(context); if (formattedContext) { this.mcpLogger.info(message, formattedContext); } else { this.mcpLogger.info(message); } } debug(message: string, context?: LogContext): void { const formattedContext = this.formatContext(context); if (formattedContext) { this.mcpLogger.debug(message, formattedContext); } else { this.mcpLogger.debug(message); } } trace(message: string, context?: LogContext): void { const formattedContext = this.formatContext(context); if (formattedContext) { this.mcpLogger.trace(message, formattedContext); } else { this.mcpLogger.trace(message); } } // Child logger creation using native mcp-logger functionality child(bindings: { module?: string; [key: string]: unknown }): Logger { 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 Logger instance wrapping the child const childLogger = new Logger(this.config, this.runtimeOptions); childLogger.mcpLogger = childMcpLogger; return childLogger; } // Legacy API compatibility - access to underlying logger get pino(): McpLogger { return this.mcpLogger; } get mcp(): McpLogger { return this.mcpLogger; } // Update configuration and reinitialize updateConfig(config: Partial<LoggingConfig>): void { this.config = { ...this.config, ...config }; const options = 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 }); } } // Global logger management let globalLogger: Logger | null = null; let selectedLoggingConfig: Partial<LoggingConfig> | null = null; /** * Get or create the global logger instance */ export function getLogger( config?: Partial<LoggingConfig>, runtimeOptions?: RuntimeOptions ): Logger { if (config || runtimeOptions) { const finalConfig = config || createLoggingConfig(runtimeOptions); globalLogger = new Logger(finalConfig, runtimeOptions); selectedLoggingConfig = finalConfig; return globalLogger; } if (!globalLogger) { globalLogger = new Logger(); } return globalLogger; } /** * Get the currently active logging configuration */ export function getActiveLoggingConfig(): Partial<LoggingConfig> | null { return selectedLoggingConfig; } /** * Create a child logger with module context * This maintains backward compatibility with the existing API */ export function createChildLogger(bindings: { module: string }): Logger { // If we don't have a global logger yet, create one with environment-aware config if (!globalLogger) { const config = createLoggingConfig(); // For early initialization, assume stdio transport (most common case) // This will be overridden when the main logger is properly initialized const runtimeOptions: RuntimeOptions = { transport: "stdio", debug: false, insecure: false, }; globalLogger = new Logger(config, runtimeOptions); selectedLoggingConfig = config; } return globalLogger.child(bindings); } /** * Get diagnostic information about child loggers from the native mcp-logger cache * Useful for debugging EventEmitter memory leaks */ export function getLoggerDiagnostics(): { hasGlobalLogger: boolean; cacheStats?: any; } { const result: any = { hasGlobalLogger: globalLogger !== null, }; // Access native mcp-logger cache stats if available through public API if ( globalLogger?.mcp && typeof globalLogger.mcp.getChildLoggerCacheStats === "function" ) { result.cacheStats = globalLogger.mcp.getChildLoggerCacheStats(); } return result; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/toolprint/hypertool-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server