| import { useState, useEffect, useCallback } from 'react'; | |
| import api from '../services/api-wrapper.ts'; | |
| import type { MCPTool } from '../types/index.ts'; | |
| import { API_ENDPOINTS } from '../constants/index.ts'; | |
| import { cacheMcpTools, getCachedMcpTools } from '../utils/cache.utils.ts'; | |
| type MCPToolsState = { | |
| tools: MCPTool[]; | |
| loading: boolean; | |
| error: Error | null; | |
| }; | |
| const INITIAL_STATE = { | |
| tools: [] as MCPTool[], | |
| loading: false, | |
| error: null, | |
| } as MCPToolsState; | |
| const isMCPTool = (value: unknown): value is MCPTool => { | |
| if (!value || typeof value !== 'object') { | |
| return false; | |
| } | |
| const candidate = value as { name?: unknown; description?: unknown }; | |
| return typeof candidate.name === 'string' && | |
| (candidate.description === undefined || typeof candidate.description === 'string'); | |
| }; | |
| const normalizeToolsResponse = (payload: unknown): MCPTool[] | null => { | |
| if (Array.isArray(payload)) { | |
| return payload.filter(isMCPTool); | |
| } | |
| if (payload && typeof payload === 'object') { | |
| const candidate = payload as { tools?: unknown; data?: unknown }; | |
| if (Array.isArray(candidate.tools)) { | |
| return candidate.tools.filter(isMCPTool); | |
| } | |
| if (Array.isArray(candidate.data)) { | |
| return candidate.data.filter(isMCPTool); | |
| } | |
| } | |
| return null; | |
| }; | |
| export const useMCPTools = () => { | |
| const [state, setState] = useState(INITIAL_STATE); | |
| const fetchTools = useCallback(async () => { | |
| try { | |
| const cachedTools = getCachedMcpTools(); | |
| if (cachedTools && cachedTools.length > 0) { | |
| setState({ tools: cachedTools, loading: false, error: null }); | |
| } else { | |
| setState((prev) => ({ ...prev, loading: true, error: null })); | |
| } | |
| const payload = await api.get<unknown>(API_ENDPOINTS.MCP_TOOLS); | |
| const parsedTools = normalizeToolsResponse(payload); | |
| if (!parsedTools) { | |
| throw new Error('Invalid MCP tools response shape'); | |
| } | |
| setState({ tools: parsedTools, loading: false, error: null }); | |
| cacheMcpTools(parsedTools); | |
| } catch (err) { | |
| setState({ | |
| tools: getCachedMcpTools() ?? [], | |
| loading: false, | |
| error: err instanceof Error ? err : new Error('Failed to fetch MCP tools'), | |
| }); | |
| } | |
| }, []); | |
| useEffect(() => { | |
| fetchTools(); | |
| }, [fetchTools]); | |
| return { ...state, refetch: fetchTools }; | |
| }; | |