'use client'; import { useEffect, useState } from 'react'; import { apiFetch } from '@/lib/api'; import { Button } from '@/components/ui/button'; import { StatusPill } from '@/components/status-pill'; import { Trash2, RefreshCw } from 'lucide-react'; interface Row { server: { id: string; slug: string; name: string; status: string; currentVersion: number; publicUrl: string | null; hostPort: number | null; createdAt: string; updatedAt: string; }; org: { id: string; name: string; slug: string; plan: string }; } const STATUS_FILTERS = ['', 'live', 'building', 'deploying', 'generating', 'failed', 'paused', 'draft']; export default function AdminServersPage() { const [rows, setRows] = useState(null); const [status, setStatus] = useState(''); async function reload() { const r = await apiFetch<{ servers: Row[] }>( `/v1/admin/servers${status ? `?status=${status}` : ''}`, ); setRows(r.servers); } useEffect(() => { reload(); }, [status]); async function rebuild(s: Row['server']) { if (!confirm(`Trigger a force rebuild of "${s.name}" (v${s.currentVersion} → v${s.currentVersion + 1})?`)) return; try { await apiFetch(`/v1/admin/servers/${s.id}/rebuild`, { method: 'POST' }); reload(); } catch (e) { alert(`Failed: ${(e as Error).message}`); } } async function remove(s: Row['server']) { if (!confirm(`Permanently delete "${s.name}" and stop its container?`)) return; await apiFetch(`/v1/admin/servers/${s.id}`, { method: 'DELETE' }); reload(); } return (

MCP servers

Cross-organization view. Force rebuilds, deletions, status filtering.

{rows === null && (

Loading…

)} {rows && rows.length === 0 && (

No servers.

)} {rows && rows.length > 0 && ( {rows.map((r) => ( ))}
Name Org Status URL Updated Actions
{r.server.name}
{r.server.slug} · v{r.server.currentVersion}
{r.org.name}
{r.org.plan}
{r.server.publicUrl ? ( {r.server.publicUrl} ) : ( '—' )} {new Date(r.server.updatedAt).toLocaleString()}
)}
); }