Skip to main content
Glama

Wanaku MCP Server

TargetsPage.tsx5.64 kB
import { ToastNotification } from "@carbon/react"; import { TargetsTable } from "./TargetsTable"; import React, { useState, useEffect } from "react"; import { useCapabilities } from "../../hooks/api/use-capabilities"; import { ServiceTargetState } from "./ServiceTargetState"; import { getGetApiV1CapabilitiesNotificationsUrl } from "../../api/wanaku-router-api"; import { ServiceTargetEvent } from "../../models"; export const TargetsPage: React.FC = () => { const [fetchedData, setFetchedData] = useState<ServiceTargetState[]>([]); const [isLoading, setIsLoading] = useState(true); const [errorMessage, setErrorMessage] = useState<string | null>(null); const { listManagementResources, listManagementTools, listManagementToolsState, listManagementResourcesState } = useCapabilities(); const fetch = async () => { setIsLoading(true); const [tools, resources] = await Promise.all([ listManagementTools(), listManagementResources() ]); const merged = tools.data.data?.concat(resources.data.data!); setFetchedData(merged!); setIsLoading(false); return merged as ServiceTargetState[]; } const fetchState = async (data: ServiceTargetState[]) => { const [tools, resources] = await Promise.all([ listManagementToolsState(), listManagementResourcesState() ]); const updatedData = data.map((entry) => { if (entry.service && tools.data?.data?.[entry.service]) { return { ...entry, ...tools.data?.data?.[entry.service][0] }; } else if (entry.service && resources.data?.data?.[entry.service]) { return { ...entry, ...resources.data?.data?.[entry.service][0] }; } return entry; }); updatedData?.forEach((t: ServiceTargetState) => { t.lastSeen = dateFormatter.format(new Date(t.lastSeen ?? new Date())); }) setFetchedData(updatedData); return updatedData as ServiceTargetState[]; } const dateFormatter = new Intl.DateTimeFormat(undefined, { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' }); const setupSSE = (data: ServiceTargetState[]) => { const baseUrl = VITE_API_URL || window.location.origin; const eventSource = new EventSource(baseUrl + getGetApiV1CapabilitiesNotificationsUrl()); eventSource.onopen = () => { console.log('SSE connection opened'); }; eventSource.addEventListener('UPDATE', (event) => { const serviceTargetEvent = JSON.parse(event.data) as ServiceTargetEvent; const updatedData = data.map((entry) => { if (entry.id == serviceTargetEvent.id) { entry.active = serviceTargetEvent.serviceState?.healthy; // TODO is this assumption true? const date = new Date(serviceTargetEvent.serviceState?.timestamp ?? new Date()); entry.lastSeen = dateFormatter.format(date); entry.reason = serviceTargetEvent.serviceState?.reason; } return entry; }); data = updatedData; setFetchedData(updatedData); }); eventSource.addEventListener('PING', (event) => { const serviceTargetEvent = JSON.parse(event.data) as ServiceTargetEvent; const updatedData = data.map((entry) => { if (entry.id == serviceTargetEvent.id) { entry.lastSeen = dateFormatter.format(new Date()); // TODO What to do with dates?! entry.active = true; // TODO is this assumption true? } return entry; }); data = updatedData; setFetchedData(updatedData); }); eventSource.addEventListener('REGISTER', (event) => { const serviceTargetEvent = JSON.parse(event.data) as ServiceTargetEvent; // Remove if already present, shuoldn't be, but better safe than sorry let updatedData = data.filter((entry) => entry.id != serviceTargetEvent.id); updatedData.push(serviceTargetEvent.serviceTarget as ServiceTargetState); data = updatedData; fetchState(updatedData); }); eventSource.addEventListener('DEREGISTER', (event) => { const serviceTargetEvent = JSON.parse(event.data) as ServiceTargetEvent; const updatedData = data.filter((entry) => entry.id != serviceTargetEvent.id); data = updatedData; setFetchedData(updatedData); }); eventSource.onerror = (error) => { console.error('SSE error:', error); }; return eventSource; }; useEffect(() => { let eventSource: EventSource | null = null; fetch() .then((list) => fetchState(list)) .then((list) => { eventSource = setupSSE(list); }); return () => { eventSource?.close(); }; }, []); useEffect(() => { if (errorMessage) { const timer = setTimeout(() => { setErrorMessage(null); }, 10000); return () => clearTimeout(timer); } }, [errorMessage]); if (isLoading) return <div>Loading...</div>; return ( <div> {errorMessage && ( <ToastNotification kind="error" title="Error" subtitle={errorMessage} onCloseButtonClick={() => setErrorMessage(null)} timeout={10000} style={{ float: "right" }} /> )} <h1 className="title">Capabilities</h1> <p className="description"> Capbilties (Downstream Services) connected to Wanaku. </p> <div id="page-content"> {fetchedData && ( <TargetsTable targets={fetchedData} /> )} </div> </div> ); };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wanaku-ai/wanaku'

If you have feedback or need assistance with the MCP directory API, please join our Discord server