Skip to main content
Glama
evalstate

Hugging Face MCP Server

by evalstate
gradio-utils.ts•9.55 kB
/** * Utility functions for handling Gradio endpoint detection and configuration */ import type { SpaceTool } from '../../shared/settings.js'; import { GRADIO_PREFIX, GRADIO_PRIVATE_PREFIX } from '../../shared/constants.js'; import { logger } from './logger.js'; import { getGradioSpaces } from './gradio-discovery.js'; /** * Determines if a tool name represents a Gradio endpoint * Gradio tools follow the pattern: gr<number>_<name> or grp<number>_<name> * * @param toolName - The name of the tool to check * @returns true if the tool is a Gradio endpoint, false otherwise * * @example * isGradioTool('gr1_evalstate_flux1_schnell') // true * isGradioTool('grp2_private_tool') // true * isGradioTool('hf_doc_search') // false * isGradioTool('regular_tool') // false */ export function isGradioTool(toolName: string): boolean { // Gradio tools follow pattern: gr<number>_<name> or grp<number>_<name> return /^grp?\d+_/.test(toolName); } /** * Creates a Gradio tool name based on tool name, index, and privacy status * This is the core logic used throughout the application for generating tool names * * @param toolName - The tool name (e.g., "flux1_schnell", "EasyGhibli") * @param index - Zero-based index position (will be converted to 1-based) * @param isPrivate - Whether this is a private space (determines gr vs grp prefix) * @param toolIndex - Optional tool index within the endpoint for uniqueness when truncating * @returns The generated tool name following Gradio naming convention * * @example * createGradioToolName('flux1_schnell', 0, false) // 'gr1_flux1_schnell' * createGradioToolName('EasyGhibli', 1, false) // 'gr2_easyghibli' * createGradioToolName('private.model', 2, true) // 'grp3_private_model' */ export function createGradioToolName( toolName: string, index: number, isPrivate: boolean | undefined, toolIndex?: number ): string { // Choose prefix based on privacy status const prefix = isPrivate ? GRADIO_PRIVATE_PREFIX : GRADIO_PREFIX; const indexStr = (index + 1).toString(); // Calculate available space for the sanitized name (49 - prefix - index - underscore) const maxNameLength = 49 - prefix.length - indexStr.length - 1; // Sanitize the tool name: replace special characters with underscores, normalize multiple underscores, and lowercase let sanitizedName = toolName ? toolName .replace(/[-\s.]+/g, '_') // Replace special chars with underscores .toLowerCase() : 'unknown'; // Handle based on length if (sanitizedName.length > maxNameLength) { // Over limit: insert tool index at beginning if provided, then truncate if (toolIndex !== undefined) { // Insert tool index after the underscore: gr1_0_toolname const toolIndexPrefix = `${toolIndex}_`; const availableForName = maxNameLength - toolIndexPrefix.length; // Keep first 20 chars, add underscore, then keep as many chars from the end as possible const keepFromEnd = availableForName - 20 - 1; // -1 for the underscore sanitizedName = toolIndexPrefix + sanitizedName.substring(0, 20) + '_' + sanitizedName.slice(-keepFromEnd); } else { // No tool index, just do middle truncation as before const keepFromEnd = maxNameLength - 20 - 1; // -1 for the underscore sanitizedName = sanitizedName.substring(0, 20) + '_' + sanitizedName.slice(-keepFromEnd); } } // Under limit: keep as-is, no normalization // Create tool name: {prefix}{1-based-index}_{sanitized_name} return `${prefix}${indexStr}_${sanitizedName}`; } /** * Parsed Gradio space ID before subdomain resolution */ export interface ParsedGradioSpace { name: string; // e.g., "microsoft/Florence-2-large" } /** * Parses the gradio parameter to extract space IDs. * Does NOT construct subdomains - they must be fetched from the HuggingFace API. * * @param gradioParam - Comma-separated list of space IDs (e.g., "microsoft/Florence-2-large,acme/foo") * @returns Array of parsed space IDs * * @example * parseGradioSpaceIds('microsoft/Florence-2-large') // [{ name: 'microsoft/Florence-2-large' }] * parseGradioSpaceIds('microsoft/Florence-2-large,acme/foo') // [{ name: 'microsoft/Florence-2-large' }, { name: 'acme/foo' }] * parseGradioSpaceIds('none') // [] */ export function parseGradioSpaceIds(gradioParam: string): ParsedGradioSpace[] { const spaces: ParsedGradioSpace[] = []; const trimmed = gradioParam.trim(); // Treat special sentinel "none" as "disable gradio" if (trimmed.toLowerCase() === 'none') { return spaces; } const entries = gradioParam .split(',') .map((s) => s.trim()) .filter((s) => s.length > 0); for (const entry of entries) { // Skip explicit "none" entries within a list if (entry.toLowerCase() === 'none') { continue; } // Validate exactly one slash in the middle (username/space-name format) const slashCount = (entry.match(/\//g) || []).length; const slashIndex = entry.indexOf('/'); const isValidFormat = slashCount === 1 && slashIndex > 0 && slashIndex < entry.length - 1; if (!isValidFormat) { logger.warn( `Skipping invalid gradio entry "${entry}": must contain exactly one slash with content on both sides (format: username/space-name)` ); continue; } spaces.push({ name: entry }); logger.debug(`Parsed gradio space ID: ${entry}`); } return spaces; } /** * Fetches real subdomains from the HuggingFace API for the given space IDs. * Now uses the optimized discovery API with caching for better performance. * * @param spaceIds - Array of space IDs to fetch subdomains for * @param hfToken - Optional HuggingFace token for authentication * @param hubUrl - Optional hub URL for custom HuggingFace instances (not used with new API) * @returns Array of SpaceTool objects with real subdomains from the API * * @example * const spaces = [{ name: 'microsoft/Florence-2-large' }]; * const tools = await fetchGradioSubdomains(spaces, 'hf_token'); * // Returns: [{ _id: 'gradio_...', name: 'microsoft/Florence-2-large', subdomain: 'microsoft-florence-2-large', emoji: 'đź”§' }] */ export async function fetchGradioSubdomains(spaceIds: ParsedGradioSpace[], hfToken?: string): Promise<SpaceTool[]> { if (spaceIds.length === 0) { return []; } const spaceNames = spaceIds.map((s) => s.name); // Use the new optimized discovery API with caching // Skip schemas since we only need metadata here const spaces = await getGradioSpaces(spaceNames, hfToken, { skipSchemas: true }); // Convert to SpaceTool format const spaceTools: SpaceTool[] = spaces.map((space) => ({ _id: space._id, name: space.name, subdomain: space.subdomain, emoji: space.emoji, })); logger.debug( { requested: spaceIds.length, successful: spaceTools.length, }, 'Fetched Gradio subdomains' ); return spaceTools; } /** * Parses gradio parameter and fetches real subdomains from HuggingFace API. * This is the main entry point that combines parsing and API fetching. * * @param gradioParam - Comma-separated list of space IDs * @param hfToken - Optional HuggingFace token for authentication * @param hubUrl - Optional hub URL for custom HuggingFace instances * @returns Array of SpaceTool objects with real subdomains * * @example * const tools = await parseAndFetchGradioEndpoints('microsoft/Florence-2-large', 'hf_token'); * // Returns array of SpaceTool objects with real subdomains from HuggingFace API */ export async function parseAndFetchGradioEndpoints(gradioParam: string, hfToken?: string): Promise<SpaceTool[]> { const parsedSpaces = parseGradioSpaceIds(gradioParam); if (parsedSpaces.length === 0) { return []; } return fetchGradioSubdomains(parsedSpaces, hfToken); } /** * Input parameters for determining if gradio_files tool should be registered */ export interface GradioFilesEligibilityParams { /** Number of configured Gradio spaces */ gradioSpaceCount: number; /** List of enabled built-in tool IDs */ builtInTools: string[]; /** The tool ID that represents dynamic_space */ dynamicSpaceToolId: string; /** Whether the user's gradio-files dataset exists */ datasetExists: boolean; } /** * Pure function to determine if the gradio_files tool should be registered. * * The tool should be registered when: * 1. The user's gradio-files dataset exists, AND * 2. Either Gradio spaces are configured OR dynamic_space tool is enabled * * @param params - The eligibility parameters * @returns true if gradio_files tool should be registered * * @example * shouldRegisterGradioFilesTool({ * gradioSpaceCount: 0, * builtInTools: ['dynamic_space'], * dynamicSpaceToolId: 'dynamic_space', * datasetExists: true, * }) // true - dynamic_space enabled with existing dataset * * @example * shouldRegisterGradioFilesTool({ * gradioSpaceCount: 2, * builtInTools: [], * dynamicSpaceToolId: 'dynamic_space', * datasetExists: true, * }) // true - Gradio spaces configured with existing dataset * * @example * shouldRegisterGradioFilesTool({ * gradioSpaceCount: 0, * builtInTools: [], * dynamicSpaceToolId: 'dynamic_space', * datasetExists: true, * }) // false - no Gradio spaces and dynamic_space not enabled */ export function shouldRegisterGradioFilesTool(params: GradioFilesEligibilityParams): boolean { const { gradioSpaceCount, builtInTools, dynamicSpaceToolId, datasetExists } = params; if (!datasetExists) { return false; } const hasGradioSpaces = gradioSpaceCount > 0; const isDynamicSpaceEnabled = builtInTools.includes(dynamicSpaceToolId); return hasGradioSpaces || isDynamicSpaceEnabled; }

Latest Blog Posts

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/evalstate/hf-mcp-server'

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