Skip to content

Commit 7efa7c3

Browse files
committed
feat(all): add MCP server status summary to admin team installations view
Added aggregated status display for MCP server installations in the admin team view. The backend endpoint now joins with mcpServerInstances table and aggregates instance statuses (online, offline, error, provisioning) per installation. The frontend displays status counts as stacked mini-badges, providing admins with a quick overview of team MCP server health. Backend changes: - Updated admin teams schemas to include status_summary field with counts - Modified mcp-installations route to join with mcpServerInstances and aggregate status by installation - Added in-memory aggregation logic to group instance statuses Frontend changes: - Updated TeamDetailMcpServers component interface to include StatusSummary - Replaced single status badge with stacked mini-badges showing counts for each status type - Added handling for edge case when installations have no instances
1 parent 266fc9d commit 7efa7c3

File tree

5 files changed

+168
-70
lines changed

5 files changed

+168
-70
lines changed

services/backend/api-spec.json

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35413,9 +35413,37 @@
3541335413
"type": "string",
3541435414
"description": "MCP server slug"
3541535415
},
35416-
"status": {
35417-
"type": "string",
35418-
"description": "Installation status (provisioning|online|offline|error|...)"
35416+
"status_summary": {
35417+
"type": "object",
35418+
"properties": {
35419+
"total_instances": {
35420+
"type": "integer",
35421+
"description": "Total number of user instances for this installation"
35422+
},
35423+
"online": {
35424+
"type": "integer",
35425+
"description": "Number of instances with online status"
35426+
},
35427+
"offline": {
35428+
"type": "integer",
35429+
"description": "Number of instances with offline status"
35430+
},
35431+
"error": {
35432+
"type": "integer",
35433+
"description": "Number of instances with error or permanently_failed status"
35434+
},
35435+
"provisioning": {
35436+
"type": "integer",
35437+
"description": "Number of instances in provisioning states (provisioning, connecting, discovering_tools, etc.)"
35438+
}
35439+
},
35440+
"required": [
35441+
"total_instances",
35442+
"online",
35443+
"offline",
35444+
"error",
35445+
"provisioning"
35446+
]
3541935447
},
3542035448
"created_at": {
3542135449
"type": "string",
@@ -35433,7 +35461,7 @@
3543335461
"installation_name",
3543435462
"server_name",
3543535463
"server_slug",
35436-
"status",
35464+
"status_summary",
3543735465
"created_at"
3543835466
]
3543935467
}

services/backend/api-spec.yaml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25071,9 +25071,31 @@ paths:
2507125071
server_slug:
2507225072
type: string
2507325073
description: MCP server slug
25074-
status:
25075-
type: string
25076-
description: Installation status (provisioning|online|offline|error|...)
25074+
status_summary:
25075+
type: object
25076+
properties:
25077+
total_instances:
25078+
type: integer
25079+
description: Total number of user instances for this installation
25080+
online:
25081+
type: integer
25082+
description: Number of instances with online status
25083+
offline:
25084+
type: integer
25085+
description: Number of instances with offline status
25086+
error:
25087+
type: integer
25088+
description: Number of instances with error or permanently_failed status
25089+
provisioning:
25090+
type: integer
25091+
description: Number of instances in provisioning states (provisioning,
25092+
connecting, discovering_tools, etc.)
25093+
required:
25094+
- total_instances
25095+
- online
25096+
- offline
25097+
- error
25098+
- provisioning
2507725099
created_at:
2507825100
type: string
2507925101
description: ISO8601 timestamp
@@ -25087,7 +25109,7 @@ paths:
2508725109
- installation_name
2508825110
- server_name
2508925111
- server_slug
25090-
- status
25112+
- status_summary
2509125113
- created_at
2509225114
pagination:
2509325115
type: object

services/backend/src/routes/admin/teams/mcp-installations.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,44 +76,89 @@ export default async function getTeamMcpInstallationsAdminRoute(server: FastifyI
7676
return reply.status(404).type('application/json').send(jsonString);
7777
}
7878

79-
// 3. Query installations with server data
79+
// 3. Query installations with server data and instance statuses
8080
const db = getDb();
8181
const schema = getSchema();
8282

83-
const installations = await db
83+
const installationsWithInstances = await db
8484
.select({
8585
installation_id: schema.mcpServerInstallations.id,
8686
server_id: schema.mcpServerInstallations.server_id,
8787
installation_name: schema.mcpServerInstallations.installation_name,
8888
server_name: schema.mcpServers.name,
8989
server_slug: schema.mcpServers.slug,
9090
created_at: schema.mcpServerInstallations.created_at,
91-
last_used_at: schema.mcpServerInstallations.last_used_at
91+
last_used_at: schema.mcpServerInstallations.last_used_at,
92+
instance_status: schema.mcpServerInstances.status
9293
})
9394
.from(schema.mcpServerInstallations)
9495
.leftJoin(
9596
schema.mcpServers,
9697
eq(schema.mcpServerInstallations.server_id, schema.mcpServers.id)
9798
)
99+
.leftJoin(
100+
schema.mcpServerInstances,
101+
eq(schema.mcpServerInstances.installation_id, schema.mcpServerInstallations.id)
102+
)
98103
.where(eq(schema.mcpServerInstallations.team_id, teamId))
99104
.orderBy(desc(schema.mcpServerInstallations.created_at));
100105

101-
// 4. Serialize installations
106+
// 4. Aggregate status by installation
107+
const installationMap = new Map<string, any>();
108+
for (const row of installationsWithInstances) {
109+
if (!installationMap.has(row.installation_id)) {
110+
installationMap.set(row.installation_id, {
111+
installation_id: row.installation_id,
112+
server_id: row.server_id,
113+
installation_name: row.installation_name,
114+
server_name: row.server_name ?? 'Unknown Server',
115+
server_slug: row.server_slug ?? 'unknown',
116+
created_at: row.created_at,
117+
last_used_at: row.last_used_at,
118+
status_summary: {
119+
total_instances: 0,
120+
online: 0,
121+
offline: 0,
122+
error: 0,
123+
provisioning: 0
124+
}
125+
});
126+
}
127+
128+
const inst = installationMap.get(row.installation_id);
129+
if (row.instance_status) {
130+
inst.status_summary.total_instances++;
131+
if (row.instance_status === 'online') {
132+
inst.status_summary.online++;
133+
} else if (row.instance_status === 'offline') {
134+
inst.status_summary.offline++;
135+
} else if (['error', 'permanently_failed'].includes(row.instance_status)) {
136+
inst.status_summary.error++;
137+
} else if (['provisioning', 'connecting', 'discovering_tools', 'syncing_tools', 'restarting', 'command_received', 'awaiting_user_config'].includes(row.instance_status)) {
138+
inst.status_summary.provisioning++;
139+
}
140+
}
141+
}
142+
143+
const installations = Array.from(installationMap.values());
144+
145+
// 5. Serialize installations
102146
const serializedInstallations = installations.map(inst => ({
103147
installation_id: inst.installation_id,
104148
server_id: inst.server_id,
105149
installation_name: inst.installation_name,
106-
server_name: inst.server_name ?? 'Unknown Server',
107-
server_slug: inst.server_slug ?? 'unknown',
150+
server_name: inst.server_name,
151+
server_slug: inst.server_slug,
152+
status_summary: inst.status_summary,
108153
created_at: inst.created_at.toISOString(),
109154
last_used_at: inst.last_used_at ? inst.last_used_at.toISOString() : null
110155
}));
111156

112-
// 5. Apply pagination
157+
// 6. Apply pagination
113158
const total = serializedInstallations.length;
114159
const paginatedInstallations = serializedInstallations.slice(offset, offset + limit);
115160

116-
// 6. Log operation
161+
// 7. Log operation
117162
server.log.info({
118163
operation: 'get_team_mcp_installations_admin',
119164
teamId,
@@ -122,7 +167,7 @@ export default async function getTeamMcpInstallationsAdminRoute(server: FastifyI
122167
pagination: { limit, offset }
123168
}, 'Team MCP installations retrieved successfully');
124169

125-
// 7. Build success response
170+
// 8. Build success response
126171
const successResponse: McpInstallationsResponse = {
127172
success: true,
128173
data: {

services/backend/src/routes/admin/teams/schemas.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,18 @@ export function validatePaginationParams(query: PaginationQuery): { limit: numbe
226226
}
227227

228228
// ===== MCP INSTALLATIONS SCHEMAS =====
229+
const STATUS_SUMMARY_SCHEMA = {
230+
type: 'object',
231+
properties: {
232+
total_instances: { type: 'integer', description: 'Total number of user instances for this installation' },
233+
online: { type: 'integer', description: 'Number of instances with online status' },
234+
offline: { type: 'integer', description: 'Number of instances with offline status' },
235+
error: { type: 'integer', description: 'Number of instances with error or permanently_failed status' },
236+
provisioning: { type: 'integer', description: 'Number of instances in provisioning states (provisioning, connecting, discovering_tools, etc.)' }
237+
},
238+
required: ['total_instances', 'online', 'offline', 'error', 'provisioning']
239+
} as const;
240+
229241
export const MCP_INSTALLATION_SCHEMA = {
230242
type: 'object',
231243
properties: {
@@ -234,11 +246,11 @@ export const MCP_INSTALLATION_SCHEMA = {
234246
installation_name: { type: 'string', description: 'User-defined installation name' },
235247
server_name: { type: 'string', description: 'MCP server name' },
236248
server_slug: { type: 'string', description: 'MCP server slug' },
237-
status: { type: 'string', description: 'Installation status (provisioning|online|offline|error|...)' },
249+
status_summary: STATUS_SUMMARY_SCHEMA,
238250
created_at: { type: 'string', description: 'ISO8601 timestamp' },
239251
last_used_at: { type: 'string', nullable: true, description: 'ISO8601 timestamp or null' }
240252
},
241-
required: ['installation_id', 'server_id', 'installation_name', 'server_name', 'server_slug', 'status', 'created_at']
253+
required: ['installation_id', 'server_id', 'installation_name', 'server_name', 'server_slug', 'status_summary', 'created_at']
242254
} as const;
243255

244256
export const MCP_INSTALLATIONS_RESPONSE_SCHEMA = {
@@ -261,12 +273,21 @@ export const MCP_INSTALLATIONS_RESPONSE_SCHEMA = {
261273
} as const;
262274

263275
// TypeScript interfaces for MCP installations
276+
export interface StatusSummary {
277+
total_instances: number;
278+
online: number;
279+
offline: number;
280+
error: number;
281+
provisioning: number;
282+
}
283+
264284
export interface McpInstallation {
265285
installation_id: string;
266286
server_id: string;
267287
installation_name: string;
268288
server_name: string;
269289
server_slug: string;
290+
status_summary: StatusSummary;
270291
created_at: string;
271292
last_used_at: string | null;
272293
}

services/frontend/src/components/admin/teams/TeamDetailMcpServers.vue

Lines changed: 33 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,21 @@ import PaginationControls from '@/components/ui/pagination/PaginationControls.vu
1515
import { Package, CircleCheck, CircleMinus, CircleAlert, Circle } from 'lucide-vue-next'
1616
import { getEnv } from '@/utils/env'
1717
18+
interface StatusSummary {
19+
total_instances: number
20+
online: number
21+
offline: number
22+
error: number
23+
provisioning: number
24+
}
25+
1826
interface McpInstallation {
1927
installation_id: string
2028
server_id: string
2129
installation_name: string
2230
server_name: string
2331
server_slug: string
24-
status: string
32+
status_summary: StatusSummary
2533
created_at: string
2634
last_used_at: string | null
2735
}
@@ -107,48 +115,6 @@ const handlePageSizeChange = async (newPageSize: number) => {
107115
await loadInstallations()
108116
}
109117
110-
// Get status icon component and classes
111-
const getStatusIcon = (status: string) => {
112-
switch (status) {
113-
case 'online':
114-
return {
115-
icon: CircleCheck,
116-
class: 'size-3 fill-green-500 text-green-500 dark:fill-green-400 dark:text-green-400'
117-
}
118-
case 'offline':
119-
return {
120-
icon: CircleMinus,
121-
class: 'size-3 text-muted-foreground'
122-
}
123-
case 'error':
124-
case 'permanently_failed':
125-
return {
126-
icon: CircleAlert,
127-
class: 'size-3 fill-red-500 text-red-500 dark:fill-red-400 dark:text-red-400'
128-
}
129-
case 'requires_reauth':
130-
return {
131-
icon: CircleAlert,
132-
class: 'size-3 fill-yellow-500 text-yellow-500 dark:fill-yellow-400 dark:text-yellow-400'
133-
}
134-
case 'provisioning':
135-
case 'connecting':
136-
case 'discovering_tools':
137-
case 'syncing_tools':
138-
case 'restarting':
139-
case 'command_received':
140-
return {
141-
icon: Circle,
142-
class: 'size-3 fill-blue-500 text-blue-500 dark:fill-blue-400 dark:text-blue-400'
143-
}
144-
default:
145-
return {
146-
icon: Circle,
147-
class: 'size-3 text-muted-foreground'
148-
}
149-
}
150-
}
151-
152118
// Format date for display
153119
const formatDate = (dateString: string) => {
154120
return new Date(dateString).toLocaleDateString()
@@ -231,14 +197,30 @@ onMounted(async () => {
231197
</router-link>
232198
</TableCell>
233199
<TableCell>
234-
<div
235-
class="inline-flex items-center justify-center rounded-full border px-1.5 py-0.5 text-xs font-medium text-muted-foreground gap-1"
236-
>
237-
<component
238-
:is="getStatusIcon(installation.status).icon"
239-
:class="getStatusIcon(installation.status).class"
240-
/>
241-
<span>{{ installation.status }}</span>
200+
<div v-if="installation.status_summary.total_instances === 0" class="text-muted-foreground text-xs">
201+
No instances
202+
</div>
203+
<div v-else class="flex flex-col gap-1">
204+
<!-- Show online count if > 0 -->
205+
<div v-if="installation.status_summary.online > 0" class="inline-flex items-center gap-1 text-xs">
206+
<CircleCheck class="size-3 fill-green-500 text-green-500 dark:fill-green-400 dark:text-green-400" />
207+
<span>{{ installation.status_summary.online }} online</span>
208+
</div>
209+
<!-- Show offline count if > 0 -->
210+
<div v-if="installation.status_summary.offline > 0" class="inline-flex items-center gap-1 text-xs">
211+
<CircleMinus class="size-3 text-muted-foreground" />
212+
<span>{{ installation.status_summary.offline }} offline</span>
213+
</div>
214+
<!-- Show error count if > 0 -->
215+
<div v-if="installation.status_summary.error > 0" class="inline-flex items-center gap-1 text-xs">
216+
<CircleAlert class="size-3 fill-red-500 text-red-500 dark:fill-red-400 dark:text-red-400" />
217+
<span>{{ installation.status_summary.error }} error</span>
218+
</div>
219+
<!-- Show provisioning count if > 0 -->
220+
<div v-if="installation.status_summary.provisioning > 0" class="inline-flex items-center gap-1 text-xs">
221+
<Circle class="size-3 fill-blue-500 text-blue-500 dark:fill-blue-400 dark:text-blue-400" />
222+
<span>{{ installation.status_summary.provisioning }} provisioning</span>
223+
</div>
242224
</div>
243225
</TableCell>
244226
<TableCell>{{ formatDate(installation.created_at) }}</TableCell>

0 commit comments

Comments
 (0)