Skip to main content
Glama
evalstate

Hugging Face MCP Server

by evalstate
web-server.ts16.3 kB
import express, { type Express } from 'express'; import cors from 'cors'; import type { CorsOptions, CorsRequest, CorsOptionsDelegate } from 'cors'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import type { Server } from 'node:http'; import type { TransportInfo } from '../shared/transport-info.js'; import { settingsService, type SpaceTool } from '../shared/settings.js'; import { logger } from './utils/logger.js'; import type { BaseTransport } from './transport/base-transport.js'; import type { McpApiClient } from './utils/mcp-api-client.js'; import { formatMetricsForAPI } from '../shared/transport-metrics.js'; import { ALL_BUILTIN_TOOL_IDS } from '@llmindset/hf-mcp'; import { CORS_ALLOWED_ORIGINS, CORS_EXPOSED_HEADERS } from '../shared/constants.js'; import { apiMetrics } from './utils/api-metrics.js'; import { gradioMetrics } from './utils/gradio-metrics.js'; import { formatCacheMetricsForAPI } from './utils/gradio-cache.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export class WebServer { private app: Express; private server: Server | null = null; private transportInfo: TransportInfo = { transport: 'unknown', defaultHfTokenSet: false, externalApiMode: false, stdioClient: null, }; private localSharedToolStates: Map<string, boolean> = new Map(); private transport?: BaseTransport; private apiClient?: McpApiClient; constructor() { this.app = express() as Express; this.setupMiddleware(); } private setupMiddleware(): void { this.app.disable('x-powered-by'); this.app.set('trust proxy', true); // Inbound body size limit to prevent abuse this.app.use(express.json({ limit: '1mb' })); // Basic security headers (complementary to CORS) this.app.use((_, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Referrer-Policy', 'no-referrer'); if (process.env.HSTS === 'true') { res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); } next(); }); // Global CORS for all routes (API + MCP endpoints) // Simple exact-match allowlist with optional env override const envOrigins = (process.env.CORS_ALLOWED_ORIGINS || '') .split(',') .map((s) => s.trim()) .filter(Boolean); const normalize = (s: string) => s.replace(/\/+$/, ''); const envOriginsNorm = envOrigins.map(normalize); const allowedOrigins = (envOriginsNorm.length > 0 ? envOriginsNorm : CORS_ALLOWED_ORIGINS).map(normalize); // Support wildcard "*" to allow all origins explicitly let originSetting: CorsOptions['origin']; if (allowedOrigins.length === 1 && allowedOrigins[0] === '*') { originSetting = '*'; } else if (allowedOrigins.some((o) => o.includes('*'))) { // Support basic subdomain wildcards like "https://*.use-mcp.dev" or "*.use-mcp.dev" const exact = new Set(allowedOrigins.filter((o) => !o.includes('*'))); const patterns = allowedOrigins.filter((o) => o.includes('*')); originSetting = (requestOrigin: string | undefined, cb: (err: Error | null, allow?: boolean) => void) => { if (!requestOrigin) return cb(null, true); const reqOrigin = normalize(requestOrigin); if (exact.has(reqOrigin)) return cb(null, true); try { const u = new URL(requestOrigin); for (const p of patterns) { let scheme: string | undefined; let hostPattern = p; if (p.startsWith('http://') || p.startsWith('https://')) { scheme = p.split('://', 1)[0]; hostPattern = p.slice((scheme + '://').length); } // Only support leading wildcard: *.domain.tld if (!hostPattern.startsWith('*.')) continue; const suffix = hostPattern.slice(2); // domain.tld const host = u.hostname; if (scheme && u.protocol !== scheme + ':') continue; if (host.endsWith('.' + suffix) && host !== suffix) { return cb(null, true); } } return cb(null, false); } catch { return cb(null, false); } }; } else { originSetting = allowedOrigins; } const corsOptions: CorsOptions | CorsOptionsDelegate<CorsRequest> = { origin: originSetting, exposedHeaders: CORS_EXPOSED_HEADERS, }; this.app.use(cors(corsOptions)); // Ensure preflight requests succeed for any path this.app.options('*', cors(corsOptions)); } public getApp(): Express { return this.app; } public setTransportInfo(info: TransportInfo): void { this.transportInfo = info; } public setClientInfo(clientInfo: { name: string; version: string } | null): void { this.transportInfo.stdioClient = clientInfo; } public initializeToolStates(): void { // Initialize local shared tool states based on current settings to prevent initial event burst const currentSettings = settingsService.getSettings(); for (const toolId of ALL_BUILTIN_TOOL_IDS) { const isEnabled = currentSettings.builtInTools.includes(toolId); this.localSharedToolStates.set(toolId, isEnabled); } } public setTransport(transport: BaseTransport): void { this.transport = transport; } public setApiClient(apiClient: McpApiClient): void { this.apiClient = apiClient; } public getTransportInfo(): TransportInfo { return this.transportInfo; } public async start(port: number): Promise<void> { if (this.server) { throw new Error('Server is already running'); } return new Promise((resolve, reject) => { this.server = this.app .listen(port, () => { this.transportInfo.port = port; resolve(); }) .on('error', reject); }); } public async stop(): Promise<void> { if (!this.server) { return; } return new Promise((resolve, reject) => { this.server?.close((err) => { if (err) { reject(err); } else { this.server = null; resolve(); } }); }); } public async setupStaticFiles(isDevelopment: boolean): Promise<void> { if (isDevelopment) { // In development mode, use Vite's dev server middleware try { const { createServer: createViteServer } = await import('vite'); const rootDir = path.resolve(__dirname, '..', '..', '..', 'app', 'src', 'web'); // Create Vite server with proper HMR configuration const vite = await createViteServer({ configFile: path.resolve(__dirname, '..', '..', '..', 'app', 'vite.config.ts'), server: { middlewareMode: true, hmr: true, // Explicitly enable HMR }, appType: 'spa', root: rootDir, }); // Use Vite's middleware for dev server with HMR this.app.use(vite.middlewares); logger.info('Using Vite middleware in development mode with HMR enabled'); logger.info({ rootDir }, 'Vite root directory'); } catch (err) { logger.error({ err }, 'Error setting up Vite middleware'); throw err; } } else { // In production, serve static files const staticPath = path.join(__dirname, '..', 'web'); this.app.use(express.static(staticPath)); // Fallback to index.html for SPA routing this.app.get('*', (req, res) => { if (!req.path.startsWith('/api/')) { res.sendFile(path.join(staticPath, 'index.html')); } }); } } public setupApiRoutes(): void { // Transport info endpoint this.app.get('/api/transport', (_req, res) => { res.json(this.transportInfo); }); // Sessions endpoint this.app.get('/api/sessions', (_req, res) => { if (!this.transport) { res.json([]); return; } const sessions = this.transport.getSessions(); // For STDIO transport, also update the stdioClient info if we have a session if (this.transportInfo.transport === 'stdio' && sessions.length > 0) { const stdioSession = sessions[0]; if (stdioSession?.clientInfo && !this.transportInfo.stdioClient) { this.transportInfo.stdioClient = { name: stdioSession.clientInfo.name, version: stdioSession.clientInfo.version, }; } } res.json(sessions); }); // Transport metrics endpoint this.app.get('/api/transport-metrics', (req, res) => { if (!this.transport) { res.status(503).json({ error: 'Transport not initialized' }); return; } try { // Check for templog query parameter const tempLogParam = req.query.templog; let tempLogStatus: { activated: boolean; remaining: number; maxAllowed: number } | undefined = undefined; if (tempLogParam && this.transportInfo.transport === 'streamableHttpJson') { // Only activate for stateless transport with analytics mode // We need to import StatelessHttpTransport type or use method check const statelessTransport = this.transport as { activateTempLogging?: (count: number) => number; getTempLogStatus?: () => { enabled: boolean; remaining: number; maxAllowed: number }; }; if (statelessTransport.activateTempLogging && statelessTransport.getTempLogStatus) { const requestedCount = parseInt(tempLogParam as string, 10); if (!isNaN(requestedCount) && requestedCount > 0) { const activated = statelessTransport.activateTempLogging(requestedCount); tempLogStatus = { activated: true, remaining: activated, maxAllowed: statelessTransport.getTempLogStatus().maxAllowed, }; } } } // Get raw metrics from transport const metrics = this.transport.getMetrics(); // Determine if transport is stateless const isStateless = this.transportInfo.transport === 'streamableHttpJson'; // Get configuration for stateful transports const config = this.transport.getConfiguration(); // Get sessions (empty for stateless transports) const sessions = this.transport.getSessions().map((session) => { // Determine connection status: Connected, Distressed, or Disconnected const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); const hasRecentActivity = session.lastActivity > fiveMinutesAgo; const hasPingFailures = (session.pingFailures || 0) >= 1; // Note that from the WebUI this is provided as a courtesy. If a Client connects and // disconnects before the Client refresh it will not be shown in the Client list. let connectionStatus: 'Connected' | 'Distressed' | 'Disconnected'; if (!hasRecentActivity) { connectionStatus = 'Disconnected'; } else if (hasPingFailures) { connectionStatus = 'Distressed'; } else { connectionStatus = 'Connected'; } return { id: session.id, connectedAt: session.connectedAt.toISOString(), lastActivity: session.lastActivity.toISOString(), requestCount: session.requestCount, clientInfo: session.clientInfo, isConnected: hasRecentActivity, connectionStatus, pingFailures: session.pingFailures || 0, lastPingAttempt: session.lastPingAttempt?.toISOString(), ipAddress: session.ipAddress, }; }); // Format for API response const formattedMetrics = formatMetricsForAPI(metrics, this.transportInfo.transport, isStateless, sessions); // Add configuration if available if (!isStateless && config.staleCheckInterval && config.staleTimeout) { formattedMetrics.configuration = { heartbeatInterval: config.heartbeatInterval || 30000, staleCheckInterval: config.staleCheckInterval, staleTimeout: config.staleTimeout, pingEnabled: config.pingEnabled, pingInterval: config.pingInterval, pingFailureThreshold: config.pingFailureThreshold || 1, }; } // Add API metrics if in external API mode if (this.transportInfo.externalApiMode) { formattedMetrics.apiMetrics = apiMetrics.getMetrics(); } // Add Gradio metrics formattedMetrics.gradioMetrics = gradioMetrics.getMetrics(); // Add Gradio cache metrics formattedMetrics.gradioCacheMetrics = formatCacheMetricsForAPI(); // Add temp log status if it was activated or if we need to check current status const extendedMetrics = formattedMetrics as typeof formattedMetrics & { tempLogStatus?: unknown }; if (tempLogStatus) { extendedMetrics.tempLogStatus = tempLogStatus; } else if (this.transportInfo.transport === 'streamableHttpJson') { // Include current status even if not activating const statelessTransport = this.transport as { getTempLogStatus?: () => { enabled: boolean; remaining: number; maxAllowed: number }; }; if (statelessTransport.getTempLogStatus) { const status = statelessTransport.getTempLogStatus(); if (status.enabled) { extendedMetrics.tempLogStatus = status; } } } res.json(formattedMetrics); } catch (error) { logger.error({ error }, 'Error retrieving transport metrics'); res.status(500).json({ error: 'Failed to retrieve transport metrics' }); } }); // Settings endpoint this.app.get('/api/settings', (_req, res) => { res.json(settingsService.getSettings()); }); // Update tool settings endpoint this.app.post('/api/settings', express.json(), (req, res) => { const { builtInTools, spaceTools } = req.body as { builtInTools?: string[]; spaceTools?: SpaceTool[] }; let updatedSettings = settingsService.getSettings(); if (builtInTools !== undefined) { updatedSettings = settingsService.updateBuiltInTools(builtInTools); } if (spaceTools !== undefined) { updatedSettings = settingsService.updateSpaceTools(spaceTools); } // Enable or disable only the tools that actually changed state if (builtInTools !== undefined) { for (const toolId of ALL_BUILTIN_TOOL_IDS) { const shouldBeEnabled = builtInTools.includes(toolId); const currentlyEnabled = this.localSharedToolStates.get(toolId) ?? false; // Only update state and emit events if state actually changed if (currentlyEnabled !== shouldBeEnabled) { this.localSharedToolStates.set(toolId, shouldBeEnabled); // Emit event for MCP server instances if (this.apiClient) { this.apiClient.emit('toolStateChange', toolId, shouldBeEnabled); } logger.info(`Tool ${toolId} has been ${shouldBeEnabled ? 'enabled' : 'disabled'} via API`); } } } res.json(updatedSettings); }); // Gradio endpoints endpoint this.app.get('/api/gradio-endpoints', (_req, res) => { if (!this.apiClient) { res.json([]); return; } res.json(this.apiClient.getGradioEndpoints()); }); // Update Gradio endpoint status this.app.post('/api/gradio-endpoints/:index', express.json(), (req, res) => { const index = parseInt(req.params.index); const { enabled } = req.body as { enabled: boolean }; if (!this.apiClient) { res.status(500).json({ error: 'API client not initialized' }); return; } const endpoints = this.apiClient.getGradioEndpoints(); if (index < 0 || index >= endpoints.length) { res.status(404).json({ error: 'Endpoint not found' }); return; } // Update the state in the API client this.apiClient.updateGradioEndpointState(index, enabled); // Emit tool state change event for Gradio endpoint const endpoint = endpoints[index]; if (endpoint) { const toolId = `gradio_${endpoint.subdomain}`; this.apiClient.emit('toolStateChange', toolId, enabled); } // Get the updated endpoint const updatedEndpoint = endpoints[index]; res.json(updatedEndpoint); }); // Update Gradio endpoint this.app.put('/api/gradio-endpoints/:index', express.json(), (req, res) => { const index = parseInt(req.params.index); const { name, subdomain, id, emoji } = req.body as { name: string; subdomain: string; id?: string; emoji?: string; }; if (!this.apiClient) { res.status(500).json({ error: 'API client not initialized' }); return; } const endpoints = this.apiClient.getGradioEndpoints(); if (index < 0 || index >= endpoints.length) { res.status(404).json({ error: 'Endpoint not found' }); return; } // Validate required fields if (!name || !subdomain) { res.status(400).json({ error: 'Name and subdomain are required' }); return; } // Update the endpoint in the API client const updatedEndpoint = { name, subdomain, id, emoji }; this.apiClient.updateGradioEndpoint(index, updatedEndpoint); res.json(updatedEndpoint); }); } }

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