Skip to main content
Glama
rateLimit.js4.13 kB
// rateLimit.js - Rate Limiting and Resource Management const config = require('./config'); const audit = require('./audit'); class RateLimiter { constructor() { this.requestMap = new Map(); // clientId -> array of timestamps this.activeCaptureCount = 0; this.maxConcurrent = config.security.maxConcurrentCaptures; this.enabled = config.security.rateLimit.enabled; this.maxRequests = config.security.rateLimit.maxRequests; this.windowMs = config.security.rateLimit.windowMs; // Cleanup old entries periodically setInterval(() => this.cleanup(), 60000); // Every minute } /** * Check if client has exceeded rate limit */ checkRateLimit(clientId) { if (!this.enabled) return true; const now = Date.now(); const clientRequests = this.requestMap.get(clientId) || []; // Remove old requests outside the time window const validRequests = clientRequests.filter( timestamp => now - timestamp < this.windowMs ); // Check if limit exceeded if (validRequests.length >= this.maxRequests) { audit.logRateLimitViolation(clientId, 'API'); throw new Error( `Rate limit exceeded: Maximum ${this.maxRequests} requests per ${this.windowMs/1000} seconds` ); } // Add current request validRequests.push(now); this.requestMap.set(clientId, validRequests); return true; } /** * Acquire a capture slot (for concurrent capture limiting) */ async acquireCaptureSlot(clientId) { if (this.activeCaptureCount >= this.maxConcurrent) { audit.logSecurityEvent( 'CAPTURE_LIMIT_REACHED', { activeCaptureCount: this.activeCaptureCount, maxConcurrent: this.maxConcurrent }, clientId ); throw new Error( `Too many concurrent captures: Maximum ${this.maxConcurrent} allowed, ${this.activeCaptureCount} active` ); } this.activeCaptureCount++; console.error(`[RESOURCE] Capture slot acquired (${this.activeCaptureCount}/${this.maxConcurrent})`); } /** * Release a capture slot */ releaseCaptureSlot() { if (this.activeCaptureCount > 0) { this.activeCaptureCount--; console.error(`[RESOURCE] Capture slot released (${this.activeCaptureCount}/${this.maxConcurrent})`); } } /** * Get current rate limit status for a client */ getStatus(clientId) { const now = Date.now(); const clientRequests = this.requestMap.get(clientId) || []; const validRequests = clientRequests.filter( timestamp => now - timestamp < this.windowMs ); return { requestsInWindow: validRequests.length, maxRequests: this.maxRequests, windowMs: this.windowMs, remaining: Math.max(0, this.maxRequests - validRequests.length), resetAt: validRequests.length > 0 ? new Date(validRequests[0] + this.windowMs).toISOString() : null, activeCaptureCount: this.activeCaptureCount, maxConcurrentCaptures: this.maxConcurrent }; } /** * Cleanup old entries from memory */ cleanup() { const now = Date.now(); let cleaned = 0; for (const [clientId, requests] of this.requestMap.entries()) { const validRequests = requests.filter( timestamp => now - timestamp < this.windowMs ); if (validRequests.length === 0) { this.requestMap.delete(clientId); cleaned++; } else { this.requestMap.set(clientId, validRequests); } } if (cleaned > 0) { console.error(`[RESOURCE] Cleaned ${cleaned} expired rate limit entries`); } } /** * Reset rate limit for a specific client (admin function) */ reset(clientId) { this.requestMap.delete(clientId); console.error(`[RESOURCE] Rate limit reset for client: ${clientId}`); } /** * Reset all rate limits (admin function) */ resetAll() { const size = this.requestMap.size; this.requestMap.clear(); console.error(`[RESOURCE] All rate limits reset (${size} clients)`); } } // Export singleton instance module.exports = new RateLimiter();

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/anishphilip012git/WireMCP-Secure'

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