Notifications

MCP notifications provide real-time updates from servers about ongoing operations, resource changes, and system events. This guide covers how to handle different types of notifications in your application.

Table of contents

  1. Notifications
    1. Types of Notifications
      1. Logging Notifications
      2. Progress Notifications
      3. Resource Update Notifications
    2. Logging Notifications
      1. Basic Logging Setup
      2. Logging Levels
      3. Filtering Logging by Level
      4. Structured Logging
    3. Progress Notifications
      1. Basic Progress Tracking
      2. Progress with UI Updates
      3. Progress with Timeouts
    4. Resource Update Notifications
      1. Basic Resource Updates
      2. Automatic Resource Refresh
      3. Resource Change Notifications
    5. Next Steps

Types of Notifications

Logging Notifications

Logging notifications allow MCP servers to send real-time log messages during tool execution.

Progress Notifications

Progress notifications provide updates about the progress of long-running operations.

Resource Update Notifications

Resource update notifications inform you when resources on the server have changed.

Logging Notifications

Basic Logging Setup

client = RubyLLM::MCP.client(
  name: "filesystem",
  transport_type: :stdio,
  config: {
    command: "bunx",
    args: ["@modelcontextprotocol/server-filesystem", "/path/to/directory"]
  }
)

# Set up logging notification handler
client.on_logging do |logging|
  puts "#{logging.level.upcase}: #{logging.message}"
end

# Execute a tool that produces logging
tool = client.tool("long_running_operation")
result = tool.execute(operation: "data_processing")

# Output might look like:
# INFO: Starting data processing
# INFO: Processing file 1 of 100
# WARNING: File corrupted, skipping
# INFO: Processing complete

Logging Levels

Handle different logging levels:

# Handle all logging levels
client.on_logging do |logging|
  case logging.level
  when RubyLLM::MCP::Logging::DEBUG
    puts "🐛 DEBUG: #{logging.message}"
  when RubyLLM::MCP::Logging::INFO
    puts "â„šī¸  INFO: #{logging.message}"
  when RubyLLM::MCP::Logging::WARNING
    puts "âš ī¸  WARNING: #{logging.message}"
  when RubyLLM::MCP::Logging::ERROR
    puts "❌ ERROR: #{logging.message}"
  when RubyLLM::MCP::Logging::FATAL
    puts "💀 FATAL: #{logging.message}"
  end
end

Filtering Logging by Level

# Only handle warning and error messages
client.on_logging(RubyLLM::MCP::Logging::WARNING) do |logging|
  puts "âš ī¸  #{logging.level.upcase}: #{logging.message}"
end

# Only handle errors
client.on_logging(RubyLLM::MCP::Logging::ERROR) do |logging|
  puts "❌ ERROR: #{logging.message}"
  # Log to file, send alert, etc.
end

Structured Logging

# Create structured log entries
client.on_logging do |logging|
  log_entry = {
    timestamp: Time.now.iso8601,
    level: logging.level,
    message: logging.message,
    source: "mcp_server"
  }

  # Send to your logging system
  Rails.logger.info(log_entry.to_json)
end

Progress Notifications

Basic Progress Tracking

# Set up progress notification handler
client.on_progress do |progress|
  puts "Progress: #{progress.progress}% - #{progress.message}"
end

# Execute a tool that supports progress notifications
tool = client.tool("large_file_processor")
result = tool.execute(file_path: "/path/to/large/file.csv")

# Output might look like:
# Progress: 25% - Processing data...
# Progress: 50% - Validating records...
# Progress: 75% - Generating report...
# Progress: 100% - Complete

Progress with UI Updates

# Update a progress bar or UI element
client.on_progress do |progress|
  # Update progress bar
  update_progress_bar(progress.progress)

  # Update status message
  update_status_message(progress.message)

  # Log progress
  Rails.logger.info("Progress: #{progress.progress}% - #{progress.message}")
end

def update_progress_bar(percentage)
  # Update your UI progress bar
  bar_width = (percentage / 100.0 * 50).to_i
  bar = "#" * bar_width + "-" * (50 - bar_width)
  print "\r[#{bar}] #{percentage}%"
end

def update_status_message(message)
  # Update status display
  puts "\nStatus: #{message}"
end

Progress with Timeouts

# Track progress with timeout handling
class ProgressTracker
  def initialize(client)
    @client = client
    @last_progress = 0
    @last_update = Time.now
  end

  def setup_tracking(timeout: 300)
    @client.on_progress do |progress|
      @last_progress = progress.progress
      @last_update = Time.now

      puts "Progress: #{progress.progress}% - #{progress.message}"

      # Check if we're stuck
      if stalled?
        puts "âš ī¸  Progress appears stalled"
      end
    end
  end

  private

  def stalled?
    Time.now - @last_update > 60 # No progress for 60 seconds
  end
end

tracker = ProgressTracker.new(client)
tracker.setup_tracking(timeout: 300)

Resource Update Notifications

Basic Resource Updates

# Handle resource update notifications
client.on_resource_updated do |resource_uri|
  puts "Resource updated: #{resource_uri}"

  # Refresh the resource in your cache
  refresh_resource_cache(resource_uri)
end

# Execute operations that might update resources
tool = client.tool("modify_file")
result = tool.execute(path: "config.json", content: new_config)

# Output: Resource updated: file:///path/to/config.json

Automatic Resource Refresh

# Automatically refresh resources when they change
class ResourceManager
  def initialize(client)
    @client = client
    @cache = {}
    setup_update_handler
  end

  def resource(name)
    @cache[name] ||= @client.resource(name)
  end

  private

  def setup_update_handler
    @client.on_resource_updated do |resource_uri|
      # Find and refresh the cached resource
      @cache.each do |name, cached_resource|
        if cached_resource.uri == resource_uri
          puts "Refreshing cached resource: #{name}"
          @cache[name] = @client.resource(name, refresh: true)
          break
        end
      end
    end
  end
end

manager = ResourceManager.new(client)
resource = manager.resource("config_file")

Resource Change Notifications

# Handle different types of resource changes
client.on_resource_updated do |resource_uri|
  case resource_uri
  when /\.json$/
    puts "JSON configuration updated: #{resource_uri}"
    reload_configuration
  when /\.log$/
    puts "Log file updated: #{resource_uri}"
    # Don't need to refresh log files usually
  else
    puts "Unknown resource updated: #{resource_uri}"
    # Generic refresh
    refresh_resource_cache(resource_uri)
  end
end

Next Steps