Skip to main content
FastMCP provides flexible logging options, allowing you to customize how your server logs messages and integrate with existing logging infrastructure.

Custom Logger

Provide a custom logger implementation to control how the server logs messages:
import { FastMCP, Logger } from "fastmcp";

class CustomLogger implements Logger {
  debug(...args: unknown[]): void {
    console.log("[DEBUG]", new Date().toISOString(), ...args);
  }

  error(...args: unknown[]): void {
    console.error("[ERROR]", new Date().toISOString(), ...args);
  }

  info(...args: unknown[]): void {
    console.info("[INFO]", new Date().toISOString(), ...args);
  }

  log(...args: unknown[]): void {
    console.log("[LOG]", new Date().toISOString(), ...args);
  }

  warn(...args: unknown[]): void {
    console.warn("[WARN]", new Date().toISOString(), ...args);
  }
}

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  logger: new CustomLogger(),
});

Logger Interface

The Logger interface requires five methods:
interface Logger {
  debug(...args: unknown[]): void;
  error(...args: unknown[]): void;
  info(...args: unknown[]): void;
  log(...args: unknown[]): void;
  warn(...args: unknown[]): void;
}

Logging in Tools

Tools can log messages to the client using the log object in the context:
import { FastMCP } from "fastmcp";
import { z } from "zod";

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  name: "download",
  description: "Download a file",
  parameters: z.object({
    url: z.string(),
  }),
  execute: async (args, { log }) => {
    log.info("Downloading file...", {
      url: args.url,
    });

    // Download logic here...

    log.info("Downloaded file");

    return "done";
  },
});

Log Methods

The log object provides four methods:
MethodDescriptionUse Case
debug(message, data?)Debug-level loggingDetailed diagnostic information
error(message, data?)Error-level loggingError conditions
info(message, data?)Info-level loggingGeneral informational messages
warn(message, data?)Warning-level loggingWarning conditions
All methods accept:
  • message: string - The log message
  • data?: SerializableValue - Optional structured data

Integration Examples

import winston from 'winston';
import { FastMCP, Logger } from 'fastmcp';

class WinstonLoggerAdapter implements Logger {
  private winston: winston.Logger;

  constructor() {
    this.winston = winston.createLogger({
      level: 'debug',
      format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.errors({ stack: true }),
        winston.format.json()
      ),
      transports: [
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.colorize(),
            winston.format.simple()
          )
        }),
        new winston.transports.File({ filename: 'fastmcp.log' })
      ]
    });
  }

  debug(...args: unknown[]): void {
    this.winston.debug(this.formatArgs(args));
  }

  error(...args: unknown[]): void {
    this.winston.error(this.formatArgs(args));
  }

  info(...args: unknown[]): void {
    this.winston.info(this.formatArgs(args));
  }

  log(...args: unknown[]): void {
    this.winston.info(this.formatArgs(args));
  }

  warn(...args: unknown[]): void {
    this.winston.warn(this.formatArgs(args));
  }

  private formatArgs(args: unknown[]): string {
    return args.map(arg =>
      typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
    ).join(' ');
  }
}

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  logger: new WinstonLoggerAdapter(),
});

Real-World Example

Here’s a complete example from the FastMCP repository showing custom logging:
src/examples/custom-logger.ts
import { z } from "zod";
import { FastMCP, Logger } from "../FastMCP.js";

// Simple Custom Logger Implementation
class SimpleCustomLogger implements Logger {
  debug(...args: unknown[]): void {
    console.log("[CUSTOM DEBUG]", new Date().toISOString(), ...args);
  }

  error(...args: unknown[]): void {
    console.error("[CUSTOM ERROR]", new Date().toISOString(), ...args);
  }

  info(...args: unknown[]): void {
    console.info("[CUSTOM INFO]", new Date().toISOString(), ...args);
  }

  log(...args: unknown[]): void {
    console.log("[CUSTOM LOG]", new Date().toISOString(), ...args);
  }

  warn(...args: unknown[]): void {
    console.warn("[CUSTOM WARN]", new Date().toISOString(), ...args);
  }
}

const logger = new SimpleCustomLogger();

const server = new FastMCP({
  logger: logger,
  name: "custom-logger-example",
  version: "1.0.0",
});

server.addTool({
  description: "A test tool that demonstrates custom logging",
  execute: async (args) => {
    return `Received: ${args.message}`;
  },
  name: "test_tool",
  parameters: z.object({
    message: z.string().describe("A message to log"),
  }),
});

server.start({ transportType: "stdio" });

Structured Logging

Log structured data alongside messages:
server.addTool({
  name: "processOrder",
  description: "Process an order",
  parameters: z.object({
    orderId: z.string(),
    items: z.array(z.string()),
  }),
  execute: async ({ orderId, items }, { log }) => {
    log.info("Processing order", {
      orderId,
      itemCount: items.length,
      timestamp: new Date().toISOString(),
    });

    try {
      // Process order...
      log.info("Order processed successfully", { orderId });
    } catch (error) {
      log.error("Order processing failed", {
        orderId,
        error: error.message,
      });
      throw error;
    }

    return "Order processed";
  },
});

Best Practices

1

Use Appropriate Log Levels

  • debug: Detailed diagnostic information
  • info: General informational messages
  • warn: Warning conditions
  • error: Error conditions
2

Include Context in Logs

Always log relevant context data:
log.info("Processing file", {
  filename: file.name,
  size: file.size,
  userId: session.userId,
});
3

Log at Key Points

Log at important stages:
  • Start of operations
  • Completion of operations
  • Error conditions
  • State changes
4

Avoid Logging Sensitive Data

Never log:
  • Passwords
  • API keys
  • Personal information (unless necessary and compliant)
  • Credit card numbers
5

Use Structured Logging

Prefer structured data over string concatenation:
// Good
log.info("User login", { userId, timestamp });

// Avoid
log.info(`User ${userId} logged in at ${timestamp}`);

Production Considerations

Log Rotation

For file-based logging, implement log rotation:
import * as fs from 'fs';
import * as path from 'path';

class RotatingFileLogger implements Logger {
  private logFile: string;
  private maxSize: number = 10 * 1024 * 1024; // 10MB
  private maxFiles: number = 5;

  constructor(logFile: string) {
    this.logFile = logFile;
  }

  private rotateIfNeeded(): void {
    if (!fs.existsSync(this.logFile)) return;

    const stats = fs.statSync(this.logFile);
    if (stats.size < this.maxSize) return;

    // Rotate logs
    for (let i = this.maxFiles - 1; i >= 0; i--) {
      const oldFile = `${this.logFile}.${i}`;
      const newFile = `${this.logFile}.${i + 1}`;
      if (fs.existsSync(oldFile)) {
        fs.renameSync(oldFile, newFile);
      }
    }

    fs.renameSync(this.logFile, `${this.logFile}.0`);
  }

  private logToFile(level: string, ...args: unknown[]): void {
    this.rotateIfNeeded();

    const timestamp = new Date().toISOString();
    const message = `[${timestamp}] [${level}] ${args.map(arg =>
      typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
    ).join(' ')}\n`;

    fs.appendFileSync(this.logFile, message);
  }

  debug(...args: unknown[]): void {
    this.logToFile('DEBUG', ...args);
  }

  error(...args: unknown[]): void {
    this.logToFile('ERROR', ...args);
  }

  info(...args: unknown[]): void {
    this.logToFile('INFO', ...args);
  }

  log(...args: unknown[]): void {
    this.logToFile('LOG', ...args);
  }

  warn(...args: unknown[]): void {
    this.logToFile('WARN', ...args);
  }
}

Performance Monitoring

Log performance metrics:
server.addTool({
  name: "expensiveOperation",
  execute: async (args, { log }) => {
    const startTime = Date.now();

    log.info("Starting expensive operation", { args });

    try {
      // Do work...
      const result = await doWork();

      const duration = Date.now() - startTime;
      log.info("Operation completed", {
        duration,
        success: true,
      });

      return result;
    } catch (error) {
      const duration = Date.now() - startTime;
      log.error("Operation failed", {
        duration,
        error: error.message,
      });
      throw error;
    }
  },
});

Next Steps

Streaming

Learn how to stream content and report progress

Authentication

Secure your server with authentication

Build docs developers (and LLMs) love