Skip to main content
Glama

1MCP Server

logger.ts8.79 kB
import chalk from 'chalk'; import winston from 'winston'; // Map MCP log levels to Winston log levels const MCP_TO_WINSTON_LEVEL: Record<string, string> = { debug: 'debug', info: 'info', notice: 'info', warn: 'warn', // Support both 'warn' and 'warning' for user convenience warning: 'warn', error: 'error', critical: 'error', alert: 'error', emergency: 'error', }; // Color mapping for log levels const LEVEL_COLORS: Record<string, (text: string) => string> = { debug: chalk.gray, info: chalk.blue, warn: chalk.yellow, error: chalk.red, }; // Custom format for console and file output const customFormat = winston.format.combine( winston.format.timestamp(), winston.format.printf(({ timestamp, level, message, ...meta }) => { const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; return `${timestamp} [${level.toUpperCase()}] ${message}${metaStr}`; }), ); const consoleFormat = winston.format.combine( winston.format.timestamp(), winston.format.printf(({ timestamp, level, message, ...meta }) => { const keys = Object.keys(meta); const metaStr = keys.length > 0 ? ` ${keys.map((key) => `${key}=${JSON.stringify(meta[key])}`).join(' ')}` : ''; // Colorize timestamp and level const colorizedTimestamp = chalk.gray(timestamp); const colorFn = LEVEL_COLORS[level] || ((text: string) => text); const colorizedLevel = colorFn(`[${level.toUpperCase()}]`); return `${colorizedTimestamp} ${colorizedLevel} message=${JSON.stringify(message)}${metaStr}`; }), ); // Create the logger without the MCP transport initially const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: customFormat, transports: [ // Add a silent transport by default to prevent "no transports" warnings new winston.transports.Console({ silent: true, format: consoleFormat, }), ], // Prevent logger from exiting on error exitOnError: false, }); /** * Enable the console transport */ export function enableConsoleTransport(): void { if (logger.transports.length > 0) { logger.transports[0].silent = false; } } /** * Set the log level for the logger * @param mcpLevel The MCP log level to set */ export function setLogLevel(mcpLevel: string): void { // Convert MCP log level to Winston log level const winstonLevel = MCP_TO_WINSTON_LEVEL[mcpLevel] || 'info'; // Set the log level for all transports logger.level = winstonLevel; logger.transports.forEach((transport) => { transport.level = winstonLevel; }); } /** * Configure logger with CLI options and transport awareness * @param options Configuration options for the logger */ export function configureLogger(options: { logLevel?: string; logFile?: string; transport?: string }): void { // Determine log level priority: CLI > ONE_MCP_LOG_LEVEL > LOG_LEVEL (deprecated) let logLevel = options.logLevel; if (!logLevel) { logLevel = process.env.ONE_MCP_LOG_LEVEL; } if (!logLevel) { logLevel = process.env.LOG_LEVEL; if (logLevel) { logger.warn( 'LOG_LEVEL environment variable is deprecated. Please use ONE_MCP_LOG_LEVEL or --log-level CLI option instead.', ); } } logLevel = logLevel || 'info'; // Convert MCP log level to Winston log level const winstonLevel = MCP_TO_WINSTON_LEVEL[logLevel] || 'info'; // Clear existing transports logger.clear(); // Set logger level logger.level = winstonLevel; // Configure transports based on options if (options.logFile) { // Add file transport logger.add( new winston.transports.File({ filename: options.logFile, format: customFormat, level: winstonLevel, }), ); // Add console transport except for stdio transport (backward compatibility for serve) if (options.transport !== 'stdio') { logger.add( new winston.transports.Console({ format: consoleFormat, level: winstonLevel, }), ); } } else { // Add console transport (default behavior) // For stdio transport in serve command, suppress console output to avoid interfering with MCP protocol const shouldSilence = options.transport === 'stdio'; logger.add( new winston.transports.Console({ format: consoleFormat, level: winstonLevel, silent: shouldSilence, }), ); } } /** * Check if debug logging is enabled * Use this to avoid expensive operations when debug logging is disabled */ export function isDebugEnabled(): boolean { return logger.isDebugEnabled(); } /** * Check if info logging is enabled * Use this to avoid expensive operations when info logging is disabled */ export function isInfoEnabled(): boolean { return logger.isInfoEnabled(); } /** * Check if warn logging is enabled * Use this to avoid expensive operations when warn logging is disabled */ export function isWarnEnabled(): boolean { return logger.isWarnEnabled(); } /** * Conditional debug logging - only executes the message function if debug is enabled * @param messageOrFunc Message string or function that returns message and metadata */ export function debugIf(messageOrFunc: string | (() => { message: string; meta?: any })): void { if (isDebugEnabled()) { if (typeof messageOrFunc === 'string') { logger.debug(messageOrFunc); } else { try { const result = messageOrFunc(); if (result && typeof result === 'object' && 'message' in result) { const { message, meta } = result; if (meta) { logger.debug(message, meta); } else { logger.debug(message); } } else { // Fallback for malformed callback results logger.debug('[debugIf: Invalid callback result]', { callbackResult: result }); } } catch (error) { // Never let logging errors crash the application // Use logger.warn to avoid infinite recursion if debugIf were to call itself if (logger.isWarnEnabled()) { logger.warn('debugIf callback failed', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); } } } } } /** * Conditional info logging - only executes the message function if info is enabled * @param messageOrFunc Message string or function that returns message and metadata */ export function infoIf(messageOrFunc: string | (() => { message: string; meta?: any })): void { if (isInfoEnabled()) { if (typeof messageOrFunc === 'string') { logger.info(messageOrFunc); } else { try { const result = messageOrFunc(); if (result && typeof result === 'object' && 'message' in result) { const { message, meta } = result; if (meta) { logger.info(message, meta); } else { logger.info(message); } } else { // Fallback for malformed callback results logger.info('[infoIf: Invalid callback result]', { callbackResult: result }); } } catch (error) { // Never let logging errors crash the application // Use logger.warn to avoid infinite recursion if infoIf were to call itself if (logger.isWarnEnabled()) { logger.warn('infoIf callback failed', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); } } } } } /** * Conditional warn logging - only executes the message function if warn is enabled * @param messageOrFunc Message string or function that returns message and metadata */ export function warnIf(messageOrFunc: string | (() => { message: string; meta?: any })): void { if (isWarnEnabled()) { if (typeof messageOrFunc === 'string') { logger.warn(messageOrFunc); } else { try { const result = messageOrFunc(); if (result && typeof result === 'object' && 'message' in result) { const { message, meta } = result; if (meta) { logger.warn(message, meta); } else { logger.warn(message); } } else { // Fallback for malformed callback results logger.warn('[warnIf: Invalid callback result]', { callbackResult: result }); } } catch (error) { // Never let logging errors crash the application // For warnIf, we use console.error as last resort to avoid recursion console.error('warnIf callback failed:', error instanceof Error ? error.message : String(error)); } } } } export default logger;

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/1mcp-app/agent'

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