File size: 3,149 Bytes
921d377 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | /**
* useMatrixHub — hook for searching and browsing the MatrixHub MCP server catalog.
*
* MatrixHub (https://github.com/agent-matrix/matrix-hub) is an optional
* secondary catalog source for MCP servers, agents, and tools.
*
* Public endpoints used (no auth required):
* GET /catalog/search?q=...&type=mcp_server&limit=...
* GET /catalog?type=mcp_server&limit=...&offset=...
*
* Phase 11 — fully additive, optional feature.
*/
import { useCallback, useEffect, useMemo, useState } from 'react';
export function useMatrixHub({ hubUrl, enabled = true }) {
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [search, setSearch] = useState('');
const [healthy, setHealthy] = useState(null);
const baseUrl = hubUrl.replace(/\/+$/, '');
// Health check
useEffect(() => {
if (!enabled)
return;
let cancelled = false;
(async () => {
try {
const res = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(5000) });
if (!cancelled)
setHealthy(res.ok);
}
catch {
if (!cancelled)
setHealthy(false);
}
})();
return () => { cancelled = true; };
}, [baseUrl, enabled]);
// Search / list
const fetchCatalog = useCallback(async () => {
if (!enabled || healthy === false)
return;
setLoading(true);
setError(null);
try {
let url;
if (search.trim()) {
const q = encodeURIComponent(search.trim());
url = `${baseUrl}/catalog/search?q=${q}&type=mcp_server&limit=50`;
}
else {
url = `${baseUrl}/catalog?type=mcp_server&limit=100&offset=0`;
}
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
if (!res.ok)
throw new Error(`HTTP ${res.status}`);
const data = await res.json();
// Search returns { items, total }, list returns { items/entities, total }
const serverItems = data.items || data.entities || [];
setItems(serverItems);
setTotal(data.total ?? serverItems.length);
}
catch (e) {
setError(e?.message || 'Failed to reach MatrixHub');
setItems([]);
setTotal(0);
}
finally {
setLoading(false);
}
}, [baseUrl, enabled, search, healthy]);
useEffect(() => {
void fetchCatalog();
}, [fetchCatalog]);
// Derived
const capabilities = useMemo(() => {
const set = new Set();
items.forEach((i) => i.capabilities?.forEach((c) => set.add(c)));
return Array.from(set).sort();
}, [items]);
return {
items,
total,
loading,
error,
healthy,
search,
setSearch,
capabilities,
refresh: fetchCatalog,
};
}
|