/** * MatrixHubCatalogPanel — browse and install MCP servers from the MatrixHub catalog. * * MatrixHub (https://github.com/agent-matrix/matrix-hub) is an optional * secondary catalog source for MCP servers, agents, and tools. * * Phase 11 — fully additive, does not modify any existing component. */ import React, { useState } from 'react'; import { Search, RefreshCw, Globe, Star, ExternalLink, AlertCircle, ChevronDown, Copy, Check, X as XIcon, } from 'lucide-react'; import { useMatrixHub } from './useMatrixHub'; // ── Server card ────────────────────────────────────────────────────────── function MatrixHubServerCard({ item, onInstall, onViewDetails, }) { return (
{/* Header */}

{item.name}

{item.version && (v{item.version})}
{/* Score badge */} {item.score_final > 0 && ( {item.score_final.toFixed(1)} )}
{/* Description */}

{item.summary || 'No description available.'}

{/* Tags row */}
{item.capabilities?.slice(0, 3).map((cap) => ( {cap} ))} {item.frameworks?.slice(0, 2).map((fw) => ( {fw} ))}
{/* Action buttons */}
{(item.install_url || item.source_url || item.homepage) && ( )}
); } // ── Details drawer ────────────────────────────────────────────────────── function MatrixHubDetailsDrawer({ item, onClose, }) { const [copiedUrl, setCopiedUrl] = useState(false); const copyToClipboard = (text) => { navigator.clipboard.writeText(text).catch(() => { }); setCopiedUrl(true); setTimeout(() => setCopiedUrl(false), 2000); }; return (
e.stopPropagation()}> {/* Header */}

{item.name}

{item.version && v{item.version}} {item.score_final > 0 && (<> · {item.score_final.toFixed(1)} )}
{/* Body */}

{item.summary}

{/* Capabilities */} {item.capabilities?.length > 0 && (

Capabilities

{item.capabilities.map((c) => ( {c} ))}
)} {/* Frameworks */} {item.frameworks?.length > 0 && (

Frameworks

{item.frameworks.map((f) => ( {f} ))}
)} {/* Providers */} {item.providers?.length > 0 && (

Providers

{item.providers.map((p) => ( {p} ))}
)} {/* Snippet / install command */} {item.snippet && (

Install Snippet

                  {item.snippet}
                
)} {/* Links */}
{item.homepage && ( Homepage )} {item.source_url && ( Source Code )} {item.manifest_url && ( Manifest )}
); } // ── Capability filter ──────────────────────────────────────────────────── function CapabilityFilter({ capabilities, selected, onSelect, }) { if (capabilities.length === 0) return null; return (
); } // ── Main panel ─────────────────────────────────────────────────────────── export function MatrixHubCatalogPanel({ hubUrl, enabled = true }) { const { items, total, loading, error, healthy, search, setSearch, capabilities, refresh, } = useMatrixHub({ hubUrl, enabled }); const [refreshing, setRefreshing] = useState(false); const [capFilter, setCapFilter] = useState(''); const [detailItem, setDetailItem] = useState(null); const filteredItems = capFilter ? items.filter((i) => i.capabilities?.includes(capFilter)) : items; const handleRefresh = async () => { setRefreshing(true); try { await refresh(); } finally { setRefreshing(false); } }; // Not reachable if (!enabled || healthy === false) { return (

MatrixHub Catalog

{healthy === false ? 'Unreachable' : 'Disabled'}

{healthy === false ? `Cannot connect to MatrixHub at ${hubUrl}. Make sure it is running.` : 'MatrixHub integration is disabled.'}

); } return (
{/* Header */}
{total} servers {capabilities.length > 0 && (<> {capabilities.length} capabilities )}
{/* Filters */}
setSearch(e.target.value)} placeholder="Search MatrixHub..." className="w-full bg-white/5 border border-white/10 rounded-xl pl-10 pr-4 py-2.5 text-sm text-white placeholder-white/30 focus:outline-none focus:border-violet-500/50 transition-colors"/>
{/* Loading */} {loading && (
Loading MatrixHub catalog...
)} {/* Error */} {error && (
{error}
)} {/* Grid */} {!loading && filteredItems.length > 0 && (
{filteredItems.map((item) => ( { }} onViewDetails={() => setDetailItem(item)}/>))}
)} {/* Empty */} {!loading && !error && filteredItems.length === 0 && (search || capFilter) && (

No servers match your search.

)} {/* Details drawer */} {detailItem && ( setDetailItem(null)}/>)}
); }