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
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
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
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
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
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
- Use consistent naming patterns:
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
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
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
Establish Connections
function connectToEventSource(serverId, token) { const url = `/api/servers/${serverId}/events`; const eventSource = new EventSource(`${url}?auth=${token}`); return eventSource; }
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(); }
Clean Disconnection
function cleanupEventSource(eventSource) { if (eventSource && eventSource.readyState !== 2) { eventSource.close(); } }
Event Handling
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); }; }
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();
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
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
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
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.