
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
Anthropic Model Context Protocol (MCP) server registry manager with connection health monitoring, stdio/SSE stream transport metrics, live tool schema explorer, parameterized resource URI templates, and JSON-RPC 2.0 payload inspection drawer.
Also available for React ->$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/mcp-server-registry-manager.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/mcp-server-registry-manager.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/mcp-server-registry-manager.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/mcp-server-registry-manager.jsonnpx shadcn-vue@latest add @uipkge/mcp-server-registry-managerInstalls to:app/components/blocks/| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
initialSearch | string | '' | optional |
initialTransportFilter | 'all''stdio''sse' | 'all' | optional |
initialStatusFilter | 'all''healthy''reconnecting' | 'all' | optional |
initialSelectedToolName | string | — | optional |
initialDrawerOpen | boolean | false | optional |
initialInstallDialogOpen | boolean | false | optional |
initialHandshakeAlert | boolean | false | optional |
initialServers | McpServer[] | — | optional |
Type aliases exported from this item's source. Use these to shape the data you pass in.
ToolParaminterface ToolParam {
name: string
type: 'string' | 'number' | 'boolean' | 'object' | 'array'
required: boolean
description: string
default?: string
enum?: string[]
}ToolSchemainterface ToolSchema {
name: string
displayName: string
description: string
category: 'documentation' | 'database' | 'version-control' | 'deployment' | 'web-search' | 'observability'
parameters: ToolParam[]
sampleRpcRequest: {
jsonrpc: '2.0'
id: string
method: 'tools/call'
params: {
name: string
arguments: Record<string, any>
}
}
sampleRpcResponse: {
jsonrpc: '2.0'
id: string
result: {
content: Array<{
type: 'text' | 'resource' | 'image'
text?: string
resource?: any
}>
isError: boolean
}
}
}ResourceTemplateinterface ResourceTemplate {
uriTemplate: string
name: string
description: string
mimeType: string
}McpServerinterface McpServer {
id: string
name: string
scope: string
version: string
transport: TransportType
commandOrUrl: string
status: ServerStatus
uptime: string
latencyMs: number
callsToday: number
description: string
envVars: string[]
resourceTemplates: ResourceTemplate[]
tools: ToolSchema[]
}<script setup lang="ts">
import { computed, ref } from 'vue'
import type { HTMLAttributes } from 'vue'
import { Check, CheckCircle2, Copy, Plus, Radio, RefreshCw, Search, Server, Terminal, X } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { defaultServers } from './mcp-server-registry-data'
import type { McpServer, ToolSchema } from './mcp-server-registry-types'
import McpHandshakeLogsTable from './McpHandshakeLogsTable.vue'
import McpInstallServerDialog from './McpInstallServerDialog.vue'
import McpKpiCards from './McpKpiCards.vue'
import McpServerCard from './McpServerCard.vue'
import McpToolInspectorDrawer from './McpToolInspectorDrawer.vue'
export type {
McpServer,
ResourceTemplate,
ServerStatus,
ToolParam,
ToolSchema,
TransportType,
} from './mcp-server-registry-types'
interface Props {
class?: HTMLAttributes['class']
initialSearch?: string
initialTransportFilter?: 'all' | 'stdio' | 'sse'
initialStatusFilter?: 'all' | 'healthy' | 'reconnecting'
initialSelectedToolName?: string
initialDrawerOpen?: boolean
initialInstallDialogOpen?: boolean
initialHandshakeAlert?: boolean
initialServers?: McpServer[]
}
const props = withDefaults(defineProps<Props>(), {
initialSearch: '',
initialTransportFilter: 'all',
initialStatusFilter: 'all',
initialDrawerOpen: false,
initialInstallDialogOpen: false,
initialHandshakeAlert: false,
})
// State Management
const servers = ref<McpServer[]>(props.initialServers ? [...props.initialServers] : [...defaultServers])
const searchQuery = ref(props.initialSearch)
const transportFilter = ref<'all' | 'stdio' | 'sse'>(props.initialTransportFilter)
const statusFilter = ref<'all' | 'healthy' | 'reconnecting'>(props.initialStatusFilter)
const isDrawerOpen = ref(props.initialDrawerOpen)
const isInstallDialogOpen = ref(props.initialInstallDialogOpen)
const handshakeAlertVisible = ref(props.initialHandshakeAlert)
const isHandshaking = ref(false)
const copiedKey = ref<string | null>(null)
const inspectorTab = ref<'request' | 'response' | 'schema'>('request')
const activeViewTab = ref<'servers' | 'handshake-logs' | 'config-json'>('servers')
// Inspector state
const selectedServer = ref<McpServer>(servers.value[0] ?? defaultServers[0])
const selectedTool = ref<ToolSchema>(servers.value[0]?.tools[0] ?? defaultServers[0].tools[0])
// Initialize selected tool if provided
if (props.initialSelectedToolName) {
for (const s of servers.value) {
const t = s.tools.find((tool) => tool.name === props.initialSelectedToolName)
if (t) {
selectedServer.value = s
selectedTool.value = t
break
}
}
}
// Dry Run state
const isDryRunning = ref(false)
const dryRunOutput = ref<string | null>(null)
function resetFilters() {
searchQuery.value = ''
transportFilter.value = 'all'
statusFilter.value = 'all'
}
// Filtering Logic
const filteredServers = computed(() => {
const q = searchQuery.value.trim().toLowerCase()
return servers.value.filter((s) => {
const matchTransport = transportFilter.value === 'all' || s.transport === transportFilter.value
const matchStatus = statusFilter.value === 'all' || s.status === statusFilter.value
if (!matchTransport || !matchStatus) return false
if (!q) return true
const inName = s.name.toLowerCase().includes(q)
const inScope = s.scope.toLowerCase().includes(q)
const inDesc = s.description.toLowerCase().includes(q)
const inCmd = s.commandOrUrl.toLowerCase().includes(q)
const inTools = s.tools.some((t) => t.name.toLowerCase().includes(q) || t.displayName.toLowerCase().includes(q))
const inUris = s.resourceTemplates.some((r) => r.uriTemplate.toLowerCase().includes(q))
return inName || inScope || inDesc || inCmd || inTools || inUris
})
})
// Counts & Metrics
const totalConnectedServers = computed(() => servers.value.filter((s) => s.status === 'healthy').length)
const totalRegisteredTools = computed(() => servers.value.reduce((acc, s) => acc + s.tools.length, 0))
const totalResourceTemplates = computed(() => servers.value.reduce((acc, s) => acc + s.resourceTemplates.length, 0))
const totalInvocationsToday = computed(() => servers.value.reduce((acc, s) => acc + s.callsToday, 0))
function openToolInspector(server: McpServer, tool: ToolSchema) {
selectedServer.value = server
selectedTool.value = tool
dryRunOutput.value = null
isDrawerOpen.value = true
}
function openServerInspector(server: McpServer) {
if (server.tools.length > 0) {
openToolInspector(server, server.tools[0])
}
}
function copyToClipboard(text: string, key: string) {
if (typeof navigator !== 'undefined' && navigator.clipboard) {
navigator.clipboard.writeText(text)
copiedKey.value = key
setTimeout(() => {
if (copiedKey.value === key) copiedKey.value = null
}, 2000)
}
}
function runHandshakeTest() {
isHandshaking.value = true
handshakeAlertVisible.value = false
setTimeout(() => {
isHandshaking.value = false
handshakeAlertVisible.value = true
}, 650)
}
function onServerInstalled(newServer: McpServer) {
servers.value.unshift(newServer)
}
function removeServer(serverId: string) {
servers.value = servers.value.filter((s) => s.id !== serverId)
}
function restartServer(server: McpServer) {
server.status = 'healthy'
server.latencyMs = Math.floor(Math.random() * 20) + 15
}
function runDryRunTest() {
if (!selectedTool.value) return
isDryRunning.value = true
setTimeout(() => {
isDryRunning.value = false
dryRunOutput.value = JSON.stringify(selectedTool.value.sampleRpcResponse.result, null, 2)
}, 450)
}
const activeMcpConfigJson = computed(() => {
const config: { mcpServers: Record<string, any> } = { mcpServers: {} }
for (const s of servers.value) {
const key = s.name.replace(/^mcp\//, '')
if (s.transport === 'stdio') {
const parts = s.commandOrUrl.split(' ')
config.mcpServers[key] = {
command: parts[0],
args: parts.slice(1),
env: s.envVars.reduce((acc: Record<string, string>, ev) => {
const [k, v] = ev.split('=')
if (k && v) acc[k] = v
return acc
}, {}),
}
} else {
config.mcpServers[key] = {
transport: 'sse',
url: s.commandOrUrl,
}
}
}
return JSON.stringify(config, null, 2)
})
</script>
<template>
<div
data-slot="mcp-server-registry-manager"
:class="
cn(
'bg-background text-foreground border-border w-full space-y-6 overflow-hidden rounded-xl border p-4 shadow-xs sm:p-6 md:p-8',
props.class,
)
"
>
<!-- Top System Header -->
<header class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div class="space-y-1.5">
<div class="flex flex-wrap items-center gap-2.5">
<div
class="bg-primary/10 text-primary border-primary/20 flex size-9 items-center justify-center rounded-lg border"
>
<Server class="size-4.5" />
</div>
<h1 class="text-xl font-bold tracking-tight sm:text-2xl">Model Context Protocol (MCP) Server Manager</h1>
</div>
<div class="text-muted-foreground flex flex-wrap items-center gap-2 text-xs">
<Badge variant="secondary" class="font-mono text-xs font-normal"> MCP Spec 2024-11-05 · JSON-RPC 2.0 </Badge>
<span>•</span>
<span class="text-success inline-flex items-center gap-1.5 font-medium">
<span class="bg-success flex size-2 animate-pulse rounded-full" />
{{ totalConnectedServers }} Connected · {{ totalRegisteredTools }} Tools Active
</span>
<span class="hidden sm:inline">•</span>
<span class="hidden sm:inline">Anthropic Runtime & Agent Registry</span>
</div>
</div>
<!-- Action CTA Buttons -->
<div class="flex flex-wrap items-center gap-2.5">
<Button
variant="outline"
size="sm"
class="h-9 gap-1.5 text-xs font-medium"
:disabled="isHandshaking"
@click="runHandshakeTest"
>
<RefreshCw :class="cn('size-3.5', isHandshaking && 'text-primary animate-spin')" />
<span>{{ isHandshaking ? 'Handshaking...' : 'Test Tool Handshake' }}</span>
</Button>
<Button size="sm" class="h-9 gap-1.5 text-xs font-medium shadow-xs" @click="isInstallDialogOpen = true">
<Plus class="size-3.5" />
<span>Install New MCP Server</span>
</Button>
</div>
</header>
<!-- Handshake Diagnostic Alert Banner -->
<div
v-if="handshakeAlertVisible"
class="border-success/30 bg-success/10 text-foreground dark:text-foreground flex items-start justify-between rounded-lg border p-3.5 text-xs transition-colors"
>
<div class="flex items-start gap-2.5">
<CheckCircle2 class="text-success mt-0.5 size-4 shrink-0" />
<div class="space-y-0.5">
<p class="text-success font-semibold">Handshake Verified · All 6 MCP Servers Responded (Avg 42ms)</p>
<p class="text-success">
34 tool schemas, 18 resource templates, and prompt interfaces validated against MCP Specification
2024-11-05.
</p>
</div>
</div>
<button
aria-label="Dismiss notification"
class="text-success hover:text-foreground rounded-md p-1 transition-colors"
@click="handshakeAlertVisible = false"
>
<X class="size-3.5" />
</button>
</div>
<!-- 4 MCP System KPI Cards -->
<McpKpiCards
:servers-count="servers.length"
:healthy-count="totalConnectedServers"
:reconnecting-count="servers.length - totalConnectedServers"
:tools-count="totalRegisteredTools"
:resource-templates-count="totalResourceTemplates"
:invocations-today="totalInvocationsToday"
/>
<!-- Main Content Navigation & Filter Bar -->
<div class="space-y-4">
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<!-- View Tabs -->
<Tabs v-model="activeViewTab" class="w-full md:w-auto">
<TabsList class="grid h-9 w-full max-w-[380px] grid-cols-3 md:w-full">
<TabsTrigger value="servers" class="text-xs"> Servers & Tools ({{ servers.length }}) </TabsTrigger>
<TabsTrigger value="handshake-logs" class="text-xs"> Handshake Logs </TabsTrigger>
<TabsTrigger value="config-json" class="text-xs"> Raw JSON Config </TabsTrigger>
</TabsList>
</Tabs>
<!-- Search Bar and Transport Filters -->
<div class="flex flex-wrap items-center gap-2">
<div class="relative w-full sm:w-64">
<Search class="text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2" />
<Input
v-model="searchQuery"
placeholder="Search servers, tools, URIs..."
class="bg-background h-8.5 pl-8 text-xs"
/>
</div>
<!-- Transport Filter Buttons -->
<div class="border-border bg-muted/40 flex items-center rounded-lg border p-0.5 text-xs">
<button
:class="
cn(
'rounded-md px-2.5 py-1 font-medium transition-colors',
transportFilter === 'all'
? 'bg-background text-foreground shadow-xs'
: 'text-muted-foreground hover:text-foreground',
)
"
@click="transportFilter = 'all'"
>
All
</button>
<button
:class="
cn(
'inline-flex items-center gap-1 rounded-md px-2.5 py-1 font-medium transition-colors',
transportFilter === 'stdio'
? 'bg-background text-foreground shadow-xs'
: 'text-muted-foreground hover:text-foreground',
)
"
@click="transportFilter = 'stdio'"
>
<Terminal class="size-3" />
stdio
</button>
<button
:class="
cn(
'inline-flex items-center gap-1 rounded-md px-2.5 py-1 font-medium transition-colors',
transportFilter === 'sse'
? 'bg-background text-foreground shadow-xs'
: 'text-muted-foreground hover:text-foreground',
)
"
@click="transportFilter = 'sse'"
>
<Radio class="size-3" />
SSE Stream
</button>
</div>
</div>
</div>
<!-- Tab View 1: Servers & Tools List -->
<div v-if="activeViewTab === 'servers'" class="space-y-4">
<!-- Empty State -->
<div
v-if="filteredServers.length === 0"
class="border-border bg-card/40 flex flex-col items-center justify-center rounded-xl border border-dashed p-10 text-center"
>
<Server class="text-muted-foreground/60 mb-3 size-10" />
<h3 class="text-sm font-semibold">No MCP Servers Found</h3>
<p class="text-muted-foreground mt-1 max-w-sm text-xs">
No servers match your current search query or filter criteria. Clear filters or add a new MCP server
connection.
</p>
<Button size="sm" variant="outline" class="mt-4 h-8 text-xs" @click="resetFilters"> Reset Filters </Button>
</div>
<!-- Servers Cards -->
<McpServerCard
v-for="server in filteredServers"
:key="server.id"
:server="server"
:copied-key="copiedKey"
@open-tool-inspector="openToolInspector(server, $event)"
@open-server-inspector="openServerInspector(server)"
@restart-server="restartServer(server)"
@remove-server="removeServer(server.id)"
@copy="copyToClipboard"
/>
</div>
<!-- Tab View 2: Handshake & Telemetry Logs -->
<McpHandshakeLogsTable
v-else-if="activeViewTab === 'handshake-logs'"
:servers="servers"
:is-handshaking="isHandshaking"
@re-probe="runHandshakeTest"
/>
<!-- Tab View 3: Raw JSON Configuration -->
<div v-else-if="activeViewTab === 'config-json'" class="space-y-4">
<Card class="border-border bg-card">
<CardHeader class="pb-3">
<div class="flex items-center justify-between">
<div>
<CardTitle class="text-sm font-bold">Claude Desktop & Cursor MCP Config</CardTitle>
<CardDescription class="text-xs">
Exportable JSON format compatible with <code class="font-mono">claude_desktop_config.json</code>.
</CardDescription>
</div>
<Button
size="sm"
variant="outline"
class="h-8 gap-1.5 text-xs"
@click="copyToClipboard(activeMcpConfigJson, 'mcp-config-json')"
>
<Check v-if="copiedKey === 'mcp-config-json'" class="text-success size-3.5" />
<Copy v-else class="size-3.5" />
{{ copiedKey === 'mcp-config-json' ? 'JSON Copied!' : 'Copy Config JSON' }}
</Button>
</div>
</CardHeader>
<CardContent>
<pre
class="bg-muted/60 border-border text-foreground max-h-[420px] overflow-x-auto rounded-lg border p-4 font-mono text-xs leading-relaxed"
>{{ activeMcpConfigJson }}</pre
>
</CardContent>
</Card>
</div>
</div>
<!-- Tool Schema Inspection Drawer (Sheet) -->
<McpToolInspectorDrawer
:open="isDrawerOpen"
:server="selectedServer"
:tool="selectedTool"
:inspector-tab="inspectorTab"
:copied-key="copiedKey"
:is-dry-running="isDryRunning"
:dry-run-output="dryRunOutput"
@update:open="isDrawerOpen = $event"
@update:inspector-tab="inspectorTab = $event"
@copy="copyToClipboard"
@run-dry-run="runDryRunTest"
/>
<!-- Install New MCP Server Dialog -->
<McpInstallServerDialog v-model:open="isInstallDialogOpen" @install="onServerInstalled" />
</div>
</template>
<script setup lang="ts">
import { RefreshCw } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import type { McpServer } from './mcp-server-registry-types'
interface Props {
servers: McpServer[]
isHandshaking: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
reProbe: []
}>()
</script>
<template>
<div class="space-y-4">
<Card class="border-border bg-card">
<CardHeader class="pb-3">
<div class="flex items-center justify-between">
<div>
<CardTitle class="text-sm font-bold">MCP Handshake & Protocol Event Stream</CardTitle>
<CardDescription class="text-xs">
Real-time JSON-RPC 2.0 handshake pings and capability negotiation logs.
</CardDescription>
</div>
<Button size="sm" variant="outline" class="h-8 gap-1.5 text-xs" @click="emit('reProbe')">
<RefreshCw :class="cn('size-3.5', isHandshaking && 'animate-spin')" />
Re-probe Connections
</Button>
</div>
</CardHeader>
<CardContent class="p-0">
<div class="overflow-x-auto">
<Table>
<TableHeader>
<TableRow class="hover:bg-transparent">
<TableHead class="text-xs">Server / Scope</TableHead>
<TableHead class="text-xs">Transport</TableHead>
<TableHead class="text-xs">Handshake Status</TableHead>
<TableHead class="text-xs">Latency</TableHead>
<TableHead class="text-xs">Tools Validated</TableHead>
<TableHead class="text-right text-xs">Last Ping</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="s in servers" :key="s.id" class="text-xs">
<TableCell class="font-mono font-medium">
{{ s.name }}
</TableCell>
<TableCell>
<Badge variant="secondary" class="font-mono text-xs">
{{ s.transport }}
</Badge>
</TableCell>
<TableCell>
<span
:class="
cn(
'inline-flex items-center gap-1.5 font-medium',
s.status === 'healthy' ? 'text-success' : 'text-warning',
)
"
>
<span :class="cn('size-1.5 rounded-full', s.status === 'healthy' ? 'bg-success' : 'bg-warning')" />
{{ s.status === 'healthy' ? 'SYN_ACK 200 OK' : 'RETRY_BACKOFF' }}
</span>
</TableCell>
<TableCell class="text-muted-foreground font-mono"> {{ s.latencyMs }}ms </TableCell>
<TableCell>
<span class="text-foreground font-medium">{{ s.tools.length }} schemas</span>
</TableCell>
<TableCell class="text-muted-foreground text-right"> Just now </TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Plus, Radio, Terminal } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import type { McpServer, TransportType } from './mcp-server-registry-types'
interface Props {
open: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
'update:open': [value: boolean]
install: [server: McpServer]
}>()
const newServerName = ref('')
const newServerScope = ref('')
const newTransport = ref<TransportType>('stdio')
const newCommandOrUrl = ref('')
const newDescription = ref('')
function handleAddServer() {
if (!newServerName.value.trim() || !newCommandOrUrl.value.trim()) return
const newServer: McpServer = {
id: `server-${Date.now()}`,
name: newServerName.value.trim(),
scope: newServerScope.value.trim() || `@custom/${newServerName.value.replace(/^mcp\//, '')}`,
version: 'v1.0.0',
transport: newTransport.value,
commandOrUrl: newCommandOrUrl.value.trim(),
status: 'healthy',
uptime: '100.0%',
latencyMs: newTransport.value === 'stdio' ? 22 : 64,
callsToday: 0,
description: newDescription.value.trim() || 'Custom registered Model Context Protocol runtime server.',
envVars: ['MCP_ENABLED=true'],
resourceTemplates: [
{
uriTemplate: `custom://${newServerName.value.replace(/^mcp\//, '')}/{resourceId}`,
name: 'Custom Parameterized Resource',
description: 'Dynamic schema endpoint for custom tool payloads',
mimeType: 'application/json',
},
],
tools: [
{
name: `${newServerName.value.replace(/^mcp\//, '').replace(/[^a-zA-Z0-9_]/g, '_')}_action`,
displayName: 'Execute Custom Action',
description: 'Default action handler registered for this server.',
category: 'deployment',
parameters: [{ name: 'payload', type: 'string', required: true, description: 'Action parameter payload.' }],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-custom-001',
method: 'tools/call',
params: {
name: `${newServerName.value.replace(/^mcp\//, '').replace(/[^a-zA-Z0-9_]/g, '_')}_action`,
arguments: { payload: 'execute' },
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-custom-001',
result: {
content: [{ type: 'text', text: '{"status":"success","executedAt":"2026-08-21T12:00:00Z"}' }],
isError: false,
},
},
},
],
}
emit('install', newServer)
newServerName.value = ''
newServerScope.value = ''
newCommandOrUrl.value = ''
newDescription.value = ''
emit('update:open', false)
}
</script>
<template>
<Dialog :open="open" @update:open="emit('update:open', $event)">
<DialogContent class="sm:max-w-lg">
<DialogHeader class="space-y-1.5">
<DialogTitle class="flex items-center gap-2 text-base font-bold">
<Plus class="text-primary size-4" />
Install New MCP Server
</DialogTitle>
<DialogDescription class="text-muted-foreground text-xs">
Connect an Anthropic Model Context Protocol compliant local command or remote SSE stream.
</DialogDescription>
</DialogHeader>
<div class="space-y-3.5 py-2">
<div class="space-y-1">
<label class="text-foreground text-xs font-medium">Server Identifier</label>
<Input v-model="newServerName" placeholder="e.g. mcp/custom-inspector" class="h-8.5 font-mono text-xs" />
</div>
<div class="space-y-1">
<label class="text-foreground text-xs font-medium">Package Scope or Organization</label>
<Input v-model="newServerScope" placeholder="e.g. @org/mcp-server-custom" class="h-8.5 font-mono text-xs" />
</div>
<div class="space-y-1.5">
<label class="text-foreground text-xs font-medium">Transport Type</label>
<div class="grid grid-cols-2 gap-2">
<button
type="button"
:class="
cn(
'flex items-center gap-2 rounded-lg border p-2.5 text-left text-xs transition-colors',
newTransport === 'stdio'
? 'border-primary bg-primary/10 text-foreground font-semibold'
: 'border-border bg-card text-muted-foreground hover:bg-muted',
)
"
@click="newTransport = 'stdio'"
>
<Terminal class="text-primary size-4 shrink-0" />
<div>
<div class="text-foreground font-medium">stdio Subprocess</div>
<div class="text-muted-foreground text-xs">Local executable</div>
</div>
</button>
<button
type="button"
:class="
cn(
'flex items-center gap-2 rounded-lg border p-2.5 text-left text-xs transition-colors',
newTransport === 'sse'
? 'border-primary bg-primary/10 text-foreground font-semibold'
: 'border-border bg-card text-muted-foreground hover:bg-muted',
)
"
@click="newTransport = 'sse'"
>
<Radio class="text-info size-4 shrink-0" />
<div>
<div class="text-foreground font-medium">SSE HTTP Stream</div>
<div class="text-muted-foreground text-xs">Remote gateway</div>
</div>
</button>
</div>
</div>
<div class="space-y-1">
<label class="text-foreground text-xs font-medium">
{{ newTransport === 'stdio' ? 'Command Line Entrypoint' : 'SSE Gateway URL' }}
</label>
<Input
v-model="newCommandOrUrl"
:placeholder="
newTransport === 'stdio'
? 'npx -y @org/mcp-server --config=prod'
: 'https://mcp.internal.company.com/sse/v1'
"
class="h-8.5 font-mono text-xs"
/>
</div>
<div class="space-y-1">
<label class="text-foreground text-xs font-medium">Server Description</label>
<Input
v-model="newDescription"
placeholder="Short summary of tools and capabilities provided by this server"
class="h-8.5 text-xs"
/>
</div>
</div>
<DialogFooter class="gap-2 sm:gap-0">
<DialogClose asChild>
<Button variant="outline" class="h-8.5 text-xs"> Cancel </Button>
</DialogClose>
<Button
class="h-8.5 text-xs"
:disabled="!newServerName.trim() || !newCommandOrUrl.trim()"
@click="handleAddServer"
>
Register Server
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>
<script setup lang="ts">
import { Activity, Boxes, Link2, Server, ShieldCheck, Wrench, Zap } from 'lucide-vue-next'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
interface Props {
serversCount: number
healthyCount: number
reconnectingCount: number
toolsCount: number
resourceTemplatesCount: number
invocationsToday: number
}
defineProps<Props>()
</script>
<template>
<!-- 4 MCP System KPI Cards -->
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
<!-- KPI 1: Connected MCP Servers -->
<Card class="border-border bg-card shadow-xs">
<CardHeader class="flex flex-row items-center justify-between pb-2">
<CardTitle class="text-muted-foreground text-xs font-medium tracking-wider uppercase">
Connected Servers
</CardTitle>
<div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md">
<Server class="size-3.5" />
</div>
</CardHeader>
<CardContent class="space-y-1">
<div class="text-2xl font-bold tracking-tight">{{ serversCount }} Active Servers</div>
<div class="text-muted-foreground flex items-center gap-1.5 text-xs">
<span class="bg-success inline-block size-1.5 rounded-full" />
<span>{{ healthyCount }} healthy</span>
<span>•</span>
<span class="text-warning">{{ reconnectingCount }} reconnecting</span>
</div>
</CardContent>
</Card>
<!-- KPI 2: Registered Tools -->
<Card class="border-border bg-card shadow-xs">
<CardHeader class="flex flex-row items-center justify-between pb-2">
<CardTitle class="text-muted-foreground text-xs font-medium tracking-wider uppercase">
Registered Tools
</CardTitle>
<div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md">
<Wrench class="size-3.5" />
</div>
</CardHeader>
<CardContent class="space-y-1">
<div class="text-2xl font-bold tracking-tight">{{ toolsCount }} Tool Functions</div>
<div class="text-success flex items-center gap-1.5 text-xs">
<ShieldCheck class="size-3.5" />
<span>100% Schema Validated</span>
</div>
</CardContent>
</Card>
<!-- KPI 3: Resource Templates -->
<Card class="border-border bg-card shadow-xs">
<CardHeader class="flex flex-row items-center justify-between pb-2">
<CardTitle class="text-muted-foreground text-xs font-medium tracking-wider uppercase">
Resource Templates
</CardTitle>
<div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md">
<Link2 class="size-3.5" />
</div>
</CardHeader>
<CardContent class="space-y-1">
<div class="text-2xl font-bold tracking-tight">{{ resourceTemplatesCount }} Parameterized URIs</div>
<div class="text-muted-foreground flex items-center gap-1.5 text-xs">
<Boxes class="size-3.5" />
<span>Dynamic URI Subscriptions</span>
</div>
</CardContent>
</Card>
<!-- KPI 4: Tool Invocation Volume -->
<Card class="border-border bg-card shadow-xs">
<CardHeader class="flex flex-row items-center justify-between pb-2">
<CardTitle class="text-muted-foreground text-xs font-medium tracking-wider uppercase">
Invocation Volume
</CardTitle>
<div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md">
<Zap class="size-3.5" />
</div>
</CardHeader>
<CardContent class="space-y-1">
<div class="text-2xl font-bold tracking-tight">{{ invocationsToday.toLocaleString() }} calls today</div>
<div class="text-muted-foreground flex items-center gap-1.5 text-xs">
<Activity class="text-success size-3.5" />
<span class="text-foreground font-medium">99.8%</span> success · 142ms avg
</div>
</CardContent>
</Card>
</div>
</template>
<script setup lang="ts">
import {
Braces,
Check,
Code2,
Copy,
FileCode,
Link2,
MoreHorizontal,
Radio,
RefreshCw,
Terminal,
Trash2,
} from 'lucide-vue-next'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import type { McpServer, ToolSchema } from './mcp-server-registry-types'
interface Props {
server: McpServer
copiedKey: string | null
}
const props = defineProps<Props>()
const emit = defineEmits<{
openToolInspector: [tool: ToolSchema]
openServerInspector: []
restartServer: []
removeServer: []
copy: [text: string, key: string]
}>()
</script>
<template>
<div
class="border-border bg-card hover:border-border/80 space-y-3.5 rounded-xl border p-4 shadow-xs transition-colors"
>
<!-- Server Header Line -->
<div class="flex flex-col gap-2.5 sm:flex-row sm:items-center sm:justify-between">
<div class="flex flex-wrap items-center gap-2.5">
<div class="bg-muted text-foreground border-border flex size-8 items-center justify-center rounded-lg border">
<Terminal v-if="server.transport === 'stdio'" class="size-4" />
<Radio v-else class="text-info size-4" />
</div>
<div>
<div class="flex items-center gap-2">
<span class="text-foreground font-mono text-sm font-semibold tracking-tight">
{{ server.name }}
</span>
<Badge variant="secondary" class="font-mono text-xs font-normal">
{{ server.version }}
</Badge>
<Badge :variant="server.transport === 'stdio' ? 'outline' : 'secondary'" class="gap-1 font-mono text-xs">
<Terminal v-if="server.transport === 'stdio'" class="size-3" />
<Radio v-else class="text-info size-3" />
{{ server.transport === 'stdio' ? 'stdio' : 'SSE stream' }}
</Badge>
</div>
<p class="text-muted-foreground mt-0.5 text-xs">{{ server.scope }} · {{ server.description }}</p>
</div>
</div>
<!-- Status and Dropdown Actions -->
<div class="flex items-center gap-2 sm:self-center">
<Badge
v-if="server.status === 'healthy'"
variant="outline"
class="border-success/40 bg-success/10 text-success gap-1.5 text-xs"
>
<span class="bg-success size-1.5 rounded-full" />
Connected & Healthy
</Badge>
<Badge v-else variant="outline" class="border-warning/40 bg-warning/10 text-warning gap-1.5 text-xs">
<span class="bg-warning size-1.5 rounded-full" />
Reconnecting ({{ server.latencyMs }}ms)
</Badge>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" class="size-8">
<MoreHorizontal class="size-4" />
<span class="sr-only">Server Options</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-48 text-xs">
<DropdownMenuLabel>Server Actions</DropdownMenuLabel>
<DropdownMenuItem @click="emit('openServerInspector')">
<FileCode class="mr-2 size-3.5" />
Inspect Tool Schema
</DropdownMenuItem>
<DropdownMenuItem @click="emit('restartServer')">
<RefreshCw class="mr-2 size-3.5" />
Restart Process
</DropdownMenuItem>
<DropdownMenuItem @click="emit('copy', server.commandOrUrl, `cmd-${server.id}`)">
<Copy class="mr-2 size-3.5" />
{{ copiedKey === `cmd-${server.id}` ? 'Copied Command!' : 'Copy Command' }}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem class="text-destructive focus:text-destructive" @click="emit('removeServer')">
<Trash2 class="mr-2 size-3.5" />
Disconnect Server
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<!-- Command or Endpoint snippet -->
<div
class="bg-muted/60 border-border flex items-center justify-between rounded-lg border px-3 py-2 font-mono text-xs"
>
<div class="flex items-center gap-2 overflow-x-auto">
<span class="text-muted-foreground select-none">$</span>
<span class="text-foreground truncate">{{ server.commandOrUrl }}</span>
</div>
<Button
aria-label="Copy server command"
variant="ghost"
size="icon"
class="ml-2 size-6 shrink-0"
@click="emit('copy', server.commandOrUrl, `cmd-btn-${server.id}`)"
>
<Check v-if="copiedKey === `cmd-btn-${server.id}`" class="text-success size-3" />
<Copy v-else class="text-muted-foreground size-3" />
</Button>
</div>
<!-- Resource Templates List -->
<div v-if="server.resourceTemplates.length > 0" class="space-y-1.5">
<div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium">
<Link2 class="size-3" />
<span>Resource URI Templates:</span>
</div>
<div class="flex flex-wrap items-center gap-1.5">
<div
v-for="res in server.resourceTemplates"
:key="res.uriTemplate"
class="bg-muted/40 border-border text-foreground hover:bg-muted inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 font-mono text-xs transition-colors"
:title="res.description"
>
<span class="text-primary font-medium">{{ res.uriTemplate }}</span>
<span class="text-muted-foreground font-sans text-xs">({{ res.name }})</span>
</div>
</div>
</div>
<!-- Registered Tool Badges & Inspector Triggers -->
<div class="flex flex-col gap-2 pt-1 sm:flex-row sm:items-center sm:justify-between">
<div class="flex flex-wrap items-center gap-1.5">
<span class="text-muted-foreground mr-1 text-xs font-medium"> Tools ({{ server.tools.length }}): </span>
<button
v-for="tool in server.tools"
:key="tool.name"
class="border-primary/30 bg-primary/5 hover:bg-primary/15 text-primary focus-visible:ring-ring inline-flex cursor-pointer items-center gap-1 rounded-md border px-2 py-1 font-mono text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-hidden"
@click="emit('openToolInspector', tool)"
>
<Code2 class="size-3" />
<span>{{ tool.name }}</span>
</button>
</div>
<!-- Inspect Action Button -->
<div class="flex items-center gap-2">
<span class="text-muted-foreground hidden text-xs lg:inline">
{{ server.callsToday.toLocaleString() }} calls · {{ server.latencyMs }}ms ping
</span>
<Button variant="outline" size="sm" class="h-7.5 gap-1 text-xs" @click="emit('openServerInspector')">
<Braces class="size-3.5" />
<span>Inspect Tool JSON Schema</span>
</Button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Check, Code2, Copy, Play, RefreshCw } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import type { McpServer, ToolSchema } from './mcp-server-registry-types'
interface Props {
open: boolean
server: McpServer
tool: ToolSchema
inspectorTab: 'request' | 'response' | 'schema'
copiedKey: string | null
isDryRunning: boolean
dryRunOutput: string | null
}
const props = defineProps<Props>()
const emit = defineEmits<{
'update:open': [value: boolean]
'update:inspector-tab': [value: 'request' | 'response' | 'schema']
copy: [text: string, key: string]
runDryRun: []
}>()
function getToolSchemaDraft7(tool: ToolSchema) {
const properties: Record<string, any> = {}
const required: string[] = []
for (const p of tool.parameters) {
properties[p.name] = {
type: p.type,
description: p.description,
}
if (p.default !== undefined) properties[p.name].default = p.default
if (p.enum) properties[p.name].enum = p.enum
if (p.required) required.push(p.name)
}
return {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
properties,
required,
additionalProperties: false,
}
}
function handleCopySchema() {
const tool = props.tool
if (!tool) return
const text =
props.inspectorTab === 'request'
? JSON.stringify(tool.sampleRpcRequest, null, 2)
: props.inspectorTab === 'response'
? JSON.stringify(tool.sampleRpcResponse, null, 2)
: JSON.stringify(getToolSchemaDraft7(tool), null, 2)
emit('copy', text, 'drawer-copied-schema')
}
</script>
<template>
<Sheet :open="open" @update:open="$emit('update:open', $event)">
<SheetContent class="w-full space-y-6 overflow-y-auto p-6 sm:max-w-xl md:max-w-2xl">
<SheetHeader class="space-y-2">
<div class="flex flex-wrap items-center gap-2">
<Badge variant="secondary" class="font-mono text-xs font-medium">
{{ server?.name }}
</Badge>
<Badge variant="outline" class="font-mono text-xs">
{{ server?.transport }}
</Badge>
<Badge class="bg-primary/10 text-primary border-primary/20 text-xs capitalize" variant="outline">
{{ tool?.category }}
</Badge>
</div>
<SheetTitle class="text-foreground flex items-center gap-2 font-mono text-lg font-bold tracking-tight">
<Code2 class="text-primary size-5" />
<span>{{ tool?.name }}</span>
</SheetTitle>
<SheetDescription class="text-muted-foreground text-xs leading-relaxed">
{{ tool?.description }}
</SheetDescription>
</SheetHeader>
<Separator />
<!-- Parameters Specification Table -->
<div class="space-y-3">
<div class="flex items-center justify-between">
<h4 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
Parameter Schema ({{ tool?.parameters.length }})
</h4>
<span class="text-muted-foreground font-mono text-xs">JSON-RPC 2.0 Method</span>
</div>
<div class="border-border overflow-hidden rounded-lg border">
<div class="overflow-x-auto">
<Table>
<TableHeader class="bg-muted/40">
<TableRow class="hover:bg-transparent">
<TableHead class="text-xs font-semibold">Parameter</TableHead>
<TableHead class="text-xs font-semibold">Type</TableHead>
<TableHead class="text-xs font-semibold">Required</TableHead>
<TableHead class="text-xs font-semibold">Description</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="param in tool?.parameters" :key="param.name" class="text-xs">
<TableCell class="text-foreground font-mono font-medium">
{{ param.name }}
</TableCell>
<TableCell>
<Badge variant="secondary" class="font-mono text-xs">
{{ param.type }}
</Badge>
</TableCell>
<TableCell>
<Badge
:variant="param.required ? 'default' : 'outline'"
:class="
cn(
'text-xs font-medium',
param.required
? 'border-destructive/30 bg-destructive/10 text-destructive'
: 'text-muted-foreground',
)
"
>
{{ param.required ? 'Required' : 'Optional' }}
</Badge>
</TableCell>
<TableCell class="text-muted-foreground">
<div>{{ param.description }}</div>
<div v-if="param.default" class="text-foreground/80 mt-0.5 font-mono text-xs">
default: <span class="text-primary">{{ param.default }}</span>
</div>
<div v-if="param.enum" class="text-foreground/80 mt-0.5 font-mono text-xs">
enum: [{{ param.enum.join(', ') }}]
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</div>
</div>
<!-- Tabbed JSON-RPC Schema & Payloads -->
<div class="space-y-3">
<Tabs
:model-value="inspectorTab"
@update:model-value="$emit('update:inspector-tab', $event as 'request' | 'response' | 'schema')"
class="w-full"
>
<div class="mb-2 flex items-center justify-between">
<TabsList class="h-8">
<TabsTrigger value="request" class="text-xs">Sample Request</TabsTrigger>
<TabsTrigger value="response" class="text-xs">Sample Response</TabsTrigger>
<TabsTrigger value="schema" class="text-xs">Draft-07 Schema</TabsTrigger>
</TabsList>
<Button variant="ghost" size="sm" class="h-7 gap-1 text-xs" @click="handleCopySchema">
<Check v-if="copiedKey === 'drawer-copied-schema'" class="text-success size-3" />
<Copy v-else class="size-3" />
{{ copiedKey === 'drawer-copied-schema' ? 'Copied!' : 'Copy Code' }}
</Button>
</div>
<!-- Tab 1: Request -->
<TabsContent value="request" class="m-0">
<pre
class="bg-muted/60 border-border text-foreground max-h-56 overflow-x-auto rounded-lg border p-3.5 font-mono text-xs leading-relaxed"
>{{ JSON.stringify(tool?.sampleRpcRequest, null, 2) }}</pre
>
</TabsContent>
<!-- Tab 2: Response -->
<TabsContent value="response" class="m-0">
<pre
class="bg-muted/60 border-border text-foreground max-h-56 overflow-x-auto rounded-lg border p-3.5 font-mono text-xs leading-relaxed"
>{{ JSON.stringify(tool?.sampleRpcResponse, null, 2) }}</pre
>
</TabsContent>
<!-- Tab 3: Draft-07 Schema -->
<TabsContent value="schema" class="m-0">
<pre
class="bg-muted/60 border-border text-foreground max-h-56 overflow-x-auto rounded-lg border p-3.5 font-mono text-xs leading-relaxed"
>{{ JSON.stringify(getToolSchemaDraft7(tool), null, 2) }}</pre
>
</TabsContent>
</Tabs>
</div>
<!-- Dry Run Simulator -->
<div class="border-border bg-muted/20 space-y-3 rounded-lg border p-3.5">
<div class="flex items-center justify-between">
<span class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
<Play class="text-primary size-3.5" />
Simulate Tool Invocation
</span>
<Button size="sm" class="h-7.5 gap-1 text-xs" :disabled="isDryRunning" @click="$emit('runDryRun')">
<RefreshCw :class="cn('size-3', isDryRunning && 'animate-spin')" />
{{ isDryRunning ? 'Invoking RPC...' : 'Test Dry-Run Call' }}
</Button>
</div>
<div v-if="dryRunOutput" class="space-y-1">
<span class="text-success font-mono text-xs"> ✓ Invocation 200 OK (32ms): </span>
<pre
class="bg-muted/80 border-border text-foreground max-h-36 overflow-x-auto rounded border p-2.5 font-mono text-xs leading-tight"
>{{ dryRunOutput }}</pre
>
</div>
</div>
<SheetFooter class="pt-2">
<SheetClose asChild>
<Button variant="outline" class="h-8.5 w-full text-xs sm:w-auto"> Close Inspector </Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
</template>
import type { McpServer } from './mcp-server-registry-types'
export const defaultServers: McpServer[] = [
{
id: 'server-ctx-1',
name: 'mcp/context7-docs',
scope: '@context7/mcp-server',
version: 'v1.4.2',
transport: 'stdio',
commandOrUrl: 'npx -y @context7/mcp-server',
status: 'healthy',
uptime: '99.98%',
latencyMs: 18,
callsToday: 4120,
description: 'Context-aware documentation retriever and code snippet indexer for public libraries and frameworks.',
envVars: ['CONTEXT7_API_KEY=c7_live_***', 'CACHE_TTL=3600'],
resourceTemplates: [
{
uriTemplate: 'docs://{library}/{version}',
name: 'Library Documentation AST',
description: 'Direct access to indexed AST documentation, type exports, and code snippets',
mimeType: 'application/json',
},
{
uriTemplate: 'cheatsheets://{framework}/api-reference',
name: 'Framework Cheatsheet',
description: 'High-density API method cheat sheets and migration summaries',
mimeType: 'text/markdown',
},
],
tools: [
{
name: 'query_docs',
displayName: 'Query Library Docs',
description:
'Retrieves up-to-date documentation and code examples from Context7 for any programming library or framework.',
category: 'documentation',
parameters: [
{
name: 'libraryId',
type: 'string',
required: true,
description: 'Exact Context7-compatible library ID (e.g. /tailwindlabs/tailwindcss or /unovue/reka-ui).',
},
{
name: 'query',
type: 'string',
required: true,
description: 'What to look up in the library documentation, scoped to a single concept.',
},
{
name: 'limit',
type: 'number',
required: false,
default: '5',
description: 'Maximum number of relevant code snippets to return (1-20).',
},
{
name: 'includeSnippets',
type: 'boolean',
required: false,
default: 'true',
description: 'Whether to include verified code snippet examples in markdown format.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-ctx-001',
method: 'tools/call',
params: {
name: 'query_docs',
arguments: {
libraryId: '/tailwindlabs/tailwindcss',
query: 'CSS variables and OKLCH color palettes v4',
limit: 3,
includeSnippets: true,
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-ctx-001',
result: {
content: [
{
type: 'text',
text: 'Tailwind CSS v4 introduces native CSS-first theme configuration using `@theme` and OKLCH color functions without javascript config files.',
},
],
isError: false,
},
},
},
{
name: 'resolve_library_id',
displayName: 'Resolve Library ID',
description:
'Resolves a package or product name to a Context7-compatible library ID and returns matching ranked libraries.',
category: 'documentation',
parameters: [
{
name: 'libraryName',
type: 'string',
required: true,
description: 'Library name to search for (e.g. Next.js, Reka UI, Lucide).',
},
{
name: 'ecosystem',
type: 'string',
required: false,
default: 'npm',
enum: ['npm', 'pypi', 'crates', 'go', 'gem'],
description: 'Package registry ecosystem to prioritize during identifier resolution.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-ctx-002',
method: 'tools/call',
params: {
name: 'resolve_library_id',
arguments: {
libraryName: 'reka-ui',
ecosystem: 'npm',
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-ctx-002',
result: {
content: [
{
type: 'text',
text: '{"libraryId":"/unovue/reka-ui","name":"Reka UI","description":"Headless UI primitives for Vue 3.5","reputation":"High","benchmarkScore":99}',
},
],
isError: false,
},
},
},
],
},
{
id: 'server-pg-2',
name: 'mcp/postgres-inspector',
scope: '@modelcontextprotocol/server-postgres',
version: 'v0.9.1',
transport: 'stdio',
commandOrUrl: 'npx -y @modelcontextprotocol/server-postgres postgresql://app_user:***@db.internal:5432/prod_main',
status: 'healthy',
uptime: '99.95%',
latencyMs: 24,
callsToday: 3450,
description:
'Direct PostgreSQL read-only query execution, EXPLAIN execution plan inspector, and live schema reflection.',
envVars: ['PGSSLMODE=require', 'MAX_CONNECTIONS=10'],
resourceTemplates: [
{
uriTemplate: 'postgres://{schema}/{table}',
name: 'Table Schema Definition',
description: 'PostgreSQL table column constraints, foreign keys, row count estimations, and B-Tree indexes',
mimeType: 'application/json',
},
{
uriTemplate: 'postgres://metrics/slow-queries',
name: 'PgStatStatements Log Buffer',
description: 'Aggregated query execution statistics and cache hit ratios',
mimeType: 'application/json',
},
],
tools: [
{
name: 'execute_sql',
displayName: 'Execute Read-Only SQL',
description:
'Executes read-only SQL queries against Postgres connection with safe execution timeout safeguards.',
category: 'database',
parameters: [
{
name: 'query',
type: 'string',
required: true,
description: 'SQL statement (SELECT only) to execute on the cluster.',
},
{
name: 'readOnly',
type: 'boolean',
required: false,
default: 'true',
description: 'Enforces transaction read-only flag before executing SQL.',
},
{
name: 'timeoutMs',
type: 'number',
required: false,
default: '5000',
description: 'Statement cancellation timeout in milliseconds.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-pg-001',
method: 'tools/call',
params: {
name: 'execute_sql',
arguments: {
query: 'SELECT id, email, role, created_at FROM public.users WHERE is_active = true LIMIT 5;',
readOnly: true,
timeoutMs: 5000,
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-pg-001',
result: {
content: [
{
type: 'text',
text: '{"rows":[{"id":"usr_8492","email":"[email protected]","role":"admin","created_at":"2026-02-14T08:30:00Z"}],"rowCount":1,"durationMs":14.2}',
},
],
isError: false,
},
},
},
{
name: 'explain_query',
displayName: 'Explain Query Plan',
description: 'Runs EXPLAIN ANALYZE on query plans to detect slow sequential scans and missing indexes.',
category: 'database',
parameters: [
{
name: 'query',
type: 'string',
required: true,
description: 'SQL query to analyze execution graph and node timings.',
},
{
name: 'analyze',
type: 'boolean',
required: false,
default: 'true',
description: 'Execute statement to measure actual wall-clock row counts and buffer usage.',
},
{
name: 'format',
type: 'string',
required: false,
default: 'json',
enum: ['json', 'text', 'yaml'],
description: 'Postgres execution plan output serialization format.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-pg-002',
method: 'tools/call',
params: {
name: 'explain_query',
arguments: {
query: 'SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC;',
analyze: true,
format: 'json',
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-pg-002',
result: {
content: [
{
type: 'text',
text: '{"Plan":{"Node Type":"Index Scan","Index Name":"idx_orders_user_id","Startup Cost":0.42,"Total Cost":8.44,"Plan Rows":1,"Actual Rows":1,"Actual Total Time":0.114}}',
},
],
isError: false,
},
},
},
{
name: 'inspect_schema',
displayName: 'Inspect Schema Meta',
description: 'Returns table column definitions, constraints, foreign keys, and indexes for a database schema.',
category: 'database',
parameters: [
{
name: 'schema',
type: 'string',
required: false,
default: 'public',
description: 'Target namespace schema name (e.g. public, auth, billing).',
},
{
name: 'includeForeignKeys',
type: 'boolean',
required: false,
default: 'true',
description: 'Include relation cardinality graphs and ON DELETE cascade actions.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-pg-003',
method: 'tools/call',
params: {
name: 'inspect_schema',
arguments: {
schema: 'public',
includeForeignKeys: true,
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-pg-003',
result: {
content: [
{
type: 'text',
text: '{"schema":"public","tablesCount":18,"tables":["users","teams","orders","invoices","audit_logs"]}',
},
],
isError: false,
},
},
},
],
},
{
id: 'server-gh-3',
name: 'mcp/github-tools',
scope: '@modelcontextprotocol/server-github',
version: 'v2.1.0',
transport: 'stdio',
commandOrUrl: 'npx -y @modelcontextprotocol/server-github',
status: 'healthy',
uptime: '99.91%',
latencyMs: 92,
callsToday: 2890,
description:
'GitHub REST & GraphQL API bindings for pull request management, diff inspection, and GitHub Actions CI triggers.',
envVars: ['GITHUB_PERSONAL_ACCESS_TOKEN=ghp_live_***'],
resourceTemplates: [
{
uriTemplate: 'github://repos/{owner}/{repo}/pulls/{number}',
name: 'Pull Request Snapshot',
description: 'Structured pull request metadata, commits, CI checks, and review discussions',
mimeType: 'application/json',
},
{
uriTemplate: 'github://repos/{owner}/{repo}/actions/{runId}',
name: 'CI Workflow Run Logs',
description: 'GitHub Actions build step outputs and failure diagnostics',
mimeType: 'text/plain',
},
],
tools: [
{
name: 'create_pr',
displayName: 'Create Pull Request',
description: 'Creates a GitHub pull request with title, description, branch target, and reviewer tags.',
category: 'version-control',
parameters: [
{ name: 'owner', type: 'string', required: true, description: 'GitHub account or organization name.' },
{ name: 'repo', type: 'string', required: true, description: 'Repository name.' },
{ name: 'title', type: 'string', required: true, description: 'Pull request header title.' },
{ name: 'head', type: 'string', required: true, description: 'Source branch containing commits.' },
{ name: 'base', type: 'string', required: true, description: 'Target branch (e.g. main).' },
{ name: 'draft', type: 'boolean', required: false, default: 'false', description: 'Create in draft mode.' },
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-gh-001',
method: 'tools/call',
params: {
name: 'create_pr',
arguments: {
owner: 'uday-a',
repo: 'uipkge',
title: 'feat(mcp): add mcp-server-registry-manager block',
head: 'feat/mcp-manager',
base: 'main',
draft: false,
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-gh-001',
result: {
content: [
{
type: 'text',
text: '{"number":142,"url":"https://github.com/uday-a/uipkge/pull/142","status":"open","mergeable":true}',
},
],
isError: false,
},
},
},
{
name: 'review_diff',
displayName: 'Review Diff',
description: 'Fetches and analyzes pull request unified diffs against target base branch.',
category: 'version-control',
parameters: [
{ name: 'owner', type: 'string', required: true, description: 'Repository owner.' },
{ name: 'repo', type: 'string', required: true, description: 'Repository name.' },
{ name: 'pullNumber', type: 'number', required: true, description: 'GitHub pull request number.' },
{
name: 'contextLines',
type: 'number',
required: false,
default: '3',
description: 'Number of surrounding context lines to include.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-gh-002',
method: 'tools/call',
params: {
name: 'review_diff',
arguments: {
owner: 'uday-a',
repo: 'uipkge',
pullNumber: 142,
contextLines: 3,
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-gh-002',
result: {
content: [
{
type: 'text',
text: 'diff --git a/components/blocks/McpServerRegistryManager.vue b/components/blocks/McpServerRegistryManager.vue\n+<template>\n+ <div data-slot="mcp-server-registry-manager">...</div>\n+</template>',
},
],
isError: false,
},
},
},
{
name: 'merge_pr',
displayName: 'Merge Pull Request',
description: 'Performs squash-merge or merge commit with branch auto-deletion.',
category: 'version-control',
parameters: [
{ name: 'owner', type: 'string', required: true, description: 'Repository owner.' },
{ name: 'repo', type: 'string', required: true, description: 'Repository name.' },
{ name: 'pullNumber', type: 'number', required: true, description: 'Pull request number.' },
{
name: 'mergeMethod',
type: 'string',
required: false,
default: 'squash',
enum: ['squash', 'merge', 'rebase'],
description: 'Git merge strategy to apply.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-gh-003',
method: 'tools/call',
params: {
name: 'merge_pr',
arguments: {
owner: 'uday-a',
repo: 'uipkge',
pullNumber: 142,
mergeMethod: 'squash',
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-gh-003',
result: {
content: [
{
type: 'text',
text: '{"sha":"8a7c2b3d...","merged":true,"message":"Pull Request successfully merged and closed"}',
},
],
isError: false,
},
},
},
{
name: 'get_workflow_runs',
displayName: 'Get Workflow Runs',
description: 'Lists GitHub Actions CI workflow run statuses and failure logs.',
category: 'version-control',
parameters: [
{ name: 'owner', type: 'string', required: true, description: 'Repository owner.' },
{ name: 'repo', type: 'string', required: true, description: 'Repository name.' },
{
name: 'status',
type: 'string',
required: false,
default: 'completed',
enum: ['completed', 'in_progress', 'queued', 'failure'],
description: 'Filter CI runs by lifecycle status.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-gh-004',
method: 'tools/call',
params: {
name: 'get_workflow_runs',
arguments: {
owner: 'uday-a',
repo: 'uipkge',
status: 'completed',
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-gh-004',
result: {
content: [
{
type: 'text',
text: '{"total_count":24,"workflow_runs":[{"id":9812401,"name":"CI Registry Build","conclusion":"success"}]}',
},
],
isError: false,
},
},
},
],
},
{
id: 'server-cf-4',
name: 'mcp/cloudflare-deployer',
scope: '@cloudflare/mcp-pages-worker',
version: 'v3.0.4',
transport: 'sse',
commandOrUrl: 'https://mcp-gateway.cloudflare.com/sse/v1/uipkge-prod',
status: 'healthy',
uptime: '100.0%',
latencyMs: 48,
callsToday: 1180,
description:
'Edge runtime deployment automation, real-time Cloudflare Worker telemetry log tailing, and CDN cache invalidation.',
envVars: ['CLOUDFLARE_API_TOKEN=cft_live_***', 'CLOUDFLARE_ACCOUNT_ID=8bb9e07a...'],
resourceTemplates: [
{
uriTemplate: 'cloudflare://zones/{zoneId}/pages',
name: 'Pages Deployment Manifest',
description: 'Active production and preview deployment bundles with rollback pointers',
mimeType: 'application/json',
},
{
uriTemplate: 'cloudflare://workers/{scriptName}/tail',
name: 'Live Worker Telemetry Stream',
description: 'Real-time structured event logs, CPU execution times, and exceptions',
mimeType: 'application/x-ndjson',
},
],
tools: [
{
name: 'deploy_pages',
displayName: 'Deploy Cloudflare Pages',
description: 'Triggers atomic Cloudflare Pages deployment bundle upload with verification.',
category: 'deployment',
parameters: [
{
name: 'projectName',
type: 'string',
required: true,
description: 'Cloudflare Pages project name (uipkge).',
},
{ name: 'branch', type: 'string', required: true, description: 'Git branch trigger (e.g. main).' },
{ name: 'commitHash', type: 'string', required: true, description: 'Target 40-char Git commit hash.' },
{
name: 'environment',
type: 'string',
required: false,
default: 'production',
enum: ['production', 'preview'],
description: 'Deployment target stage.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-cf-001',
method: 'tools/call',
params: {
name: 'deploy_pages',
arguments: {
projectName: 'uipkge',
branch: 'main',
commitHash: '7c8b91a24d0e932b12f4567890abcdef12345678',
environment: 'production',
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-cf-001',
result: {
content: [
{
type: 'text',
text: '{"deploymentId":"dep_9801a","url":"https://uipkge.dev","status":"active","buildDurationSec":14}',
},
],
isError: false,
},
},
},
{
name: 'purge_cache',
displayName: 'Purge CDN Cache',
description: 'Purges Cloudflare CDN cache zones by URL pattern or Cache-Tag headers.',
category: 'deployment',
parameters: [
{ name: 'zoneId', type: 'string', required: true, description: 'Cloudflare Zone identifier.' },
{
name: 'purgeEverything',
type: 'boolean',
required: false,
default: 'false',
description: 'Purge entire CDN edge cache.',
},
{ name: 'tags', type: 'array', required: false, description: 'Array of Cache-Tag headers to invalidate.' },
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-cf-002',
method: 'tools/call',
params: {
name: 'purge_cache',
arguments: {
zoneId: 'zn_uipkge_core_912',
purgeEverything: false,
tags: ['registry-json', 'llms-txt'],
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-cf-002',
result: {
content: [
{
type: 'text',
text: '{"success":true,"purgedTags":["registry-json","llms-txt"],"timestamp":"2026-08-21T10:45:00Z"}',
},
],
isError: false,
},
},
},
{
name: 'tail_worker_logs',
displayName: 'Tail Worker Logs',
description: 'Subscribes to real-time Cloudflare Worker telemetry logs and exception traces.',
category: 'deployment',
parameters: [
{ name: 'scriptName', type: 'string', required: true, description: 'Worker script identifier.' },
{
name: 'samplingRate',
type: 'number',
required: false,
default: '1.0',
description: 'Trace event sampling ratio (0.1 to 1.0).',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-cf-003',
method: 'tools/call',
params: {
name: 'tail_worker_logs',
arguments: {
scriptName: 'uipkge-registry-proxy',
samplingRate: 1.0,
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-cf-003',
result: {
content: [
{
type: 'text',
text: '{"sessionId":"tail_sess_904","status":"streaming","eventsProcessed":480}',
},
],
isError: false,
},
},
},
],
},
{
id: 'server-bv-5',
name: 'mcp/brave-search',
scope: '@modelcontextprotocol/server-brave-search',
version: 'v1.1.2',
transport: 'sse',
commandOrUrl: 'https://mcp-stream.brave.internal/sse?channel=research',
status: 'reconnecting',
uptime: '98.40%',
latencyMs: 165,
callsToday: 840,
description: 'Privacy-preserving web search, index lookup, and clean markdown extraction from live URLs.',
envVars: ['BRAVE_API_KEY=bsa_live_***', 'RATE_LIMIT_RPS=20'],
resourceTemplates: [
{
uriTemplate: 'search://web/{query}',
name: 'Real-Time Web Search Cache',
description: 'Cached organic search result nodes with domain trust rankings',
mimeType: 'application/json',
},
{
uriTemplate: 'webpage://cached/{urlHash}',
name: 'Normalized Page Mirror',
description: 'Clean reader-mode markdown representation of fetched webpage',
mimeType: 'text/markdown',
},
],
tools: [
{
name: 'brave_search',
displayName: 'Brave Web Search',
description: 'Queries Brave Search API for web results, snippets, publishing dates, and ranking scores.',
category: 'web-search',
parameters: [
{ name: 'query', type: 'string', required: true, description: 'Search term or technical keyword.' },
{
name: 'count',
type: 'number',
required: false,
default: '10',
description: 'Number of organic results (1-20).',
},
{
name: 'freshness',
type: 'string',
required: false,
enum: ['pd', 'pw', 'pm', 'py'],
description: 'Filter by time horizon: past day, week, month, or year.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-bv-001',
method: 'tools/call',
params: {
name: 'brave_search',
arguments: {
query: 'Reka UI Vue 3.5 release notes',
count: 5,
freshness: 'pm',
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-bv-001',
result: {
content: [
{
type: 'text',
text: '{"results":[{"title":"Reka UI v1.4 Documentation","url":"https://reka-ui.com","snippet":"Radix-vue is now Reka UI with Vue 3.5 TypeScript improvements."}]}',
},
],
isError: false,
},
},
},
{
name: 'fetch_web_content',
displayName: 'Fetch Web Content',
description: 'Fetches full clean markdown representations from verified web URLs.',
category: 'web-search',
parameters: [
{ name: 'url', type: 'string', required: true, description: 'Fully qualified HTTP/HTTPS URL to crawl.' },
{
name: 'format',
type: 'string',
required: false,
default: 'markdown',
enum: ['markdown', 'text', 'html'],
description: 'Target format of extracted webpage body content.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-bv-002',
method: 'tools/call',
params: {
name: 'fetch_web_content',
arguments: {
url: 'https://modelcontextprotocol.io/introduction',
format: 'markdown',
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-bv-002',
result: {
content: [
{
type: 'text',
text: '# Model Context Protocol\n\nMCP is an open standard that enables developers to build secure, two-way connections between their data sources and AI-powered tools.',
},
],
isError: false,
},
},
},
],
},
{
id: 'server-sn-6',
name: 'mcp/sentry-telemetry',
scope: '@sentry/mcp-telemetry',
version: 'v1.8.0',
transport: 'stdio',
commandOrUrl: 'npx -y @sentry/mcp-telemetry --org=uipkge',
status: 'healthy',
uptime: '99.99%',
latencyMs: 35,
callsToday: 820,
description:
'Production crash monitoring, unhandled exceptions triage, and stacktrace source-mapping for AI agents.',
envVars: ['SENTRY_AUTH_TOKEN=sntrys_live_***', 'SENTRY_ORG=uipkge'],
resourceTemplates: [
{
uriTemplate: 'sentry://projects/{project}/issues/{issueId}',
name: 'Sentry Issue Context',
description: 'Resolved source maps, breadcrumbs trail, device context, and user impact rate',
mimeType: 'application/json',
},
{
uriTemplate: 'sentry://projects/{project}/stats/24h',
name: 'Hourly Error Volume',
description: 'Aggregated error frequency histograms across release versions',
mimeType: 'application/json',
},
],
tools: [
{
name: 'query_sentry_issues',
displayName: 'Query Sentry Issues',
description: 'Lists unhandled exceptions and regression issues by release tag and frequency.',
category: 'observability',
parameters: [
{ name: 'project', type: 'string', required: true, description: 'Sentry project slug.' },
{ name: 'query', type: 'string', required: false, description: 'Sentry search query (e.g. is:unresolved).' },
{
name: 'statsPeriod',
type: 'string',
required: false,
default: '24h',
enum: ['24h', '14d', '30d'],
description: 'Time window for issue occurrence statistics.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-sn-001',
method: 'tools/call',
params: {
name: 'query_sentry_issues',
arguments: {
project: 'uipkge-astro-site',
query: 'is:unresolved level:error',
statsPeriod: '24h',
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-sn-001',
result: {
content: [
{
type: 'text',
text: '{"issues":[{"id":"iss_991","title":"TypeError: Cannot read properties of undefined (reading \'dataset\')","count":3,"lastSeen":"2026-08-21T09:12:00Z"}]}',
},
],
isError: false,
},
},
},
{
name: 'get_issue_stacktrace',
displayName: 'Get De-minified Stacktrace',
description: 'Retrieves de-minified source code stacktrace and breadcrumb chain for an error ID.',
category: 'observability',
parameters: [
{ name: 'issueId', type: 'string', required: true, description: 'Unique Sentry issue identifier.' },
{
name: 'expandContext',
type: 'boolean',
required: false,
default: 'true',
description: 'Include 5 lines of source code surrounding the crashing line.',
},
],
sampleRpcRequest: {
jsonrpc: '2.0',
id: 'req-sn-002',
method: 'tools/call',
params: {
name: 'get_issue_stacktrace',
arguments: {
issueId: 'iss_991',
expandContext: true,
},
},
},
sampleRpcResponse: {
jsonrpc: '2.0',
id: 'req-sn-002',
result: {
content: [
{
type: 'text',
text: '{"filename":"src/components/story/Story.vue","lineno":42,"colno":14,"function":"handleCopy","contextLine":"navigator.clipboard.writeText(props.code)"}',
},
],
isError: false,
},
},
},
],
},
]
export type TransportType = 'stdio' | 'sse'
export type ServerStatus = 'healthy' | 'reconnecting' | 'degraded'
export interface ToolParam {
name: string
type: 'string' | 'number' | 'boolean' | 'object' | 'array'
required: boolean
description: string
default?: string
enum?: string[]
}
export interface ToolSchema {
name: string
displayName: string
description: string
category: 'documentation' | 'database' | 'version-control' | 'deployment' | 'web-search' | 'observability'
parameters: ToolParam[]
sampleRpcRequest: {
jsonrpc: '2.0'
id: string
method: 'tools/call'
params: {
name: string
arguments: Record<string, any>
}
}
sampleRpcResponse: {
jsonrpc: '2.0'
id: string
result: {
content: Array<{
type: 'text' | 'resource' | 'image'
text?: string
resource?: any
}>
isError: boolean
}
}
}
export interface ResourceTemplate {
uriTemplate: string
name: string
description: string
mimeType: string
}
export interface McpServer {
id: string
name: string
scope: string
version: string
transport: TransportType
commandOrUrl: string
status: ServerStatus
uptime: string
latencyMs: number
callsToday: number
description: string
envVars: string[]
resourceTemplates: ResourceTemplate[]
tools: ToolSchema[]
}
Raw manifest:https://uipkge.dev/r/vue/mcp-server-registry-manager.json