Skip to content

Commit a3ce748

Browse files
author
Lasim
committed
feat(all): add SSE streaming to MCP metrics endpoint
- Backend: Create /stream endpoint for MCP client activity metrics - Backend: 15-second update interval with hash-based change detection - Frontend: Add getStreamUrl() to McpClientActivityMetricsService - Frontend: Update McpStats.vue to use SSE composable - Frontend: Handle page unload gracefully to prevent browser warnings
1 parent 305b051 commit a3ce748

File tree

7 files changed

+376
-53
lines changed

7 files changed

+376
-53
lines changed

services/backend/api-spec.json

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6142,6 +6142,86 @@
61426142
}
61436143
}
61446144
},
6145+
"/api/me/metrics/mcp/client-activity/stream": {
6146+
"get": {
6147+
"summary": "Stream MCP client activity metrics via SSE",
6148+
"tags": [
6149+
"Users",
6150+
"Metrics",
6151+
"MCP"
6152+
],
6153+
"description": "Real-time stream of MCP client activity metrics using Server-Sent Events. Pushes updates every 15 seconds.",
6154+
"parameters": [
6155+
{
6156+
"schema": {
6157+
"type": "string",
6158+
"minLength": 1
6159+
},
6160+
"in": "query",
6161+
"name": "team_id",
6162+
"required": true,
6163+
"description": "Team ID to filter metrics"
6164+
},
6165+
{
6166+
"schema": {
6167+
"type": "string",
6168+
"enum": [
6169+
"1h",
6170+
"3h",
6171+
"6h",
6172+
"12h",
6173+
"24h",
6174+
"3d"
6175+
]
6176+
},
6177+
"in": "query",
6178+
"name": "time_range",
6179+
"required": false,
6180+
"description": "Time range for metrics (maximum 3 days for MCP client activity)"
6181+
},
6182+
{
6183+
"schema": {
6184+
"type": "string",
6185+
"enum": [
6186+
"15m"
6187+
]
6188+
},
6189+
"in": "query",
6190+
"name": "interval",
6191+
"required": false,
6192+
"description": "Bucket interval for aggregation (only 15m supported for MCP client activity)"
6193+
},
6194+
{
6195+
"schema": {
6196+
"type": "string"
6197+
},
6198+
"in": "query",
6199+
"name": "satellite_id",
6200+
"required": false,
6201+
"description": "Optional satellite ID to filter metrics"
6202+
},
6203+
{
6204+
"schema": {
6205+
"type": "string"
6206+
},
6207+
"in": "query",
6208+
"name": "auth_identifier",
6209+
"required": false,
6210+
"description": "Optional auth identifier to filter metrics"
6211+
}
6212+
],
6213+
"security": [
6214+
{
6215+
"cookieAuth": []
6216+
}
6217+
],
6218+
"responses": {
6219+
"200": {
6220+
"description": "Default Response"
6221+
}
6222+
}
6223+
}
6224+
},
61456225
"/api/settings": {
61466226
"get": {
61476227
"summary": "List all global settings",

services/backend/api-spec.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4221,6 +4221,62 @@ paths:
42214221
- success
42224222
- error
42234223
description: Internal Server Error
4224+
/api/me/metrics/mcp/client-activity/stream:
4225+
get:
4226+
summary: Stream MCP client activity metrics via SSE
4227+
tags:
4228+
- Users
4229+
- Metrics
4230+
- MCP
4231+
description: Real-time stream of MCP client activity metrics using Server-Sent
4232+
Events. Pushes updates every 15 seconds.
4233+
parameters:
4234+
- schema:
4235+
type: string
4236+
minLength: 1
4237+
in: query
4238+
name: team_id
4239+
required: true
4240+
description: Team ID to filter metrics
4241+
- schema:
4242+
type: string
4243+
enum:
4244+
- 1h
4245+
- 3h
4246+
- 6h
4247+
- 12h
4248+
- 24h
4249+
- 3d
4250+
in: query
4251+
name: time_range
4252+
required: false
4253+
description: Time range for metrics (maximum 3 days for MCP client activity)
4254+
- schema:
4255+
type: string
4256+
enum:
4257+
- 15m
4258+
in: query
4259+
name: interval
4260+
required: false
4261+
description: Bucket interval for aggregation (only 15m supported for MCP client
4262+
activity)
4263+
- schema:
4264+
type: string
4265+
in: query
4266+
name: satellite_id
4267+
required: false
4268+
description: Optional satellite ID to filter metrics
4269+
- schema:
4270+
type: string
4271+
in: query
4272+
name: auth_identifier
4273+
required: false
4274+
description: Optional auth identifier to filter metrics
4275+
security:
4276+
- cookieAuth: []
4277+
responses:
4278+
"200":
4279+
description: Default Response
42244280
/api/settings:
42254281
get:
42264282
summary: List all global settings
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { type FastifyInstance } from 'fastify';
2+
import { requirePermission } from '../../../../../middleware/roleMiddleware';
3+
import { getDb } from '../../../../../db';
4+
import { McpClientActivityMetricsService } from '../../../../../services/metrics/McpClientActivityMetricsService';
5+
6+
const QUERY_PARAMS_SCHEMA = {
7+
type: 'object',
8+
properties: {
9+
team_id: {
10+
type: 'string',
11+
minLength: 1,
12+
description: 'Team ID to filter metrics'
13+
},
14+
time_range: {
15+
type: 'string',
16+
enum: ['1h', '3h', '6h', '12h', '24h', '3d'],
17+
description: 'Time range for metrics (maximum 3 days for MCP client activity)'
18+
},
19+
interval: {
20+
type: 'string',
21+
enum: ['15m'],
22+
description: 'Bucket interval for aggregation (only 15m supported for MCP client activity)'
23+
},
24+
satellite_id: {
25+
type: 'string',
26+
description: 'Optional satellite ID to filter metrics'
27+
},
28+
auth_identifier: {
29+
type: 'string',
30+
description: 'Optional auth identifier to filter metrics'
31+
}
32+
},
33+
required: ['team_id'],
34+
additionalProperties: false
35+
} as const;
36+
37+
interface QueryParams {
38+
team_id: string;
39+
time_range?: string;
40+
interval?: string;
41+
satellite_id?: string;
42+
auth_identifier?: string;
43+
}
44+
45+
export default async function mcpClientActivityMetricsStreamRoute(server: FastifyInstance) {
46+
server.get('/client-activity/stream', {
47+
sse: true,
48+
preValidation: requirePermission('metrics.mcp_client_activity_metrics.view'),
49+
schema: {
50+
tags: ['Users', 'Metrics', 'MCP'],
51+
summary: 'Stream MCP client activity metrics via SSE',
52+
description: 'Real-time stream of MCP client activity metrics using Server-Sent Events. Pushes updates every 15 seconds.',
53+
security: [{ cookieAuth: [] }],
54+
querystring: QUERY_PARAMS_SCHEMA
55+
}
56+
}, async (request, reply) => {
57+
const userId = request.user!.id;
58+
const query = request.query as QueryParams;
59+
const teamId = query.team_id;
60+
const timeRange = query.time_range || '3h';
61+
const interval = query.interval || '15m';
62+
63+
let updateInterval: NodeJS.Timeout | null = null;
64+
let lastDataHash = '';
65+
66+
const db = getDb();
67+
const metricsService = new McpClientActivityMetricsService(db, server.log);
68+
69+
// Keep connection open
70+
reply.sse.keepAlive();
71+
72+
// Send initial data
73+
try {
74+
const result = await metricsService.getMetrics(
75+
userId,
76+
teamId,
77+
timeRange,
78+
interval,
79+
query.satellite_id,
80+
query.auth_identifier
81+
);
82+
83+
lastDataHash = JSON.stringify(result.data);
84+
85+
reply.sse.send({
86+
event: 'mcp_metrics',
87+
data: { metrics: result.data }
88+
});
89+
} catch (error) {
90+
server.log.error(error, 'SSE: Error fetching initial MCP metrics');
91+
reply.sse.send({
92+
event: 'error',
93+
data: { error: 'Failed to fetch MCP metrics' }
94+
});
95+
}
96+
97+
// Set up periodic updates (every 15 seconds)
98+
updateInterval = setInterval(async () => {
99+
if (!reply.sse.isConnected) {
100+
if (updateInterval) clearInterval(updateInterval);
101+
return;
102+
}
103+
104+
try {
105+
const result = await metricsService.getMetrics(
106+
userId,
107+
teamId,
108+
timeRange,
109+
interval,
110+
query.satellite_id,
111+
query.auth_identifier
112+
);
113+
114+
const currentHash = JSON.stringify(result.data);
115+
116+
// Only send if data changed
117+
if (currentHash !== lastDataHash) {
118+
lastDataHash = currentHash;
119+
reply.sse.send({
120+
event: 'mcp_metrics',
121+
data: { metrics: result.data }
122+
});
123+
}
124+
} catch (error) {
125+
server.log.error(error, 'SSE: Error fetching MCP metrics update');
126+
}
127+
}, 15000);
128+
129+
// Cleanup on disconnect
130+
reply.sse.onClose(() => {
131+
if (updateInterval) {
132+
clearInterval(updateInterval);
133+
updateInterval = null;
134+
}
135+
server.log.debug({ userId, teamId }, 'SSE: MCP metrics stream closed');
136+
});
137+
});
138+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { type FastifyInstance } from 'fastify';
22
import clientActivityRoute from './client-activity';
3+
import clientActivityStreamRoute from './client-activity-stream';
34

45
export default async function mcpMetricsRoutes(server: FastifyInstance) {
56
await server.register(clientActivityRoute);
7+
await server.register(clientActivityStreamRoute);
68
}

0 commit comments

Comments
 (0)