Using Server-Sent Events (SSE)
Server-Sent Events (SSE) provide real-time, unidirectional communication from the backend to the frontend. This is useful for live updates without polling.
When to Use SSE
Use SSE when you need:
- Real-time updates from the server (activity streams, notifications, live stats)
- Automatic reconnection on connection loss
- Lower network overhead compared to polling
SSE is unidirectional (server � client). For bidirectional communication, use WebSockets instead.
SSE Endpoint URL Pattern
All SSE endpoints use the /stream suffix:
// REST API endpoint (polling)
const response = await fetch('/api/users/me/mcp/client-activity?team_id=123&limit=20')
const data = await response.json()
// SSE streaming endpoint (real-time)
const url = '/api/users/me/mcp/client-activity/stream?team_id=123&limit=20'
connect(url)
URL Structure
- REST:
/api/{resource}/{action} - Returns snapshot of current data
- SSE:
/api/{resource}/{action}/stream - Pushes real-time updates
Both endpoints:
- Accept the same query parameters
- Return the same data structure
- Apply the same filters and limits
The REST endpoint serves as a fallback for clients that don’t support SSE.
The useSSE Composable
The useSSE<T> composable handles all SSE logic including connection management, auto-reconnect, and cleanup.
Import
import { useSSE } from '@/composables/useSSE'
import type { McpClientActivity } from '@/services/mcpClientActivityService'
Basic Usage
Initialize the composable
Call useSSE<T>() with your expected data type and the SSE event name:const {
data: activities,
isLoading,
error,
connect,
disconnect
} = useSSE<McpClientActivity[]>('client_activity')
Connect to the SSE endpoint
Call connect() with the full SSE stream URL:onMounted(() => {
const url = McpClientActivityService.getStreamUrl(teamId, {
limit: 20,
active_within_minutes: 30
})
connect(url)
})
Use the reactive data
The data ref will automatically update when the server sends new events:<template>
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else-if="activities">
<Item v-for="activity in activities" :key="activity.id">
{{ activity.client_name }}
</Item>
</div>
</template>
Complete Example
Here’s the complete implementation from McpClientConnectionsCard.vue:
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { useEventBus } from '@/composables/useEventBus'
import { useSSE } from '@/composables/useSSE'
import { McpClientActivityService } from '@/services/mcpClientActivityService'
import type { McpClientActivity } from '@/services/mcpClientActivityService'
const eventBus = useEventBus()
const {
data: activities,
isLoading,
error,
connect,
disconnect
} = useSSE<McpClientActivity[]>('client_activity')
function connectToTeam(teamId: string) {
const url = McpClientActivityService.getStreamUrl(teamId, {
limit: 20,
active_within_minutes: 30
})
connect(url)
}
function handleTeamChange() {
const teamId = eventBus.getState<string>('selected_team_id')
if (teamId) {
connectToTeam(teamId)
} else {
disconnect()
}
}
onMounted(() => {
const teamId = eventBus.getState<string>('selected_team_id')
if (teamId) {
connectToTeam(teamId)
}
eventBus.on('team-selected', handleTeamChange)
})
onUnmounted(() => {
eventBus.off('team-selected', handleTeamChange)
})
</script>
<template>
<div v-if="isLoading && !activities?.length">
Loading...
</div>
<div v-else-if="error">
<p>{{ error }}</p>
<button @click="handleTeamChange">Retry</button>
</div>
<div v-else-if="!activities?.length">
No active connections
</div>
<div v-else>
<Item v-for="activity in activities" :key="activity.id">
{{ activity.client_name }}
</Item>
</div>
</template>
API Reference
useSSE<T>()
function useSSE<T>(
eventName: string,
options?: UseSSEOptions
): UseSSEReturn<T>
Parameters:
eventName: The SSE event name to listen for (must match backend event name)
options: Optional configuration
Options:
interface UseSSEOptions {
reconnectDelay?: number // Default: 5000ms
withCredentials?: boolean // Default: true
}
Returns:
interface UseSSEReturn<T> {
data: Ref<T | null> // Reactive data from SSE stream
isConnected: Ref<boolean> // Connection status
isLoading: Ref<boolean> // Initial loading state
error: Ref<string | null> // Error message if any
connect: (url: string) => void // Connect to SSE endpoint
disconnect: () => void // Close connection
}
Auto-Reconnection
The composable automatically handles reconnection when the connection is lost:
- Reconnects after 5 seconds by default (configurable via
reconnectDelay)
- Maintains the last URL for reconnection
- Clears any pending reconnection attempts on manual
disconnect()
The composable automatically disconnects when the component unmounts via onUnmounted().
Your backend SSE endpoint must send events in this format:
// Backend sends:
reply.sse.send({
event: 'client_activity', // Must match eventName in useSSE()
data: JSON.stringify({
activities: [...] // Your actual data
})
})
The composable will:
- Parse the JSON data
- Extract the first key’s value (e.g.,
activities array)
- Update the
data ref
Make sure the event name in your backend matches the eventName parameter in useSSE(). Mismatched event names will cause the composable to never receive data.