diff --git a/development/backend/api/index.mdx b/development/backend/api/index.mdx
index 932dceb..61a20ce 100644
--- a/development/backend/api/index.mdx
+++ b/development/backend/api/index.mdx
@@ -577,6 +577,19 @@ interface Response {
**Note**: TypeScript interfaces and JavaScript variables can use camelCase internally, but all external API fields MUST use snake_case.
+## Server-Sent Events (SSE)
+
+For real-time updates, use the `/stream` suffix pattern:
+
+- **REST endpoint:** `/api/{resource}/{action}`
+- **SSE endpoint:** `/api/{resource}/{action}/stream`
+
+Example:
+- `GET /api/users/me/mcp/client-activity` - REST API with pagination
+- `GET /api/users/me/mcp/client-activity/stream` - SSE real-time stream
+
+See [Server-Sent Events (SSE)](/development/backend/api/sse) for complete SSE documentation.
+
## Adding Documentation to Routes
The DeployStack Backend uses **reusable JSON Schema constants** for both validation and documentation generation. This approach provides a single source of truth for API schemas.
diff --git a/development/backend/api/sse.mdx b/development/backend/api/sse.mdx
new file mode 100644
index 0000000..599e6b8
--- /dev/null
+++ b/development/backend/api/sse.mdx
@@ -0,0 +1,238 @@
+---
+title: Server-Sent Events (SSE)
+description: Implementing real-time server-to-client communication using Server-Sent Events in DeployStack Backend.
+---
+
+## Overview
+
+DeployStack Backend includes `@fastify/sse` for Server-Sent Events support. SSE provides a simple, unidirectional communication channel from server to client over HTTP - ideal for live updates, notifications, and streaming data.
+
+The plugin is globally registered with a 30-second heartbeat interval to keep connections alive.
+
+## Naming Convention Standard
+
+All SSE endpoints **MUST** follow this URL pattern:
+
+- **REST endpoint:** `/api/{resource}/{action}`
+- **SSE endpoint:** `/api/{resource}/{action}/stream`
+
+### URL Pattern Examples
+
+```bash
+# Client Activity
+GET /api/users/me/mcp/client-activity # REST API (polling)
+GET /api/users/me/mcp/client-activity/stream # SSE stream
+
+# Notifications
+GET /api/teams/:teamId/notifications # REST API (polling)
+GET /api/teams/:teamId/notifications/stream # SSE stream
+
+# Metrics
+GET /api/satellites/:satelliteId/metrics # REST API (polling)
+GET /api/satellites/:satelliteId/metrics/stream # SSE stream
+```
+
+### Paired Endpoints
+
+Every SSE endpoint should have a corresponding REST endpoint:
+
+1. **Same Query Parameters**: Both endpoints accept identical query parameters
+2. **Same Data Structure**: Both return the same data format
+3. **Consistent Behavior**: Both apply the same filters and limits
+4. **Fallback Support**: REST endpoint serves as fallback for clients without SSE support
+
+### Why `/stream`?
+
+- **Industry Standard**: Used by GitHub, Stripe, and Twitter APIs
+- **RESTful**: Treats streaming as a sub-resource
+- **Technology Agnostic**: Works for SSE, WebSockets, or any streaming protocol
+- **Clear Intent**: Immediately indicates real-time streaming capability
+
+## Enabling SSE on a Route
+
+Add the `sse: true` option to any route definition:
+
+```typescript
+server.get('/events', { sse: true }, async (request, reply) => {
+ // SSE methods available on reply.sse
+})
+```
+
+For route-specific configuration:
+
+```typescript
+server.get('/events', {
+ sse: {
+ heartbeat: false, // Disable heartbeat for this route
+ serializer: (data) => JSON.stringify(data) // Custom serializer
+ }
+}, handler)
+```
+
+## Sending Messages
+
+### Single Message
+
+```typescript
+reply.sse.send({ data: 'Hello world' })
+```
+
+### Full SSE Format
+
+```typescript
+reply.sse.send({
+ id: '123',
+ event: 'user_update',
+ data: { userId: 'abc', status: 'online' },
+ retry: 5000 // Client retry interval in ms
+})
+```
+
+### Streaming with Async Generator
+
+```typescript
+async function* generateUpdates() {
+ for (let i = 0; i < 10; i++) {
+ yield { data: { count: i } }
+ await new Promise(r => setTimeout(r, 1000))
+ }
+}
+
+reply.sse.send(generateUpdates())
+```
+
+## Connection Management
+
+### Keep Connection Open
+
+By default, the connection closes after the handler completes. To keep it open:
+
+```typescript
+reply.sse.keepAlive()
+```
+
+### Handle Disconnection
+
+```typescript
+reply.sse.onClose(() => {
+ // Cleanup logic when client disconnects
+ server.log.info('Client disconnected')
+})
+```
+
+### Manual Close
+
+```typescript
+reply.sse.close()
+```
+
+## Client Reconnection
+
+Handle reconnecting clients using the `Last-Event-ID` header:
+
+```typescript
+reply.sse.replay(async (lastEventId) => {
+ // Fetch and send missed events since lastEventId
+ const missedEvents = await getMissedEvents(lastEventId)
+ for (const event of missedEvents) {
+ reply.sse.send(event)
+ }
+})
+```
+
+Access the last event ID directly:
+
+```typescript
+const lastId = reply.sse.lastEventId
+```
+
+## Connection State
+
+```typescript
+if (reply.sse.isConnected) {
+ reply.sse.send({ data: 'still connected' })
+}
+```
+
+## Complete Route Example
+
+```typescript
+import { type FastifyInstance } from 'fastify'
+import { requirePermission } from '../../../middleware/roleMiddleware'
+
+export default async function sseRoute(server: FastifyInstance) {
+ server.get('/notifications/stream', {
+ sse: true,
+ preValidation: requirePermission('notifications.read'),
+ schema: {
+ tags: ['Notifications'],
+ summary: 'Stream notifications',
+ description: 'Real-time notification stream via SSE',
+ security: [{ cookieAuth: [] }]
+ }
+ }, async (request, reply) => {
+ const userId = request.user!.id
+
+ // Handle client reconnection
+ reply.sse.replay(async (lastEventId) => {
+ const missed = await notificationService.getMissedNotifications(userId, lastEventId)
+ for (const notification of missed) {
+ reply.sse.send({ id: notification.id, event: 'notification', data: notification })
+ }
+ })
+
+ // Keep connection open
+ reply.sse.keepAlive()
+
+ // Subscribe to new notifications
+ const unsubscribe = notificationService.subscribe(userId, (notification) => {
+ if (reply.sse.isConnected) {
+ reply.sse.send({ id: notification.id, event: 'notification', data: notification })
+ }
+ })
+
+ // Cleanup on disconnect
+ reply.sse.onClose(() => {
+ unsubscribe()
+ server.log.debug({ userId }, 'SSE connection closed')
+ })
+ })
+}
+```
+
+## Frontend Client
+
+```javascript
+const eventSource = new EventSource('/api/notifications/stream', {
+ withCredentials: true // Include cookies for authentication
+})
+
+eventSource.addEventListener('notification', (event) => {
+ const data = JSON.parse(event.data)
+ console.log('New notification:', data)
+})
+
+eventSource.onerror = () => {
+ // Browser automatically reconnects
+ console.log('Connection lost, reconnecting...')
+}
+```
+
+## TypeScript Types
+
+Import types from the package:
+
+```typescript
+import type { SSEMessage } from '@fastify/sse'
+
+const message: SSEMessage = {
+ id: '123',
+ event: 'update',
+ data: { status: 'active' }
+}
+```
+
+## Related Documentation
+
+- [API Documentation Generation](/development/backend/api) - General API development patterns
+- [API Security](/development/backend/api/security) - Authorization middleware for protected SSE endpoints
diff --git a/development/backend/cron.mdx b/development/backend/cron.mdx
index 86c63d7..08ac3ed 100644
--- a/development/backend/cron.mdx
+++ b/development/backend/cron.mdx
@@ -33,22 +33,29 @@ Create a new file in `src/cron/jobs/`:
```typescript
// src/cron/jobs/dailyCleanup.ts
import type { CronJob } from '../cronManager';
-import type { JobQueueService } from '../../services/jobQueueService';
-export function createDailyCleanupJob(jobQueueService: JobQueueService): CronJob {
+export function createDailyCleanupJob(): CronJob {
return {
name: 'daily-cleanup',
schedule: '0 2 * * *', // Every day at 2 AM
-
- task: async () => {
- await jobQueueService.createJob('cleanup_old_data', {
- daysToKeep: 30
- });
- }
+ jobType: 'cleanup_old_data',
+ payload: {
+ daysToKeep: 30,
+ },
};
}
```
+The `CronJob` interface has these fields:
+
+| Field | Required | Description |
+|-------|----------|-------------|
+| `name` | Yes | Unique identifier for logging and tracking |
+| `schedule` | Yes | Cron expression (see syntax below) |
+| `jobType` | Yes | Job type that matches a registered worker |
+| `payload` | No | Static object or function returning payload data |
+| `maxAttempts` | No | Override default retry attempts (default: 3) |
+
### Step 2: Create the Worker
Create a worker to process the job in `src/workers/`:
@@ -136,14 +143,16 @@ export function initializeCronJobs(
jobQueueService: JobQueueService,
logger: FastifyBaseLogger
): CronManager {
- const cronManager = new CronManager(logger);
+ const cronManager = new CronManager(logger, jobQueueService);
- cronManager.register(createDailyCleanupJob(jobQueueService));
+ cronManager.register(createDailyCleanupJob());
return cronManager;
}
```
+The `CronManager` handles job creation automatically when the schedule fires. You don't need to call `createJob()` yourself - just define the `jobType` and `payload` in your cron job definition.
+
## Cron Expression Syntax
The system uses standard cron syntax with 5 or 6 fields:
@@ -194,24 +203,37 @@ Here's a complete example showing how to create a cron job that sends a daily em
```typescript
// src/cron/jobs/dailyDigest.ts
import type { CronJob } from '../cronManager';
-import type { JobQueueService } from '../../services/jobQueueService';
-export function createDailyDigestJob(jobQueueService: JobQueueService): CronJob {
+export function createDailyDigestJob(): CronJob {
return {
name: 'daily-digest-email',
schedule: '0 8 * * *', // Every day at 8 AM
-
- task: async () => {
- // Create job to send digest email
- await jobQueueService.createJob('send_email', {
- to: 'admin@example.com',
- subject: 'Daily Activity Digest',
- template: 'daily_digest',
- variables: {
- date: new Date().toISOString().split('T')[0]
- }
- });
- }
+ jobType: 'send_email',
+ payload: {
+ to: 'admin@example.com',
+ subject: 'Daily Activity Digest',
+ template: 'daily_digest',
+ },
+ };
+}
+```
+
+For dynamic payload values computed at runtime, use a function:
+
+```typescript
+export function createDailyDigestJob(): CronJob {
+ return {
+ name: 'daily-digest-email',
+ schedule: '0 8 * * *',
+ jobType: 'send_email',
+ payload: () => ({
+ to: 'admin@example.com',
+ subject: 'Daily Activity Digest',
+ template: 'daily_digest',
+ variables: {
+ date: new Date().toISOString().split('T')[0],
+ },
+ }),
};
}
```
diff --git a/development/backend/satellite/commands.mdx b/development/backend/satellite/commands.mdx
new file mode 100644
index 0000000..0608d69
--- /dev/null
+++ b/development/backend/satellite/commands.mdx
@@ -0,0 +1,319 @@
+---
+title: Satellite Commands
+description: Command queue system for satellite orchestration including command types, priorities, and event payload patterns.
+sidebarTitle: Commands
+---
+
+The backend uses a command queue system to orchestrate satellites, enabling dynamic MCP server management through prioritized commands with automatic retry logic.
+
+## Command Queue Architecture
+
+Commands are created by the backend and stored in the `satelliteCommands` table. Satellites poll for pending commands, execute them, and report results back to the backend.
+
+### Command Flow
+
+```
+Backend → Creates Command → satelliteCommands Table → Satellite Polls → Executes → Reports Result → Backend Updates Status
+```
+
+**Key Components:**
+- **Command Queue**: PostgreSQL table with priority-based ordering
+- **Polling**: Satellites poll every 2-60 seconds based on priority
+- **Execution**: Satellite processes command and performs action
+- **Result Reporting**: Satellite reports success/failure back to backend
+
+## Command Types
+
+The system supports 5 command types defined in the `command_type` enum:
+
+| Command Type | Purpose | Satellite Action |
+|---|---|---|
+| `configure` | Trigger configuration refresh | Fetch fresh config, apply changes |
+| `spawn` | Start MCP server process | Launch HTTP proxy or stdio process |
+| `kill` | Stop MCP server process | Terminate process gracefully |
+| `restart` | Restart MCP server | Stop and start process |
+| `health_check` | Verify server health | Call tools/list to check connectivity |
+
+### Configure Commands
+
+**Most Common**: Used for all MCP installation lifecycle events (create/update/delete).
+
+**How It Works:**
+1. Backend creates `configure` command with event metadata
+2. Satellite fetches fresh configuration from backend
+3. DynamicConfigManager compares old vs. new config
+4. Identifies added/removed/modified servers
+5. Takes appropriate action (spawn/restart/terminate)
+
+**Key Insight**: The satellite doesn't parse individual event types. All `configure` commands trigger a full configuration refresh where changes are detected by comparing configurations.
+
+## Command Priorities
+
+Commands are processed based on priority:
+
+| Priority | Use Case | Polling Interval |
+|---|---|---|
+| `immediate` | New installations, deletions | 2 seconds |
+| `high` | Server recovery, critical updates | 10 seconds |
+| `normal` | Configuration updates | 30 seconds |
+| `low` | Maintenance tasks | 60 seconds |
+
+Higher priority commands are retrieved first when satellite polls.
+
+## Command Payload Structure
+
+All commands include a JSON payload with event-specific data:
+
+```typescript
+interface CommandPayload {
+ event?: string; // Event type for logging/tracking
+ installation_id?: string; // MCP installation identifier
+ team_id?: string; // Team context
+ server_name?: string; // Legacy server identifier
+ // Additional command-specific fields
+}
+```
+
+## Command Event Types
+
+All `configure` commands include an `event` field in the payload for tracking and logging:
+
+| Event Type | When Sent | Purpose |
+|---|---|---|
+| `mcp_installation_created` | New MCP installation | Satellite refreshes config, spawns new servers |
+| `mcp_installation_updated` | Config changes (args/env/headers) | Satellite refreshes config, restarts affected servers |
+| `mcp_installation_deleted` | Installation removed | Satellite refreshes config, terminates removed servers |
+| `mcp_recovery` | Server recovered from failure | Satellite rediscovers tools for recovered server |
+
+### Event Field Usage
+
+**Current Implementation**: The satellite does NOT parse the `event` field. All configure commands trigger a full configuration refresh where the satellite:
+
+1. Fetches fresh config from backend
+2. Compares with current running config
+3. Identifies added/removed/modified servers
+4. Takes appropriate action
+
+**Purpose of Event Field**:
+- Database record keeping
+- Structured logging
+- Future extensibility
+- Developer debugging
+
+**Example payload** for deletion:
+```json
+{
+ "event": "mcp_installation_deleted",
+ "installation_id": "U3hCfHenbK5kH2_8ehSGx",
+ "team_id": "4vj7igb2fcwzmko"
+}
+```
+
+## SatelliteCommandService API
+
+The backend provides convenience methods for creating commands:
+
+### notifyMcpInstallation()
+
+Creates immediate priority configure commands when a new MCP server is installed.
+
+```typescript
+await satelliteCommandService.notifyMcpInstallation(
+ installationId,
+ teamId,
+ userId
+);
+```
+
+**Payload**: `event: 'mcp_installation_created'`
+
+### notifyMcpUpdate()
+
+Creates immediate priority configure commands when MCP configuration is updated.
+
+```typescript
+await satelliteCommandService.notifyMcpUpdate(
+ installationId,
+ teamId,
+ userId
+);
+```
+
+**Payload**: `event: 'mcp_installation_updated'`
+
+### notifyMcpDeletion()
+
+Creates immediate priority configure commands when an MCP installation is deleted.
+
+```typescript
+await satelliteCommandService.notifyMcpDeletion(
+ installationId,
+ teamId,
+ userId
+);
+```
+
+**Payload**: `event: 'mcp_installation_deleted'`
+
+### notifyMcpRecovery()
+
+Creates high priority configure commands when a server recovers from failure.
+
+```typescript
+await satelliteCommandService.notifyMcpRecovery(
+ installationId,
+ teamId
+);
+```
+
+**Payload**: `event: 'mcp_recovery'`
+
+## Critical Pattern
+
+**ALWAYS use the correct convenience method**:
+
+- Installation created → `notifyMcpInstallation()`
+- Installation updated → `notifyMcpUpdate()`
+- Installation deleted → `notifyMcpDeletion()`
+- Server recovered → `notifyMcpRecovery()`
+
+**NEVER** call the wrong method for an operation! For example, don't call `notifyMcpInstallation()` for delete operations.
+
+## Command Lifecycle
+
+Commands progress through the following states:
+
+| Status | Description |
+|---|---|
+| `pending` | Command created, awaiting satellite poll |
+| `acknowledged` | Satellite retrieved command |
+| `executing` | Satellite processing command |
+| `completed` | Command executed successfully |
+| `failed` | Command execution failed |
+
+### Retry Logic
+
+Failed commands are automatically retried:
+
+- **Max Retries**: 3 attempts
+- **Retry Delay**: Exponential backoff
+- **Expiration**: Commands expire after 5 minutes
+
+## Database Schema
+
+**Table**: `satelliteCommands`
+
+**Key Fields**:
+- `id`: Command UUID
+- `satellite_id`: Target satellite
+- `command_type`: One of 5 command types
+- `priority`: Command priority level
+- `payload`: JSON command data (includes event type)
+- `status`: Current execution state
+- `target_team_id`: Team context
+- `correlation_id`: Request tracing
+- `retry_count`: Retry attempts
+- `error_message`: Failure details
+- `result`: Execution result
+
+## Command Processing on Satellite
+
+When satellites receive commands:
+
+**For `configure` commands**:
+1. Fetch fresh configuration from backend
+2. Compare with current running config
+3. Identify changes (added/removed/modified servers)
+4. Execute appropriate actions:
+ - **Added**: Spawn new processes
+ - **Modified**: Restart affected processes
+ - **Removed**: Terminate processes
+
+**For `spawn` commands**:
+1. Parse server configuration
+2. For stdio: Launch subprocess with JSON-RPC
+3. For HTTP/SSE: Create proxy tracker entry
+4. Discover tools via tools/list call
+
+**For `kill` commands**:
+1. Locate running process
+2. Send SIGTERM for graceful shutdown
+3. Wait for timeout (10 seconds)
+4. Send SIGKILL if needed
+
+**For `restart` commands**:
+1. Execute kill sequence
+2. Wait for process termination
+3. Execute spawn sequence
+
+**For `health_check` commands**:
+1. Call tools/list on target server
+2. Verify response
+3. Report health status
+
+## Example Usage
+
+### Creating Commands for Installation Deletion
+
+```typescript
+// In route handler
+const satelliteCommandService = new SatelliteCommandService(db, logger);
+const commands = await satelliteCommandService.notifyMcpDeletion(
+ installationId,
+ teamId,
+ userId
+);
+
+logger.info({
+ installationId,
+ commandsCreated: commands.length,
+ satelliteIds: commands.map(c => c.satellite_id)
+}, 'Satellite commands created for deletion');
+```
+
+### Creating Commands for Configuration Update
+
+```typescript
+// After updating environment variables
+const satelliteCommandService = new SatelliteCommandService(db, logger);
+const commands = await satelliteCommandService.notifyMcpUpdate(
+ installationId,
+ teamId,
+ userId
+);
+```
+
+## Monitoring Commands
+
+### Check Command Status
+
+```sql
+SELECT id, command_type, status, priority, created_at, updated_at
+FROM "satelliteCommands"
+WHERE satellite_id = 'sat-123'
+ORDER BY created_at DESC
+LIMIT 10;
+```
+
+### View Command Payload
+
+```sql
+SELECT id, command_type, payload::json->>'event' as event_type, payload
+FROM "satelliteCommands"
+WHERE id = 'cmd-456';
+```
+
+### Monitor Failed Commands
+
+```sql
+SELECT id, command_type, status, retry_count, error_message
+FROM "satelliteCommands"
+WHERE status = 'failed'
+AND retry_count >= max_retries;
+```
+
+## Related Documentation
+
+- [Satellite Communication](/development/backend/satellite/communication) - Overall communication architecture
+- [Satellite Events](/development/backend/satellite/events) - Event system for real-time updates
+- [Database Management](/development/backend/database/) - Schema and migrations
diff --git a/development/backend/satellite/communication.mdx b/development/backend/satellite/communication.mdx
index 909a34c..3eefcce 100644
--- a/development/backend/satellite/communication.mdx
+++ b/development/backend/satellite/communication.mdx
@@ -46,6 +46,7 @@ The satellite communication system includes:
- Satellites register with backend and receive API keys
- Initial status set to 'inactive' for security
- API keys stored as Argon2 hashes in database
+- Satellite URL sent during registration (optional - auto-detected if not provided)
**Activation Process**:
- Satellites send heartbeat after registration
@@ -82,8 +83,8 @@ The system uses three distinct communication patterns:
**Heartbeat (Satellite → Backend, Periodic)**:
- Satellites report status every 30 seconds
-- Contains: System metrics, process counts, resource usage
-- Used for: Health monitoring, capacity planning
+- Contains: System metrics, process counts, resource usage, satellite URL (first heartbeat only)
+- Used for: Health monitoring, capacity planning, satellite URL updates
**Events (Satellite → Backend, Immediate)**:
- Satellites emit events when actions occur, batched every 3 seconds
@@ -149,6 +150,17 @@ The backend maintains a priority-based command queue system:
- `normal`: Standard commands processed during regular polling
- `low`: Background maintenance commands
+### Command Event Types
+
+Configure commands include an `event` field in the payload for tracking purposes:
+
+- `mcp_installation_created` - New installation
+- `mcp_installation_updated` - Configuration change
+- `mcp_installation_deleted` - Installation removed
+- `mcp_recovery` - Server recovery
+
+See [Satellite Commands](/development/backend/satellite/commands) for complete event type documentation and usage patterns.
+
### Adaptive Polling Strategy
Satellites adjust polling behavior based on backend signals:
@@ -191,6 +203,7 @@ Satellites report health and performance metrics:
- Disk usage and network connectivity status
- Process count and resource utilization
- Uptime and stability indicators
+- Satellite URL (first heartbeat after startup only) - Updates public URL for satellite discovery
**Process Metrics**:
- Individual MCP server process status
@@ -259,6 +272,7 @@ The satellite system integrates with existing DeployStack schema through 5 speci
- Type classification (global/team) and ownership
- Capability tracking and status monitoring
- API key management and authentication
+- Satellite URL tracking - Publicly accessible URL updated during registration and first heartbeat
**Command Queue** (`satelliteCommands`):
- Priority-based command orchestration
diff --git a/development/frontend/api-sse.mdx b/development/frontend/api-sse.mdx
new file mode 100644
index 0000000..ec1639f
--- /dev/null
+++ b/development/frontend/api-sse.mdx
@@ -0,0 +1,252 @@
+---
+title: Using Server-Sent Events (SSE)
+description: How to consume real-time SSE endpoints from the backend using the useSSE composable
+---
+
+# 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:
+
+```typescript
+// 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` composable handles all SSE logic including connection management, auto-reconnect, and cleanup.
+
+### Import
+
+```typescript
+import { useSSE } from '@/composables/useSSE'
+import type { McpClientActivity } from '@/services/mcpClientActivityService'
+```
+
+### Basic Usage
+
+
+
+ Call `useSSE()` with your expected data type and the SSE event name:
+
+ ```typescript
+ const {
+ data: activities,
+ isLoading,
+ error,
+ connect,
+ disconnect
+ } = useSSE('client_activity')
+ ```
+
+
+
+ Call `connect()` with the full SSE stream URL:
+
+ ```typescript
+ onMounted(() => {
+ const url = McpClientActivityService.getStreamUrl(teamId, {
+ limit: 20,
+ active_within_minutes: 30
+ })
+ connect(url)
+ })
+ ```
+
+
+
+ The `data` ref will automatically update when the server sends new events:
+
+ ```vue
+
+
Loading...
+
{{ error }}
+
+
+ {{ activity.client_name }}
+
+
+
+ ```
+
+
+
+## Complete Example
+
+Here's the complete implementation from [McpClientConnectionsCard.vue](https://github.com/deploystackio/deploystack/blob/main/services/frontend/src/components/mcp-server/McpClientConnectionsCard.vue):
+
+```vue
+
+
+
+
+ Loading...
+
+
+
+
{{ error }}
+
+
+
+
+ No active connections
+
+
+
+
+ {{ activity.client_name }}
+
+
+
+```
+
+## API Reference
+
+### useSSE\()
+
+```typescript
+function useSSE(
+ eventName: string,
+ options?: UseSSEOptions
+): UseSSEReturn
+```
+
+**Parameters:**
+- `eventName`: The SSE event name to listen for (must match backend event name)
+- `options`: Optional configuration
+
+**Options:**
+```typescript
+interface UseSSEOptions {
+ reconnectDelay?: number // Default: 5000ms
+ withCredentials?: boolean // Default: true
+}
+```
+
+**Returns:**
+```typescript
+interface UseSSEReturn {
+ data: Ref // Reactive data from SSE stream
+ isConnected: Ref // Connection status
+ isLoading: Ref // Initial loading state
+ error: Ref // 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()`.
+
+
+## Backend SSE Event Format
+
+Your backend SSE endpoint must send events in this format:
+
+```typescript
+// Backend sends:
+reply.sse.send({
+ event: 'client_activity', // Must match eventName in useSSE()
+ data: JSON.stringify({
+ activities: [...] // Your actual data
+ })
+})
+```
+
+The composable will:
+1. Parse the JSON data
+2. Extract the first key's value (e.g., `activities` array)
+3. 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.
+
+
+## Related Documentation
+
+- [Backend SSE Plugin Configuration](/development/backend/api/sse)
+- [useEventBus Composable](/development/frontend/event-bus)
diff --git a/development/frontend/ui/design-card.mdx b/development/frontend/ui/design-card.mdx
new file mode 100644
index 0000000..50ebb87
--- /dev/null
+++ b/development/frontend/ui/design-card.mdx
@@ -0,0 +1,186 @@
+---
+title: Card (DsCard)
+description: Vercel-style card component with title, description, content area, and flexible footer.
+---
+
+The `DsCard` component is a Vercel-style fieldset/card for forms and settings. It features a content area with title and description, plus a footer with flexible slots for status text and action buttons.
+
+## Component Location
+
+```
+services/frontend/src/components/ui/ds-card/
+ DsCard.vue # Card component
+ index.ts # Component exports
+```
+
+## Props
+
+| Prop | Type | Required | Description |
+|------|------|----------|-------------|
+| `title` | `string` | No | Card heading (hidden when not provided) |
+
+## Slots
+
+| Slot | Description |
+|------|-------------|
+| `default` | Main content area (description, forms, inputs, etc.) |
+| `footer-status` | Left side of footer (text, links, hints) |
+| `footer-actions` | Right side of footer (buttons, button groups) |
+
+## Basic Usage
+
+```vue
+
+
+
+
+
+ This is your team's visible name. For example, the name of your company or department.
+