Skip to main content
Glama

Agent MCP

data-store.ts13.6 kB
import { create } from 'zustand' import { Agent, Task, apiClient } from '../api' // Memoization cache for expensive computations const memoCache = new Map<string, { data: any, timestamp: number }>() const CACHE_DURATION = 5000 // 5 seconds cache function memoize<T>(key: string, computation: () => T): T { const cached = memoCache.get(key) const now = Date.now() if (cached && now - cached.timestamp < CACHE_DURATION) { return cached.data as T } const result = computation() memoCache.set(key, { data: result, timestamp: now }) return result } function clearMemoCache() { memoCache.clear() } // Debounce utility for API calls function debounce<T extends (...args: any[]) => any>( func: T, wait: number ): (...args: Parameters<T>) => void { let timeout: NodeJS.Timeout | null = null return (...args: Parameters<T>) => { if (timeout) { clearTimeout(timeout) } timeout = setTimeout(() => { func(...args) }, wait) } } interface AllData { agents: Agent[] tasks: Task[] context: any[] actions: any[] file_metadata: any[] file_map: Record<string, any> admin_token: string timestamp: string } interface DataStore { // Data data: AllData | null loading: boolean error: string | null lastFetch: number isRefreshing: boolean // Actions fetchAllData: (force?: boolean) => Promise<void> getAgent: (agentId: string) => Agent | undefined getAgentTasks: (agentId: string) => Task[] getAgentActions: (agentId: string) => any[] getTask: (taskId: string) => Task | undefined getContext: (contextKey: string) => any | undefined getAdminToken: () => string | undefined getAgentToken: (agentId: string) => string | undefined getAgentTaskAnalysis: (agentId: string) => { assignedTasks: Task[] workedOnTasks: Task[] completedTasks: Task[] completionActions: any[] totalTasks: number assignedCount: number workedOnCount: number completedCount: number completionActionCount: number } updateAgent: (agent: Agent) => void updateTask: (task: Task) => void refreshData: () => Promise<void> shouldDisplayAgent: (agent: any) => boolean getActiveAgents: () => any[] getIdleAgentsForCleanup: () => any[] } export const useDataStore = create<DataStore>((set, get) => ({ data: null, loading: false, error: null, lastFetch: 0, isRefreshing: false, fetchAllData: async (force = false) => { const state = get() // Skip if already loading if (state.loading || state.isRefreshing) return // Skip if data is fresh (less than 30 seconds old) unless forced const now = Date.now() if (!force && state.data && now - state.lastFetch < 30000) return // Set loading state appropriately if (!state.data || force) { set({ loading: true, error: null }) } else { set({ isRefreshing: true, error: null }) } try { // Try the new all-data endpoint first let data try { const response = await fetch(`${apiClient.getServerUrl()}/api/all-data`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, mode: 'cors' }) if (!response.ok) throw new Error(`Failed to fetch: ${response.statusText}`) data = await response.json() } catch (err) { // Fallback to fetching data from individual endpoints console.debug('All-data endpoint not available, using fallback...') const [agents, tasks, tokens, contextData] = await Promise.all([ apiClient.getAgents(), apiClient.getTasks(), apiClient.getTokens(), fetch(`${apiClient.getServerUrl()}/api/context-data`).then(res => res.ok ? res.json() : []) ]) // Merge tokens into agents const agentsWithTokens = agents.map(agent => { const token = tokens.agent_tokens.find(t => t.agent_id === agent.agent_id)?.token || (agent.agent_id === 'Admin' ? tokens.admin_token : undefined) return { ...agent, auth_token: token } }) data = { agents: agentsWithTokens, tasks, context: contextData, actions: [], file_metadata: [], file_map: {}, admin_token: tokens.admin_token, timestamp: new Date().toISOString() } } console.debug('Fetched all data:', { agents: data.agents?.length || 0, tasks: data.tasks?.length || 0, context: data.context?.length || 0, actions: data.actions?.length || 0 }) console.debug('Context data received:', data.context) // Clear memoization cache when data updates clearMemoCache() set({ data, loading: false, isRefreshing: false, error: null, lastFetch: now }) } catch (error) { console.debug('Failed to fetch all data:', error) set({ loading: false, isRefreshing: false, error: error instanceof Error ? error.message : 'Failed to fetch data' }) } }, getAgent: (agentId: string) => { const state = get() if (!state.data) return undefined // Handle Admin specially if (agentId === 'Admin' || agentId === 'admin') { return state.data.agents.find(a => a.agent_id === 'Admin') } // Strip prefix if present const cleanId = agentId.startsWith('agent_') ? agentId.substring(6) : agentId return state.data.agents.find(a => a.agent_id === cleanId) }, getAgentTasks: (agentId: string) => { const state = get() if (!state.data) return [] // Create cache key including data timestamp for cache invalidation const cacheKey = `agent-tasks-${agentId}-${state.data.timestamp}` return memoize(cacheKey, () => { // Strip prefix if present for consistent matching const cleanAgentId = agentId.startsWith('agent_') ? agentId.substring(6) : agentId // Handle admin variations - both 'Admin' and 'admin' are used in the system const normalizedAgentId = cleanAgentId === 'Admin' ? 'admin' : cleanAgentId // Get tasks assigned to this agent (handle both admin variations) const assignedTasks = state.data!.tasks.filter(t => t.assigned_to === normalizedAgentId || t.assigned_to === cleanAgentId || (normalizedAgentId === 'admin' && (t.assigned_to === 'Admin' || t.assigned_to === 'admin')) ) // Get tasks this agent has worked on (via actions) const workedOnTaskIds = new Set<string>() const agentActions = state.data!.actions.filter(a => a.agent_id === normalizedAgentId || a.agent_id === cleanAgentId || (normalizedAgentId === 'admin' && (a.agent_id === 'Admin' || a.agent_id === 'admin')) ) agentActions.forEach(action => { if (action.task_id) { workedOnTaskIds.add(action.task_id) } }) // Get tasks worked on but not assigned const workedOnTasks = state.data!.tasks.filter(t => workedOnTaskIds.has(t.task_id) && t.assigned_to !== normalizedAgentId && t.assigned_to !== cleanAgentId && !(normalizedAgentId === 'admin' && (t.assigned_to === 'Admin' || t.assigned_to === 'admin')) ) // Combine and deduplicate const allTasks = [...assignedTasks, ...workedOnTasks] const uniqueTasks = allTasks.filter((task, index, arr) => arr.findIndex(t => t.task_id === task.task_id) === index ) return uniqueTasks }) }, getAgentActions: (agentId: string) => { const state = get() if (!state.data) return [] // Strip prefix if present for consistent matching const cleanAgentId = agentId.startsWith('agent_') ? agentId.substring(6) : agentId // Handle admin variations - both 'Admin' and 'admin' are used in the system const normalizedAgentId = cleanAgentId === 'Admin' ? 'admin' : cleanAgentId return state.data.actions.filter(a => a.agent_id === normalizedAgentId || a.agent_id === cleanAgentId || (normalizedAgentId === 'admin' && (a.agent_id === 'Admin' || a.agent_id === 'admin')) ) }, getTask: (taskId: string) => { const state = get() if (!state.data) return undefined // Strip prefix if present const cleanId = taskId.startsWith('task_') ? taskId.substring(5) : taskId return state.data.tasks.find(t => t.task_id === cleanId) }, getContext: (contextKey: string) => { const state = get() if (!state.data) return undefined // Strip prefix if present const cleanKey = contextKey.startsWith('context_') ? contextKey.substring(8) : contextKey return state.data.context.find(c => c.context_key === cleanKey) }, getAdminToken: () => { const state = get() return state.data?.admin_token }, getAgentToken: (agentId: string) => { const agent = get().getAgent(agentId) return agent?.auth_token }, getAgentTaskAnalysis: (agentId: string) => { const state = get() if (!state.data) return { assignedTasks: [], workedOnTasks: [], completedTasks: [], completionActions: [], totalTasks: 0, assignedCount: 0, workedOnCount: 0, completedCount: 0, completionActionCount: 0 } const cleanAgentId = agentId.startsWith('agent_') ? agentId.substring(6) : agentId // Handle admin variations const normalizedAgentId = cleanAgentId === 'Admin' ? 'admin' : cleanAgentId const allTasks = get().getAgentTasks(agentId) const assignedTasks = allTasks.filter(t => t.assigned_to === normalizedAgentId || t.assigned_to === cleanAgentId || (normalizedAgentId === 'admin' && (t.assigned_to === 'Admin' || t.assigned_to === 'admin')) ) const workedOnTasks = allTasks.filter(t => t.assigned_to !== normalizedAgentId && t.assigned_to !== cleanAgentId && !(normalizedAgentId === 'admin' && (t.assigned_to === 'Admin' || t.assigned_to === 'admin')) ) const completedTasks = allTasks.filter(t => t.status === 'completed') // Get completion actions for this agent const completionActions = state.data.actions.filter(a => (a.agent_id === normalizedAgentId || a.agent_id === cleanAgentId) && (a.action_type === 'task_completed' || a.action_type === 'complete_task' || a.action_type.includes('complet')) ) return { assignedTasks, workedOnTasks, completedTasks, completionActions, totalTasks: allTasks.length, assignedCount: assignedTasks.length, workedOnCount: workedOnTasks.length, completedCount: completedTasks.length, completionActionCount: completionActions.length } }, updateAgent: (agent: Agent) => { const state = get() if (!state.data) return const index = state.data.agents.findIndex(a => a.agent_id === agent.agent_id) if (index !== -1) { const newAgents = [...state.data.agents] newAgents[index] = agent set({ data: { ...state.data, agents: newAgents } }) } }, updateTask: (task: Task) => { const state = get() if (!state.data) return const index = state.data.tasks.findIndex(t => t.task_id === task.task_id) if (index !== -1) { const newTasks = [...state.data.tasks] newTasks[index] = task set({ data: { ...state.data, tasks: newTasks } }) } }, refreshData: async () => { // Force refresh await get().fetchAllData(true) }, // Debounced refresh to prevent rapid successive calls debouncedRefresh: debounce(async () => { await get().fetchAllData() }, 500), // Agent lifecycle management shouldDisplayAgent: (agent: any) => { // Always show admin if (agent.agent_id === 'Admin' || agent.agent_id === 'admin') return true // Show if agent has an active task if (agent.current_task) return true // Show if agent is new (created within last 10 minutes) const now = new Date() const createdAt = new Date(agent.created_at) const ageInMinutes = (now.getTime() - createdAt.getTime()) / (1000 * 60) return ageInMinutes <= 10 }, getActiveAgents: () => { const state = get() if (!state.data) return [] return state.data.agents.filter(agent => get().shouldDisplayAgent(agent)) }, getIdleAgentsForCleanup: () => { const state = get() if (!state.data) return [] const now = new Date() return state.data.agents.filter(agent => { // Never cleanup admin if (agent.agent_id === 'Admin' || agent.agent_id === 'admin') return false // Don't cleanup if has active task if (agent.current_task) return false // Don't cleanup if already terminated if (agent.status === 'terminated') return false // Cleanup if older than 10 minutes without task const createdAt = new Date(agent.created_at) const ageInMinutes = (now.getTime() - createdAt.getTime()) / (1000 * 60) return ageInMinutes > 10 }) } })) // Auto-refresh every 60 seconds (reduced from 30s for better performance) if (typeof window !== 'undefined') { setInterval(() => { const store = useDataStore.getState() if (store.data) { store.refreshData() } }, 60000) }

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/rinadelph/Agent-MCP'

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