Home

SSE Protocol Best Practices

This guide outlines recommended practices for working with Server-Sent Events (SSE) in MCP-Cloud applications, covering both server-side implementation and client-side consumption.

Server-Side Best Practices

Connection Management

  1. Limit Concurrent Connections

    • Set appropriate limits for concurrent SSE connections per user
    • Implement graceful degradation when limits are reached
    • Consider using shared connections for users with multiple tabs open
  2. Handle Connection Lifetimes

    • Implement session timeout after appropriate inactivity period
    • Send periodic heartbeat events to keep connections alive
    • Gracefully close connections when servers are being updated or restarted
  3. Scale Appropriately

    • Use Redis or similar service for cross-instance event propagation
    • Implement connection distribution across multiple servers
    • Monitor connection counts and event throughput

Event Design

  1. Event Payload Design

    • Keep event payloads compact and focused
    • Structure data hierarchically for readability
    • Include only necessary information in each event
    • Avoid circular references in JSON objects
  2. Event Naming Conventions

    • Use consistent naming patterns: category:action
    • Use past tense for completed actions: deployment:completed
    • Use present tense for in-progress states: server:starting
    • Document all event types comprehensively
  3. Event Frequency Management

    • Throttle high-frequency events (e.g., logging, metrics)
    • Batch related events when appropriate
    • Provide timestamps for event sequences
    • Consider debouncing rapidly changing values

Error Handling

  1. Error Recovery

    • Implement robust error handling for connection failures
    • Log connection errors with client information
    • Provide clear error events with actionable information
    • Implement retry backoff strategies
  2. Security Considerations

    • Validate authentication for all SSE connections
    • Implement proper authorization for event access
    • Sanitize sensitive information from event data
    • Set appropriate CORS headers for browser clients

Client-Side Best Practices

Connection Management

  1. Establish Connections

    function connectToEventSource(serverId, token) {
      const url = `/api/servers/${serverId}/events`;
      const eventSource = new EventSource(`${url}?auth=${token}`);
      return eventSource;
    }
    
  2. Handle Reconnection

    function setupEventSourceWithReconnection(serverId, token) {
      let reconnectAttempts = 0;
      const maxReconnectAttempts = 5;
      let reconnectTimeout = 1000; // Start with 1 second
      
      function connect() {
        const eventSource = connectToEventSource(serverId, token);
        
        eventSource.onopen = () => {
          reconnectAttempts = 0;
          reconnectTimeout = 1000;
          console.log('Connected to event stream');
        };
        
        eventSource.onerror = (error) => {
          eventSource.close();
          
          if (reconnectAttempts < maxReconnectAttempts) {
            reconnectAttempts++;
            console.log(`Connection error, reconnecting (${reconnectAttempts}/${maxReconnectAttempts})...`);
            setTimeout(connect, reconnectTimeout);
            reconnectTimeout = Math.min(reconnectTimeout * 2, 30000); // Exponential backoff, max 30 seconds
          } else {
            console.error('Maximum reconnection attempts reached');
            // Notify user or trigger manual reconnect
          }
        };
        
        return eventSource;
      }
      
      return connect();
    }
    
  3. Clean Disconnection

    function cleanupEventSource(eventSource) {
      if (eventSource && eventSource.readyState !== 2) {
        eventSource.close();
      }
    }
    

Event Handling

  1. Register Event Listeners

    function registerEventHandlers(eventSource) {
      // Listen for specific events
      eventSource.addEventListener('deployment:started', handleDeploymentStarted);
      eventSource.addEventListener('server:online', handleServerOnline);
      eventSource.addEventListener('metrics:cpu', handleCpuMetrics);
      
      // Generic message handler for uncategorized events
      eventSource.onmessage = (event) => {
        const data = JSON.parse(event.data);
        console.log('Received uncategorized event:', data);
      };
    }
    
  2. Process Events Efficiently

    // Efficiently handle high-frequency events
    function handleMetricsEvents() {
      let pendingMetrics = [];
      let updateTimeout = null;
      
      function processMetricsBatch() {
        if (pendingMetrics.length > 0) {
          // Update UI with batched metrics
          updateMetricsUI(pendingMetrics);
          pendingMetrics = [];
        }
        updateTimeout = null;
      }
      
      return function(event) {
        const metric = JSON.parse(event.data);
        pendingMetrics.push(metric);
        
        // Debounce UI updates to max 4 per second
        if (!updateTimeout) {
          updateTimeout = setTimeout(processMetricsBatch, 250);
        }
      };
    }
    
    const handleCpuMetrics = handleMetricsEvents();
    
  3. Maintain Event Order

    function createOrderedEventProcessor() {
      const processedEvents = new Set();
      
      return function(event) {
        const data = JSON.parse(event.data);
        
        // Skip if we've seen this event before (deduplication)
        if (data.id && processedEvents.has(data.id)) {
          return;
        }
        
        // Process the event
        handleEventData(data);
        
        // Remember we processed this event
        if (data.id) {
          processedEvents.add(data.id);
          // Limit the size of our tracking set
          if (processedEvents.size > 1000) {
            const iterator = processedEvents.values();
            processedEvents.delete(iterator.next().value);
          }
        }
      };
    }
    

Performance Considerations

  1. Memory Management

    • Clean up event listeners when components unmount
    • Implement efficient data structures for event storage
    • Consider using WeakMap for event-to-object mappings
    • Regularly prune old events from memory
  2. UI Updates

    • Batch related UI updates to reduce DOM operations
    • Use requestAnimationFrame for visual updates
    • Consider using web workers for event processing
    • Implement throttling for high-frequency UI updates
  3. Testing

    • Mock SSE connections in tests
    • Test reconnection scenarios
    • Verify event handling with large volumes of events
    • Test with delayed and out-of-order events

Example Implementation

Here's a complete example of a React component that handles SSE events for a server monitoring dashboard:

import { useState, useEffect, useRef } from 'react';

function ServerMonitor({ serverId, authToken }) {
  const [serverStatus, setServerStatus] = useState('unknown');
  const [metrics, setMetrics] = useState({ cpu: 0, memory: 0, network: 0 });
  const [logs, setLogs] = useState([]);
  const eventSourceRef = useRef(null);
  
  useEffect(() => {
    // Set up event source
    const url = `/api/servers/${serverId}/events?types=server:*,metrics:*,logs:*`;
    const eventSource = new EventSource(`${url}?auth=${authToken}`);
    eventSourceRef.current = eventSource;
    
    // Server status events
    eventSource.addEventListener('server:online', (event) => {
      const data = JSON.parse(event.data);
      setServerStatus('online');
    });
    
    eventSource.addEventListener('server:offline', (event) => {
      const data = JSON.parse(event.data);
      setServerStatus('offline');
    });
    
    // Metrics events (throttled to max 1 update per second)
    let metricsUpdateTimeout = null;
    const handleMetricsEvent = (event) => {
      const data = JSON.parse(event.data);
      
      setMetrics(prevMetrics => {
        // Update just the specific metric that changed
        return { ...prevMetrics, [data.type]: data.value };
      });
    };
    
    eventSource.addEventListener('metrics:cpu', handleMetricsEvent);
    eventSource.addEventListener('metrics:memory', handleMetricsEvent);
    eventSource.addEventListener('metrics:network', handleMetricsEvent);
    
    // Log events
    eventSource.addEventListener('logs:app', (event) => {
      const data = JSON.parse(event.data);
      setLogs(prevLogs => [...prevLogs.slice(-99), data].sort((a, b) => 
        new Date(b.timestamp) - new Date(a.timestamp)
      ));
    });
    
    // Generic error handler
    eventSource.onerror = (error) => {
      console.error('EventSource error:', error);
      // Implement reconnection logic here
    };
    
    // Clean up on unmount
    return () => {
      if (eventSourceRef.current) {
        eventSourceRef.current.close();
        eventSourceRef.current = null;
      }
    };
  }, [serverId, authToken]);
  
  return (
    <div className="server-monitor">
      <h2>Server Status: {serverStatus}</h2>
      
      <div className="metrics">
        <div className="metric">
          <h3>CPU Usage</h3>
          <div className="meter">
            <div className="meter-fill" style={{ width: `${metrics.cpu}%` }}></div>
          </div>
          <span>{metrics.cpu}%</span>
        </div>
        {/* Similar components for memory and network */}
      </div>
      
      <div className="logs">
        <h3>Recent Logs</h3>
        <ul>
          {logs.map((log, i) => (
            <li key={i} className={`log-level-${log.level}`}>
              <span className="timestamp">{new Date(log.timestamp).toLocaleTimeString()}</span>
              <span className="message">{log.message}</span>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

Browser Compatibility

For older browsers without native EventSource support, use a polyfill:

import EventSourcePolyfill from 'eventsource-polyfill';

// Use the polyfill if needed
const EventSourceImpl = window.EventSource || EventSourcePolyfill;

const eventSource = new EventSourceImpl(url, options);

This ensures compatibility across all browsers, including older versions of IE and Edge.