WebSockets
MCP Hangar provides WebSocket endpoints for real-time streaming of domain events, provider state changes, and provider logs.
Endpoints
All WebSocket endpoints are mounted under /api/ws/:
| Endpoint | Description |
|---|---|
/api/ws/events | All domain events |
/api/ws/state | Provider state changes only |
/api/ws/providers/{id}/logs | Live stderr log stream for a provider |
Connecting
Use any WebSocket client. Examples with websocat:
# Stream all domain events
websocat ws://localhost:8000/api/ws/events
# Stream state changes only
websocat ws://localhost:8000/api/ws/state
# Stream logs for a specific provider
websocat ws://localhost:8000/api/ws/providers/math/logsEvent Stream (/api/ws/events)
Streams all domain events as they occur. Each message is a JSON object:
{
"event_type": "ProviderStarted",
"provider_id": "math",
"mode": "subprocess",
"tools_count": 5,
"startup_duration_ms": 120,
"timestamp": "2026-03-23T10:15:30.123456"
}Event Types
All events from domain/events.py are published:
ProviderStateChanged-- State transition with old/new state.ProviderStarted-- Provider initialization complete.ProviderStopped-- Provider shut down.ProviderDegraded-- Provider marked as degraded.HealthCheckPassed/HealthCheckFailed-- Health check results.ToolInvocationCompleted/ToolInvocationFailed-- Tool call results.ProviderDiscovered/ProviderRegistered/ProviderDeregistered-- Discovery events.CircuitBreakerStateChanged-- Circuit breaker transitions.
Filtering
The events endpoint supports optional query parameters for filtering:
ws://localhost:8000/api/ws/events?provider_id=math
ws://localhost:8000/api/ws/events?event_type=ProviderStateChangedState Stream (/api/ws/state)
A filtered view of the event stream that only includes ProviderStateChanged events:
{
"event_type": "ProviderStateChanged",
"provider_id": "math",
"old_state": "COLD",
"new_state": "INITIALIZING",
"timestamp": "2026-03-23T10:15:30.123456"
}This endpoint is used by the Dashboard to update provider state badges in real-time without polling.
Log Stream (/api/ws/providers/{id}/logs)
Streams stderr log lines for a specific provider. See the Log Streaming guide for details.
{
"timestamp": "2026-03-23T10:15:30.123456",
"line": "INFO: Processing request...",
"provider_id": "math",
"stream": "stderr"
}Connection Management
WebSocket Manager
The WebSocketManager in server/api/ws/manager.py tracks all active connections. It provides:
- Connection registry -- Tracks active WebSocket connections per endpoint.
- Broadcast -- Publishes events to all connected clients for an endpoint.
- Cleanup -- Removes disconnected clients automatically.
Event Bus Integration
The WebSocket layer subscribes to the EventBus for domain events. On server shutdown, subscriptions are cleaned up via EventBus.unsubscribe_from_all().
Queue and Backpressure
Each WebSocket connection has an internal message queue (WebSocketQueue). If a client falls behind:
- Messages queue up to the buffer limit.
- Beyond the limit, oldest messages are dropped.
- The client receives a warning frame indicating dropped messages.
Dashboard Integration
The Dashboard UI connects to WebSocket endpoints using custom hooks:
useWebSocket
Base hook with exponential backoff reconnection:
const { isConnected, lastMessage } = useWebSocket('/api/ws/events');Features:
- Automatic reconnection with exponential backoff (1s, 2s, 4s, ... up to 30s).
- Connection state tracking.
- Message deserialization from JSON.
useEventStream
High-level hook for the events endpoint:
const { events } = useEventStream({ providerFilter: 'math' });useProviderState
Connects to the state endpoint and maintains a local map of provider states:
const { providerStates } = useProviderState();
// providerStates: Record<string, ProviderState>useProviderLogs
Combines REST fetch with WebSocket for live logs:
const { logs, isConnected } = useProviderLogs('math');State Management
WebSocket connection state is managed by a Zustand store (src/store/websocket.ts):
interface WebSocketStore {
connections: Record<string, WebSocketConnection>;
connect: (url: string) => void;
disconnect: (url: string) => void;
}This enables the Dashboard to show a global connection status indicator and reconnect all WebSocket connections after a network interruption.