Skip to main content
Glama
evalstate

Hugging Face MCP Server

by evalstate
transport-metrics.ts21.3 kB
import { createHash } from 'node:crypto'; import type { TransportType } from './constants.js'; /** * Core metrics data tracked by each transport */ export interface TransportMetrics { startupTime: Date; // Connection metrics connections: { active: number | 'stateless'; total: number; authenticated: number; denied: number; anonymous: number; unauthorized?: number; // 401 errors cleaned?: number; // Only for stateful transports uniqueIps?: number; // Unique IP addresses that have connected }; // Session lifecycle metrics (only for stateful transports) sessions?: { created: number; resumedFailed: number; deleted: number; }; // Request metrics requests: { total: number; averagePerMinute: number; lastMinute: number; lastHour: number; last3Hours: number; }; // Ping metrics (for stateful transports) pings?: { sent: number; successful: number; failed: number; lastPingTime?: Date; }; // Error metrics errors: { expected: number; // 4xx errors unexpected: number; // 5xx errors lastError?: { type: string; message: string; timestamp: Date; }; }; // Client identity aggregation (name version) clients: Map<string, ClientMetrics>; // Method metrics methods: Map<string, MethodMetrics>; // Static page hits (for stateless transport) staticPageHits200?: number; staticPageHits405?: number; } /** * Metrics per client identity (name@version) */ export interface ClientMetrics { name: string; version: string; requestCount: number; firstSeen: Date; lastSeen: Date; isConnected: boolean; activeConnections: number; totalConnections: number; toolCallCount: number; newIpCount: number; anonCount: number; uniqueAuthCount: number; } /** * Metrics per client for a specific method */ export interface ClientMethodMetrics { clientName: string; count: number; } /** * Metrics per MCP method */ export interface MethodMetrics { method: string; count: number; firstCalled: Date; lastCalled: Date; averageResponseTime?: number; errors: number; byClient: Map<string, ClientMethodMetrics>; } /** * API call metrics for external HuggingFace API calls */ export interface ApiCallMetrics { anonymous: number; authenticated: number; unauthorized: number; // 401 forbidden: number; // 403 } /** * Gradio tool call metrics */ export interface GradioToolMetrics { success: number; failure: number; byTool: Record<string, { success: number; failure: number }>; } /** * Gradio cache metrics for space metadata and schemas */ export interface GradioCacheMetrics { spaceMetadata: { hits: number; misses: number; hitRate: number; // percentage etagRevalidations: number; cacheSize: number; }; schemas: { hits: number; misses: number; hitRate: number; // percentage cacheSize: number; }; totalHits: number; totalMisses: number; overallHitRate: number; // percentage } /** * API response format for transport metrics */ export interface SessionData { id: string; connectedAt: string; // ISO date string lastActivity: string; // ISO date string clientInfo?: { name: string; version: string; }; isConnected: boolean; connectionStatus?: 'Connected' | 'Distressed' | 'Disconnected'; pingFailures?: number; lastPingAttempt?: string; // ISO date string ipAddress?: string; } export interface TransportMetricsResponse { transport: TransportType; isStateless: boolean; startupTime: string; // ISO date string currentTime: string; // ISO date string uptimeSeconds: number; // Configuration settings (only for stateful transports) configuration?: { heartbeatInterval: number; // milliseconds staleCheckInterval: number; // milliseconds staleTimeout: number; // milliseconds pingEnabled?: boolean; pingInterval?: number; // milliseconds pingFailureThreshold?: number; // number of failed pings before distressed }; connections: { active: number | 'stateless'; total: number; cleaned?: number; }; // Session lifecycle metrics (only for stateful transports) sessionLifecycle?: { created: number; resumedFailed: number; deleted: number; }; requests: { total: number; averagePerMinute: number; lastMinute: number; lastHour: number; last3Hours: number; }; // Static page hits (stateless transport only) staticPageHits200?: number; staticPageHits405?: number; pings?: { sent: number; successful: number; failed: number; lastPingTime?: string; // ISO date string }; errors: { expected: number; unexpected: number; lastError?: { type: string; message: string; timestamp: string; // ISO date string }; }; clients: Array<{ name: string; version: string; requestCount: number; firstSeen: string; // ISO date string lastSeen: string; // ISO date string isConnected: boolean; activeConnections: number; totalConnections: number; toolCallCount: number; newIpCount: number; anonCount: number; uniqueAuthCount: number; }>; sessions: SessionData[]; methods: Array<{ method: string; count: number; firstCalled: string; // ISO date string lastCalled: string; // ISO date string averageResponseTime?: number; // milliseconds errors: number; errorRate: number; // percentage byClient: Array<{ clientName: string; count: number; }>; }>; // API call metrics (only shown in external API mode) apiMetrics?: ApiCallMetrics; // Gradio tool call metrics gradioMetrics?: GradioToolMetrics; // Gradio cache metrics (discovery optimization) gradioCacheMetrics?: GradioCacheMetrics; } /** * Convert internal metrics to API response format */ export function formatMetricsForAPI( metrics: TransportMetrics, transport: TransportType, isStateless: boolean, sessions: SessionData[] = [] ): TransportMetricsResponse { const currentTime = new Date(); const uptimeSeconds = Math.floor((currentTime.getTime() - metrics.startupTime.getTime()) / 1000); return { transport, isStateless, startupTime: metrics.startupTime.toISOString(), currentTime: currentTime.toISOString(), uptimeSeconds, connections: metrics.connections, sessionLifecycle: metrics.sessions, requests: metrics.requests, pings: metrics.pings ? { ...metrics.pings, lastPingTime: metrics.pings.lastPingTime?.toISOString(), } : undefined, errors: { ...metrics.errors, lastError: metrics.errors.lastError ? { ...metrics.errors.lastError, timestamp: metrics.errors.lastError.timestamp.toISOString(), } : undefined, }, clients: Array.from(metrics.clients.values()).map((client) => ({ ...client, firstSeen: client.firstSeen.toISOString(), lastSeen: client.lastSeen.toISOString(), })), sessions, staticPageHits200: metrics.staticPageHits200, staticPageHits405: metrics.staticPageHits405, methods: Array.from(metrics.methods.values()).map((method) => ({ ...method, firstCalled: method.firstCalled.toISOString(), lastCalled: method.lastCalled.toISOString(), errorRate: method.count > 0 ? (method.errors / method.count) * 100 : 0, byClient: Array.from(method.byClient.values()).map((client) => ({ clientName: client.clientName, count: client.count, })), })), }; } /** * Check if a method name is an initialization request */ export function isInitializeRequest(method: string): boolean { return method === 'initialize'; } /** * Create a new empty metrics object */ export function createEmptyMetrics(): TransportMetrics { return { startupTime: new Date(), connections: { active: 0, total: 0, authenticated: 0, denied: 0, anonymous: 0, }, sessions: { created: 0, resumedFailed: 0, deleted: 0, }, requests: { total: 0, averagePerMinute: 0, lastMinute: 0, lastHour: 0, last3Hours: 0, }, errors: { expected: 0, unexpected: 0, }, clients: new Map(), methods: new Map(), staticPageHits200: 0, staticPageHits405: 0, }; } /** * Get client identity key from name and version */ export function getClientKey(name: string, version: string): string { return `${name} ${version}`; } /** * Rolling window counter for tracking metrics over time periods */ class RollingWindowCounter { private readonly buckets: number[]; private currentBucket: number = 0; private lastRotation: number = Date.now(); constructor(windowMinutes: number) { this.buckets = new Array(windowMinutes).fill(0); } increment(): void { this.rotateBucketsIfNeeded(); const current = this.buckets[this.currentBucket] ?? 0; this.buckets[this.currentBucket] = current + 1; } getCount(): number { this.rotateBucketsIfNeeded(); return this.buckets.reduce((sum, count) => sum + count, 0); } private rotateBucketsIfNeeded(): void { const now = Date.now(); const minutesPassed = Math.floor((now - this.lastRotation) / 60000); // Rotate and clear buckets for each minute that has passed for (let i = 0; i < minutesPassed && i < this.buckets.length; i++) { this.currentBucket = (this.currentBucket + 1) % this.buckets.length; this.buckets[this.currentBucket] = 0; } // If all buckets need to be cleared (time gap > window size) if (minutesPassed >= this.buckets.length) { this.buckets.fill(0); this.currentBucket = 0; } if (minutesPassed > 0) { // Update to the last minute boundary that was processed this.lastRotation = this.lastRotation + (minutesPassed * 60000); } } } /** * Hash auth tokens before counting them to avoid storing raw secrets. */ function hashToken(token: string): string { return createHash('sha256').update(token).digest('hex'); } /** * Centralized metrics counter for transport operations */ export class MetricsCounter { private metrics: TransportMetrics; private rollingMinute: RollingWindowCounter; private rollingHour: RollingWindowCounter; private rolling3Hours: RollingWindowCounter; private uniqueIps: Set<string>; private clientIps: Map<string, Set<string>>; // Map of clientKey -> Set of IPs private clientAuthHashes: Map<string, Set<string>>; // Map of clientKey -> Set of auth token hashes constructor() { this.metrics = createEmptyMetrics(); this.rollingMinute = new RollingWindowCounter(1); this.rollingHour = new RollingWindowCounter(60); this.rolling3Hours = new RollingWindowCounter(180); this.uniqueIps = new Set(); this.clientIps = new Map(); this.clientAuthHashes = new Map(); } /** * Get the underlying metrics data */ getMetrics(): TransportMetrics { // Calculate rates (requests per minute) for each window // Note: All values represent "requests per minute" calculated over their respective windows this.metrics.requests.lastMinute = this.rollingMinute.getCount(); // Requests in last 1 minute (already per minute) // For longer windows, divide total count by window size to get per-minute rate const hourCount = this.rollingHour.getCount(); const threeHourCount = this.rolling3Hours.getCount(); // Calculate per-minute rates for the longer windows this.metrics.requests.lastHour = Math.round((hourCount / 60) * 100) / 100; // Requests per minute over last hour this.metrics.requests.last3Hours = Math.round((threeHourCount / 180) * 100) / 100; // Requests per minute over last 3 hours // Update unique IPs count this.metrics.connections.uniqueIps = this.uniqueIps.size; return this.metrics; } /** * Track a new request received by the transport */ trackRequest(): void { this.metrics.requests.total++; this.updateRequestsPerMinute(); // Update rolling window counters this.rollingMinute.increment(); this.rollingHour.increment(); this.rolling3Hours.increment(); // Calculate rates (requests per minute) for each window // Note: All values represent "requests per minute" calculated over their respective windows this.metrics.requests.lastMinute = this.rollingMinute.getCount(); // Requests in last 1 minute (already per minute) // For longer windows, divide total count by window size to get per-minute rate const hourCount = this.rollingHour.getCount(); const threeHourCount = this.rolling3Hours.getCount(); // Calculate per-minute rates for the longer windows this.metrics.requests.lastHour = Math.round((hourCount / 60) * 100) / 100; // Requests per minute over last hour this.metrics.requests.last3Hours = Math.round((threeHourCount / 180) * 100) / 100; // Requests per minute over last 3 hours } /** * Track an error in the transport */ trackError(statusCode?: number, error?: Error): void { if (statusCode && statusCode >= 400 && statusCode < 500) { this.metrics.errors.expected++; } else { this.metrics.errors.unexpected++; } if (error) { this.metrics.errors.lastError = { type: error.constructor.name, message: error.message, timestamp: new Date(), }; } } /** * Track a new connection established (global counter) */ trackNewConnection(): void { this.metrics.connections.total++; } /** * Track an IP address */ trackIpAddress(ipAddress: string | undefined): void { if (ipAddress) { this.uniqueIps.add(ipAddress); } } /** * Track an IP address for a specific client */ trackClientIpAddress(ipAddress: string | undefined, clientInfo?: { name: string; version: string }): void { // Always track globally this.trackIpAddress(ipAddress); // Track per-client if client info is available if (ipAddress && clientInfo) { const clientKey = getClientKey(clientInfo.name, clientInfo.version); const clientMetrics = this.metrics.clients.get(clientKey); if (clientMetrics) { // Get or create the IP set for this client let clientIpSet = this.clientIps.get(clientKey); if (!clientIpSet) { clientIpSet = new Set(); this.clientIps.set(clientKey, clientIpSet); } // Check if this is a new IP for this client const isNewIp = !clientIpSet.has(ipAddress); if (isNewIp) { clientIpSet.add(ipAddress); clientMetrics.newIpCount++; } } } } /** * Track auth status for a specific client */ trackClientAuth(authToken: string | undefined, clientInfo?: { name: string; version: string }): void { if (!clientInfo) return; const clientKey = getClientKey(clientInfo.name, clientInfo.version); const clientMetrics = this.metrics.clients.get(clientKey); if (clientMetrics) { if (!authToken) { // Anonymous request clientMetrics.anonCount++; } else { // Authenticated request - hash the token for privacy const tokenHash = hashToken(authToken); // Get or create the auth hash set for this client let clientAuthSet = this.clientAuthHashes.get(clientKey); if (!clientAuthSet) { clientAuthSet = new Set(); this.clientAuthHashes.set(clientKey, clientAuthSet); } // Check if this is a new auth token for this client const isNewAuth = !clientAuthSet.has(tokenHash); if (isNewAuth) { clientAuthSet.add(tokenHash); clientMetrics.uniqueAuthCount++; } } } } /** * Update active connection count */ updateActiveConnections(count: number): void { this.metrics.connections.active = count; } /** * Track a session that was cleaned up/removed */ trackSessionCleaned(): void { if (!this.metrics.connections.cleaned) { this.metrics.connections.cleaned = 0; } this.metrics.connections.cleaned++; } /** * Associate a session with a client identity when client info becomes available */ associateSessionWithClient(clientInfo: { name: string; version: string }): void { const clientKey = getClientKey(clientInfo.name, clientInfo.version); let clientMetrics = this.metrics.clients.get(clientKey); if (!clientMetrics) { clientMetrics = { name: clientInfo.name, version: clientInfo.version, requestCount: 0, firstSeen: new Date(), lastSeen: new Date(), isConnected: true, activeConnections: 1, totalConnections: 1, toolCallCount: 0, newIpCount: 0, anonCount: 0, uniqueAuthCount: 0, }; this.metrics.clients.set(clientKey, clientMetrics); } else { clientMetrics.lastSeen = new Date(); clientMetrics.isConnected = true; clientMetrics.activeConnections++; clientMetrics.totalConnections++; } } /** * Update client activity when a request is made */ updateClientActivity(clientInfo?: { name: string; version: string }): void { if (!clientInfo) return; const clientKey = getClientKey(clientInfo.name, clientInfo.version); const clientMetrics = this.metrics.clients.get(clientKey); if (clientMetrics) { clientMetrics.requestCount++; clientMetrics.lastSeen = new Date(); } } /** * Mark a client connection as disconnected */ disconnectClient(clientInfo?: { name: string; version: string }): void { if (!clientInfo) return; const clientKey = getClientKey(clientInfo.name, clientInfo.version); const clientMetrics = this.metrics.clients.get(clientKey); if (clientMetrics && clientMetrics.activeConnections > 0) { clientMetrics.activeConnections--; if (clientMetrics.activeConnections === 0) { clientMetrics.isConnected = false; } } } /** * Track a method call */ trackMethod( method: string | null, responseTime?: number, isError: boolean = false, clientInfo?: { name: string; version: string } ): void { if (!method) return; let methodMetrics = this.metrics.methods.get(method); if (!methodMetrics) { methodMetrics = { method, count: 0, firstCalled: new Date(), lastCalled: new Date(), errors: 0, byClient: new Map(), }; this.metrics.methods.set(method, methodMetrics); } methodMetrics.count++; methodMetrics.lastCalled = new Date(); // Track per-client breakdown if (clientInfo) { const clientName = clientInfo.name; let clientMethodMetrics = methodMetrics.byClient.get(clientName); if (!clientMethodMetrics) { clientMethodMetrics = { clientName, count: 0, }; methodMetrics.byClient.set(clientName, clientMethodMetrics); } clientMethodMetrics.count++; // If this is a tool call, increment the client's tool call count if (method.startsWith('tools/call:')) { const clientKey = getClientKey(clientInfo.name, clientInfo.version); const clientMetrics = this.metrics.clients.get(clientKey); if (clientMetrics) { clientMetrics.toolCallCount++; } } } if (isError) { methodMetrics.errors++; } // Update average response time if provided if (responseTime !== undefined && !isError) { const successfulCalls = methodMetrics.count - methodMetrics.errors; if (successfulCalls === 1) { methodMetrics.averageResponseTime = responseTime; } else { const currentAvg = methodMetrics.averageResponseTime || 0; const totalTime = currentAvg * (successfulCalls - 1) + responseTime; methodMetrics.averageResponseTime = totalTime / successfulCalls; } } } /** * Track a ping being sent */ trackPingSent(): void { if (!this.metrics.pings) { this.metrics.pings = { sent: 0, successful: 0, failed: 0, }; } this.metrics.pings.sent++; } /** * Track a successful ping response */ trackPingSuccess(): void { if (!this.metrics.pings) { this.metrics.pings = { sent: 0, successful: 0, failed: 0, }; } this.metrics.pings.successful++; this.metrics.pings.lastPingTime = new Date(); } /** * Track a failed ping */ trackPingFailed(): void { if (!this.metrics.pings) { this.metrics.pings = { sent: 0, successful: 0, failed: 0, }; } this.metrics.pings.failed++; } /** * Track a static page hit with status code */ trackStaticPageHit(statusCode: number): void { if (statusCode === 200) { this.metrics.staticPageHits200 = (this.metrics.staticPageHits200 || 0) + 1; } else if (statusCode === 405) { this.metrics.staticPageHits405 = (this.metrics.staticPageHits405 || 0) + 1; } } /** * Track authenticated connection */ trackAuthenticatedConnection(): void { this.metrics.connections.authenticated++; } /** * Track anonymous connection */ trackAnonymousConnection(): void { this.metrics.connections.anonymous++; } /** * Track unauthorized connection (401) */ trackUnauthorizedConnection(): void { if (!this.metrics.connections.unauthorized) { this.metrics.connections.unauthorized = 0; } this.metrics.connections.unauthorized++; } /** * Track a new session being created */ trackSessionCreated(): void { if (this.metrics.sessions) { this.metrics.sessions.created++; } } /** * Track a failed session resumption attempt */ trackSessionResumeFailed(): void { if (this.metrics.sessions) { this.metrics.sessions.resumedFailed++; } } /** * Track a session being deleted/cleaned up */ trackSessionDeleted(): void { if (this.metrics.sessions) { this.metrics.sessions.deleted++; } } /** * Update requests per minute calculation */ private updateRequestsPerMinute(): void { const now = Date.now(); const startupTime = this.metrics.startupTime.getTime(); const uptimeMinutes = (now - startupTime) / (1000 * 60); this.metrics.requests.averagePerMinute = uptimeMinutes > 0 ? Math.round((this.metrics.requests.total / uptimeMinutes) * 100) / 100 : 0; } }

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