Buckets:
| //import "./App.css"; | |
| import { useState, useEffect } from 'react'; | |
| import useSWR, { mutate } from 'swr'; | |
| import { ToolsCard } from './components/ToolsCard'; | |
| import { GradioEndpointsCard } from './components/GradioEndpointsCard'; | |
| import { TransportMetricsCard } from './components/TransportMetricsCard'; | |
| import { McpMethodsCard } from './components/McpMethodsCard'; | |
| import { ConnectionFooter } from './components/ConnectionFooter'; | |
| import { Tabs, TabsList, TabsTrigger, TabsContent } from './components/ui/tabs'; | |
| import { Card, CardHeader, CardTitle, CardDescription, CardContent } from './components/ui/card'; | |
| import { Button } from './components/ui/button'; | |
| import { Separator } from './components/ui/separator'; | |
| import { Copy, Settings } from 'lucide-react'; | |
| import type { TransportInfo } from '../shared/transport-info.js'; | |
| import { GRADIO_IMAGE_FILTER_FLAG, README_INCLUDE_FLAG } from '../shared/behavior-flags.js'; | |
| import { normalizeBuiltInTools } from '../shared/tool-normalizer.js'; | |
| import { | |
| SPACE_SEARCH_TOOL_ID, | |
| REPO_SEARCH_TOOL_ID, | |
| CREATE_REPO_TOOL_ID, | |
| PAPER_SEARCH_TOOL_ID, | |
| DUPLICATE_SPACE_TOOL_ID, | |
| SPACE_FILES_TOOL_ID, | |
| DOCS_SEMANTIC_SEARCH_TOOL_ID, | |
| DOC_FETCH_TOOL_ID, | |
| HUB_REPO_DETAILS_TOOL_ID, | |
| SEMANTIC_SEARCH_TOOL_CONFIG, | |
| REPO_SEARCH_TOOL_CONFIG, | |
| CREATE_REPO_TOOL_CONFIG, | |
| PAPER_SEARCH_TOOL_CONFIG, | |
| HUB_REPO_DETAILS_TOOL_CONFIG, | |
| DUPLICATE_SPACE_TOOL_CONFIG, | |
| SPACE_FILES_TOOL_CONFIG, | |
| DOCS_SEMANTIC_SEARCH_CONFIG, | |
| DOC_FETCH_CONFIG, | |
| } from '@llmindset/hf-mcp'; | |
| type SpaceTool = { | |
| _id: string; | |
| name: string; | |
| subdomain: string; | |
| emoji: string; | |
| }; | |
| type AppSettings = { | |
| builtInTools: string[]; | |
| spaceTools: SpaceTool[]; | |
| }; | |
| // SWR fetcher function | |
| const fetcher = (url: string) => | |
| fetch(url).then((res) => { | |
| if (!res.ok) { | |
| throw new Error(`Failed to fetch: ${res.status}`); | |
| } | |
| return res.json(); | |
| }); | |
| function App() { | |
| // Use SWR for transport info with auto-refresh | |
| const { data: transportInfo, error: transportError } = useSWR<TransportInfo>('/api/transport', fetcher, { | |
| refreshInterval: 3000, // Refresh every 3 seconds | |
| revalidateOnFocus: true, | |
| revalidateOnReconnect: true, | |
| }); | |
| // Use SWR for sessions to trigger stdioClient update | |
| useSWR('/api/sessions', fetcher, { | |
| refreshInterval: 3000, // Refresh every 3 seconds | |
| revalidateOnFocus: true, | |
| }); | |
| // Use SWR for settings | |
| const { data: settings } = useSWR<AppSettings>('/api/settings', fetcher); | |
| const readmeFlagEnabled = settings?.builtInTools?.includes(README_INCLUDE_FLAG) ?? false; | |
| const gradioImageFilterEnabled = settings?.builtInTools?.includes(GRADIO_IMAGE_FILTER_FLAG) ?? false; | |
| // Simple state: 3 text boxes, 3 checkboxes | |
| const [spaceNames, setSpaceNames] = useState<string[]>(['', '', '']); | |
| const [spaceSubdomains, setSpaceSubdomains] = useState<string[]>(['', '', '']); | |
| const [enabledSpaces, setEnabledSpaces] = useState<boolean[]>([false, false, false]); | |
| // Load space tools from API settings only on initial load | |
| const [initialLoadDone, setInitialLoadDone] = useState(false); | |
| useEffect(() => { | |
| if (settings?.spaceTools && !initialLoadDone) { | |
| const names = ['', '', '']; | |
| const subdomains = ['', '', '']; | |
| const enabled = [false, false, false]; | |
| settings.spaceTools.forEach((tool, index) => { | |
| if (index < 3) { | |
| names[index] = tool.name; | |
| subdomains[index] = tool.subdomain; | |
| enabled[index] = true; | |
| } | |
| }); | |
| setSpaceNames(names); | |
| setSpaceSubdomains(subdomains); | |
| setEnabledSpaces(enabled); | |
| setInitialLoadDone(true); | |
| } | |
| }, [settings, initialLoadDone]); | |
| const isLoading = !transportInfo && !transportError; | |
| const error = transportError ? transportError.message : null; | |
| // Handle checkbox changes | |
| const handleToolToggle = async (toolId: string, checked: boolean) => { | |
| try { | |
| // Optimistic update - immediately update the UI | |
| const currentSettings = settings || { builtInTools: [] }; | |
| const currentTools = normalizeBuiltInTools(currentSettings.builtInTools); | |
| const updatedTools = checked | |
| ? [...currentTools.filter((id) => id !== toolId), toolId] | |
| : currentTools.filter((id) => id !== toolId); | |
| const newTools = normalizeBuiltInTools(updatedTools); | |
| const optimisticSettings = { | |
| ...currentSettings, | |
| builtInTools: newTools, | |
| }; | |
| // Update the cache optimistically | |
| mutate('/api/settings', optimisticSettings, false); | |
| // Make the API call | |
| const response = await fetch('/api/settings', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ builtInTools: newTools }), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to update tool settings: ${response.status}`); | |
| } | |
| // Revalidate to get fresh data from server | |
| mutate('/api/settings'); | |
| console.log(`${toolId} is now ${checked ? 'enabled' : 'disabled'}`); | |
| } catch (err) { | |
| console.error(`Error updating tool settings:`, err); | |
| alert(`Error updating ${toolId}: ${err instanceof Error ? err.message : 'Unknown error'}`); | |
| // Revert optimistic update on error | |
| mutate('/api/settings'); | |
| } | |
| }; | |
| // Handle space tool toggle - just update checkbox and send to API | |
| const handleSpaceToolToggle = async (index: number, enabled: boolean) => { | |
| const newEnabled = [...enabledSpaces]; | |
| newEnabled[index] = enabled; | |
| setEnabledSpaces(newEnabled); | |
| // Send only checked items to API | |
| await updateSpaceToolsAPI(newEnabled); | |
| }; | |
| // Handle space tool name change - just update the text box | |
| const handleSpaceToolNameChange = async (index: number, name: string) => { | |
| const newNames = [...spaceNames]; | |
| newNames[index] = name; | |
| setSpaceNames(newNames); | |
| // Send to API if this one is checked | |
| if (enabledSpaces[index]) { | |
| await updateSpaceToolsAPI(enabledSpaces); | |
| } | |
| }; | |
| // Handle space tool subdomain change - just update the text box | |
| const handleSpaceToolSubdomainChange = async (index: number, subdomain: string) => { | |
| const newSubdomains = [...spaceSubdomains]; | |
| newSubdomains[index] = subdomain; | |
| setSpaceSubdomains(newSubdomains); | |
| // Send to API if this one is checked | |
| if (enabledSpaces[index]) { | |
| await updateSpaceToolsAPI(enabledSpaces); | |
| } | |
| }; | |
| // Simple helper to send current state to API | |
| const updateSpaceToolsAPI = async (enabledArray: boolean[]) => { | |
| try { | |
| const spaceTools: SpaceTool[] = []; | |
| // Only include checked items | |
| for (let i = 0; i < 3; i++) { | |
| if (enabledArray[i] && spaceNames[i] && spaceSubdomains[i]) { | |
| spaceTools.push({ | |
| _id: `space-${i}`, | |
| name: spaceNames[i], | |
| subdomain: spaceSubdomains[i], | |
| emoji: '🔧', | |
| }); | |
| } | |
| } | |
| const response = await fetch('/api/settings', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ spaceTools }), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to update space tools: ${response.status}`); | |
| } | |
| } catch (err) { | |
| console.error('Error updating space tools:', err); | |
| } | |
| }; | |
| // Handler for copying MCP URL | |
| const handleCopyMcpUrl = async () => { | |
| const mcpUrl = `https://huggingface.co/mcp`; | |
| try { | |
| await navigator.clipboard.writeText(mcpUrl); | |
| } catch (err) { | |
| console.error('Failed to copy URL:', err); | |
| } | |
| }; | |
| // Handler for going to settings (switch to search tab) | |
| const handleGoToSettings = () => { | |
| window.open('https://huggingface.co/settings/mcp', '_blank'); | |
| }; | |
| /** should we use annotations / Title here? */ | |
| const searchTools = { | |
| paper_search: { | |
| id: PAPER_SEARCH_TOOL_ID, | |
| label: PAPER_SEARCH_TOOL_CONFIG.annotations.title, | |
| description: PAPER_SEARCH_TOOL_CONFIG.description, | |
| settings: { enabled: settings?.builtInTools?.includes(PAPER_SEARCH_TOOL_ID) ?? true }, | |
| }, | |
| space_search: { | |
| id: SPACE_SEARCH_TOOL_ID, | |
| label: SEMANTIC_SEARCH_TOOL_CONFIG.annotations.title, | |
| description: SEMANTIC_SEARCH_TOOL_CONFIG.description, | |
| settings: { enabled: settings?.builtInTools?.includes(SPACE_SEARCH_TOOL_ID) ?? true }, | |
| }, | |
| hub_repo_search: { | |
| id: REPO_SEARCH_TOOL_ID, | |
| label: REPO_SEARCH_TOOL_CONFIG.annotations.title, | |
| description: REPO_SEARCH_TOOL_CONFIG.description, | |
| settings: { enabled: settings?.builtInTools?.includes(REPO_SEARCH_TOOL_ID) ?? true }, | |
| }, | |
| create_repo: { | |
| id: CREATE_REPO_TOOL_ID, | |
| label: CREATE_REPO_TOOL_CONFIG.annotations.title, | |
| description: CREATE_REPO_TOOL_CONFIG.description, | |
| settings: { enabled: settings?.builtInTools?.includes(CREATE_REPO_TOOL_ID) ?? true }, | |
| }, | |
| hub_repo_details: { | |
| id: HUB_REPO_DETAILS_TOOL_ID, | |
| label: HUB_REPO_DETAILS_TOOL_CONFIG.annotations.title, | |
| description: HUB_REPO_DETAILS_TOOL_CONFIG.description, | |
| settings: { enabled: settings?.builtInTools?.includes(HUB_REPO_DETAILS_TOOL_ID) ?? true }, | |
| }, | |
| include_readme: { | |
| id: README_INCLUDE_FLAG, | |
| label: 'Allow README Include (flag)', | |
| description: | |
| 'Allows hub_repo_details to attach README content when the tool request explicitly opts in. Requires reconnect to take effect.', | |
| settings: { enabled: readmeFlagEnabled }, | |
| }, | |
| doc_semantic_search: { | |
| id: DOCS_SEMANTIC_SEARCH_TOOL_ID, | |
| label: DOCS_SEMANTIC_SEARCH_CONFIG.annotations.title, | |
| description: DOCS_SEMANTIC_SEARCH_CONFIG.description, | |
| settings: { enabled: settings?.builtInTools?.includes(DOCS_SEMANTIC_SEARCH_TOOL_ID) ?? true }, | |
| }, | |
| doc_fetch: { | |
| id: DOC_FETCH_TOOL_ID, | |
| label: DOC_FETCH_CONFIG.annotations.title, | |
| description: DOC_FETCH_CONFIG.description, | |
| settings: { enabled: settings?.builtInTools?.includes(DOC_FETCH_TOOL_ID) ?? true }, | |
| }, | |
| }; | |
| const spaceTools = { | |
| duplicate_space: { | |
| id: DUPLICATE_SPACE_TOOL_ID, | |
| label: DUPLICATE_SPACE_TOOL_CONFIG.annotations.title, | |
| description: DUPLICATE_SPACE_TOOL_CONFIG.description || 'Duplicate a Hugging Face Space to your account.', | |
| settings: { enabled: settings?.builtInTools?.includes(DUPLICATE_SPACE_TOOL_ID) ?? true }, | |
| }, | |
| space_files: { | |
| id: SPACE_FILES_TOOL_ID, | |
| label: SPACE_FILES_TOOL_CONFIG.annotations.title, | |
| description: | |
| SPACE_FILES_TOOL_CONFIG.description || 'List all files in a static Hugging Face Space with download URLs.', | |
| settings: { enabled: settings?.builtInTools?.includes(SPACE_FILES_TOOL_ID) ?? true }, | |
| }, | |
| no_gradio_images: { | |
| id: GRADIO_IMAGE_FILTER_FLAG, | |
| label: 'Disable Gradio Images (flag)', | |
| description: | |
| "Strips image responses from Gradio spaces. Mirrors the 'no_image_content' URL parameter and requires reconnect to take effect.", | |
| settings: { enabled: gradioImageFilterEnabled }, | |
| }, | |
| }; | |
| return ( | |
| <> | |
| <div className="min-h-screen p-4 sm:p-8 pb-20"> | |
| <div className="max-w-4xl mx-auto"> | |
| <Tabs defaultValue="metrics" className="w-full"> | |
| <TabsList className="mb-6 w-full overflow-x-auto flex-nowrap"> | |
| <TabsTrigger value="metrics" className="whitespace-nowrap"> | |
| 📊 Metrics | |
| </TabsTrigger> | |
| <TabsTrigger value="mcp" className="whitespace-nowrap"> | |
| 🔧 MCP | |
| </TabsTrigger> | |
| {!transportInfo?.externalApiMode && ( | |
| <TabsTrigger value="search" className="whitespace-nowrap"> | |
| 🔍 Search | |
| </TabsTrigger> | |
| )} | |
| {!transportInfo?.externalApiMode && ( | |
| <TabsTrigger value="spaces" className="whitespace-nowrap"> | |
| 🚀 Spaces | |
| </TabsTrigger> | |
| )} | |
| {!transportInfo?.externalApiMode && ( | |
| <TabsTrigger value="gradio" className="whitespace-nowrap"> | |
| 🚀 Gradio | |
| </TabsTrigger> | |
| )} | |
| <TabsTrigger value="home" className="whitespace-nowrap"> | |
| 🏠 Home | |
| </TabsTrigger> | |
| </TabsList> | |
| <TabsContent value="metrics" className="mt-0"> | |
| <TransportMetricsCard /> | |
| </TabsContent> | |
| <TabsContent value="mcp" className="mt-0"> | |
| <McpMethodsCard /> | |
| </TabsContent> | |
| {!transportInfo?.externalApiMode && ( | |
| <TabsContent value="search" className="mt-0"> | |
| <ToolsCard | |
| title="🤗 Hugging Face Search Tools (MCP)" | |
| description="Find and use Hugging Face and Community content." | |
| tools={searchTools} | |
| onToolToggle={handleToolToggle} | |
| /> | |
| </TabsContent> | |
| )} | |
| {!transportInfo?.externalApiMode && ( | |
| <TabsContent value="spaces" className="mt-0"> | |
| <ToolsCard | |
| title="🤗 Hugging Face Space Tools (MCP)" | |
| description="Manage and duplicate Hugging Face Spaces." | |
| tools={spaceTools} | |
| onToolToggle={handleToolToggle} | |
| /> | |
| </TabsContent> | |
| )} | |
| {!transportInfo?.externalApiMode && ( | |
| <TabsContent value="gradio" className="mt-0"> | |
| <GradioEndpointsCard | |
| spaceNames={spaceNames} | |
| spaceSubdomains={spaceSubdomains} | |
| enabledSpaces={enabledSpaces} | |
| onSpaceToolToggle={handleSpaceToolToggle} | |
| onSpaceToolNameChange={handleSpaceToolNameChange} | |
| onSpaceToolSubdomainChange={handleSpaceToolSubdomainChange} | |
| /> | |
| </TabsContent> | |
| )} | |
| <TabsContent value="home" className="mt-0"> | |
| {/* HF MCP Server Card */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>🤗 HF MCP Server</CardTitle> | |
| <CardDescription>Connect with AI assistants through the Model Context Protocol</CardDescription> | |
| </CardHeader> | |
| <CardContent className="space-y-6"> | |
| {/* What's MCP Section */} | |
| <div> | |
| <h3 className="text-sm font-semibold text-foreground mb-3">What's MCP?</h3> | |
| <p className="text-sm text-muted-foreground leading-relaxed"> | |
| The Model Context Protocol (MCP) is an open standard that enables AI assistants to securely | |
| connect to external data sources and tools. This HF MCP Server provides access to Hugging Face's | |
| ecosystem of models, datasets, and Spaces, allowing AI assistants to search, analyze, and interact | |
| with ML resources directly. | |
| </p> | |
| </div> | |
| <Separator /> | |
| {/* Tool Management Scope Information */} | |
| <div className="bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 rounded-lg p-4"> | |
| <div className="flex items-start space-x-3"> | |
| <div className="flex-shrink-0 mt-0.5"> | |
| <svg | |
| className="h-5 w-5 text-blue-600 dark:text-blue-400" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| fillRule="evenodd" | |
| d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" | |
| clipRule="evenodd" | |
| /> | |
| </svg> | |
| </div> | |
| <div> | |
| <h3 className="text-sm font-semibold text-blue-900 dark:text-blue-100 mb-2"> | |
| Tool Management Scope | |
| </h3> | |
| {(transportInfo?.externalApiMode || | |
| (transportInfo?.transport !== 'stdio' && !transportInfo?.externalApiMode)) && ( | |
| <p className="text-sm text-blue-700 dark:text-blue-300 leading-relaxed"> | |
| {transportInfo?.externalApiMode ? ( | |
| <> | |
| <strong>External API Mode:</strong> Tools are managed by an external API. Tool Selection | |
| Tabs are not available. | |
| </> | |
| ) : ( | |
| <> | |
| <strong>Shared Mode:</strong> Tool toggles in this interface affect{' '} | |
| <strong>all connected MCP server instances</strong> and control tool availability for | |
| all connected Hosts. | |
| </> | |
| )} | |
| </p> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| <Separator /> | |
| {/* Action Buttons */} | |
| <div className="flex flex-col gap-4"> | |
| <Button | |
| size="xl" | |
| onClick={handleCopyMcpUrl} | |
| className="w-full transition-all duration-200 active:bg-green-500 active:border-green-500 touch-manipulation" | |
| > | |
| <Copy className="mr-2 h-5 w-5" /> | |
| Copy MCP URL | |
| </Button> | |
| <Button | |
| size="xl" | |
| variant="outline" | |
| onClick={handleGoToSettings} | |
| className="w-full touch-manipulation" | |
| > | |
| <Settings className="mr-2 h-5 w-5" /> | |
| Go to Settings | |
| </Button> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </TabsContent> | |
| </Tabs> | |
| </div> | |
| </div> | |
| <ConnectionFooter isLoading={isLoading} error={error} transportInfo={transportInfo || { transport: 'unknown' }} /> | |
| </> | |
| ); | |
| } | |
| export default App; | |
Xet Storage Details
- Size:
- 16.5 kB
- Xet hash:
- d85cb238c1a1c20939321da8f4712c10d519e74849f00d71252a95267d70fb4c
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.