Skip to main content
Glama
logger.ts5.39 kB
/** * CRITICAL LOGGING ABSTRACTION FOR MCP PROTOCOL INTEGRITY * * PURPOSE: Prevent console output from corrupting MCP JSON-RPC stdio communication * CONTEXT: MCP server uses stdio for JSON-RPC protocol - any console output breaks communication * SOLUTION: Transport-based logging that routes output safely away from stdio */ import * as fs from 'fs'; import * as path from 'path'; export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; export type LogTransport = 'stdio' | 'file' | 'null'; export interface LogMessage { timestamp: string; level: LogLevel; message: string; context?: string; } /** * Logger class with transport-based output routing * * CRITICAL DESIGN: * - 'stdio' transport: NEVER outputs to console (for MCP server) * - 'file' transport: Outputs to log file (for web server) * - 'null' transport: Discards all output (for testing) */ export class Logger { private static instance: Logger; private readonly transport: LogTransport; private readonly logFilePath?: string; private readonly context?: string; constructor(transport: LogTransport, context?: string, logFilePath?: string) { this.transport = transport; this.context = context; if (transport === 'file') { this.logFilePath = logFilePath || path.join(process.cwd(), 'logs', 'app.log'); this.ensureLogDirectory(); } } /** * Create singleton logger instance for global use * CRITICAL: Must be initialized before any logging occurs */ static initialize(transport: LogTransport, context?: string, logFilePath?: string): Logger { Logger.instance = new Logger(transport, context, logFilePath); return Logger.instance; } /** * Get singleton logger instance * SAFETY: Returns null logger if not initialized to prevent crashes */ static getInstance(): Logger { if (!Logger.instance) { // SAFETY: Create null logger to prevent crashes during initialization Logger.instance = new Logger('null'); } return Logger.instance; } /** * Log debug message */ debug(message: string, context?: string): void { this.log('debug', message, context); } /** * Log info message */ info(message: string, context?: string): void { this.log('info', message, context); } /** * Log warning message */ warn(message: string, context?: string): void { this.log('warn', message, context); } /** * Log error message */ error(message: string, error?: Error, context?: string): void { const errorMessage = error ? `${message}: ${error.message}` : message; this.log('error', errorMessage, context); } /** * Core logging method with transport routing * * CRITICAL BEHAVIOR: * - 'stdio' transport: NO OUTPUT (preserves MCP JSON-RPC protocol) * - 'file' transport: Write to log file * - 'null' transport: Discard all output */ private log(level: LogLevel, message: string, context?: string): void { const logMessage: LogMessage = { timestamp: new Date().toISOString(), level, message, context: context || this.context }; switch (this.transport) { case 'stdio': // CRITICAL: NO console output for MCP protocol integrity // All output is intentionally discarded to preserve stdio for JSON-RPC break; case 'file': this.writeToFile(logMessage); break; case 'null': // Intentionally discard all output break; default: // SAFETY: Unknown transport defaults to null behavior break; } } /** * Write log message to file */ private writeToFile(logMessage: LogMessage): void { if (!this.logFilePath) return; const formattedMessage = this.formatLogMessage(logMessage); try { fs.appendFileSync(this.logFilePath, formattedMessage + '\n', 'utf-8'); } catch (error) { // CRITICAL: Cannot use console here as it might break MCP protocol // Log file errors are silently discarded to maintain system stability } } /** * Format log message for output */ private formatLogMessage(logMessage: LogMessage): string { const contextPart = logMessage.context ? `[${logMessage.context}] ` : ''; return `${logMessage.timestamp} ${logMessage.level.toUpperCase()}: ${contextPart}${logMessage.message}`; } /** * Ensure log directory exists */ private ensureLogDirectory(): void { if (!this.logFilePath) return; const logDir = path.dirname(this.logFilePath); try { if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } } catch (error) { // CRITICAL: Cannot use console here - silently handle directory creation failures } } } /** * Convenience functions for global logger access * * USAGE: * - Initialize logger once: Logger.initialize('stdio', 'MCP-Server') * - Use globally: log.info('Message'), log.error('Error', error) */ export const log = { debug: (message: string, context?: string) => Logger.getInstance().debug(message, context), info: (message: string, context?: string) => Logger.getInstance().info(message, context), warn: (message: string, context?: string) => Logger.getInstance().warn(message, context), error: (message: string, error?: Error, context?: string) => Logger.getInstance().error(message, error, context), };

Latest Blog Posts

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/LightspeedDMS/ssh-mcp'

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