/**
* 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 */}
);
}
// ── 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 */}
{/* Loading */}
{loading && (
Loading MatrixHub catalog...
)}
{/* 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)}/>)}
);
}