'use client'; import { useEffect, useState } from 'react'; import { useParams } from 'next/navigation'; import { apiFetch } from '@/lib/api'; import { StatusPill } from '@/components/status-pill'; import { CodeBlock } from '@/components/code-block'; import { InstallSnippets } from '@/components/install-snippets'; import { StreamingLogs } from '@/components/streaming-logs'; import { Button } from '@/components/ui/button'; import { Textarea, Label } from '@/components/input'; import { cn } from '@/lib/cn'; import type { ToolSpec } from '@bmm/types'; interface ServerDetail { id: string; name: string; slug: string; status: string; publicUrl: string | null; toolsSchema: ToolSpec[] | null; currentVersion: number; oauthEnabled: boolean; createdAt: string; updatedAt: string; } interface BuildSummary { id: string; version: number; status: string; startedAt: string | null; finishedAt: string | null; errorMessage: string | null; } type Tab = 'overview' | 'tools' | 'logs' | 'metrics' | 'secrets' | 'iterate'; export default function ServerDetailPage() { const params = useParams<{ id: string }>(); const [server, setServer] = useState(null); const [builds, setBuilds] = useState([]); const [tab, setTab] = useState('overview'); const [iteratePrompt, setIteratePrompt] = useState(''); const [latestBuildId, setLatestBuildId] = useState(null); async function refresh() { const r = await apiFetch<{ server: ServerDetail; builds: BuildSummary[] }>( `/v1/servers/${params.id}`, ); setServer(r.server); setBuilds(r.builds); if (r.builds.length > 0 && !latestBuildId) { setLatestBuildId(r.builds[0]!.id); } } useEffect(() => { refresh(); const t = setInterval(refresh, 4000); return () => clearInterval(t); }, [params.id]); async function onIterate() { if (!server) return; const res = await apiFetch<{ build: { id: string } }>( `/v1/servers/${server.id}/iterate`, { method: 'POST', body: JSON.stringify({ prompt: iteratePrompt, secrets: {} }) }, ); setLatestBuildId(res.build.id); setIteratePrompt(''); setTab('logs'); } if (!server) { return (
Loading…
); } const tabs: { id: Tab; label: string }[] = [ { id: 'overview', label: 'Overview' }, { id: 'tools', label: 'Tools' }, { id: 'logs', label: 'Logs' }, { id: 'metrics', label: 'Metrics' }, { id: 'secrets', label: 'Secrets' }, { id: 'iterate', label: 'Iterate' }, ]; return (

{server.name}

{server.slug} · v{server.currentVersion} {server.publicUrl && ( <> · {server.publicUrl}/mcp )}
{tabs.map((t) => ( ))}
{tab === 'overview' && (
Endpoint
{server.publicUrl ? ( ) : (
Not deployed yet.
)}
OAuth 2.1 enforced · Streamable HTTP
Builds
{builds.slice(0, 5).map((b) => (
v{b.version} {b.startedAt ? new Date(b.startedAt).toLocaleString() : '—'}
))}
{server.publicUrl && (
Install
)}
)} {tab === 'tools' && (
{!server.toolsSchema || server.toolsSchema.length === 0 ? (
No tools yet.
) : (
{server.toolsSchema.map((tool) => (
{tool.name}

{tool.description}

{Object.keys(tool.inputSchema ?? {}).length > 0 && (
)}
))}
)}
)} {tab === 'logs' && ( latestBuildId ? ( ) : (
No builds to stream yet.
) )} {tab === 'metrics' && (

Live metrics begin streaming after the first tool call.

)} {tab === 'secrets' && (
Secrets are AES-256-GCM encrypted at rest. They are injected as environment variables into your container and are never echoed back. Use the wizard or CLI to rotate them.
)} {tab === 'iterate' && (