Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
RateLimiter.tsโ€ข5.63 kB
/** * RateLimiter - Implements rate limiting for API calls to prevent abuse * * Features: * - Token bucket algorithm for flexible rate limiting * - Configurable limits per time window * - Memory-efficient implementation * - Thread-safe for concurrent requests */ export interface RateLimiterConfig { maxRequests: number; // Maximum requests allowed windowMs: number; // Time window in milliseconds minDelayMs?: number; // Minimum delay between requests (optional) } export interface RateLimitStatus { allowed: boolean; retryAfterMs?: number; remainingTokens: number; resetTime: Date; } export class RateLimiter { private tokens: number; private lastRefill: number; private lastRequest: number; private readonly maxTokens: number; private readonly refillRate: number; private readonly minDelay: number; constructor(config: RateLimiterConfig) { if (config.maxRequests <= 0) { throw new Error('maxRequests must be positive'); } if (config.windowMs <= 0) { throw new Error('windowMs must be positive'); } this.maxTokens = config.maxRequests; this.tokens = this.maxTokens; this.refillRate = this.maxTokens / config.windowMs; // Validate refill rate to prevent division by zero if (this.refillRate <= 0 || !Number.isFinite(this.refillRate)) { throw new Error('Invalid configuration: refill rate must be positive and finite'); } this.lastRefill = Date.now(); this.lastRequest = 0; this.minDelay = config.minDelayMs || 0; } /** * Check if a request is allowed under the rate limit * @returns Status object indicating if request is allowed */ checkLimit(): RateLimitStatus { const now = Date.now(); // Refill tokens based on time elapsed this.refillTokens(now); // Check minimum delay between requests if (this.minDelay > 0 && this.lastRequest > 0) { const timeSinceLastRequest = now - this.lastRequest; if (timeSinceLastRequest < this.minDelay) { const retryAfterMs = this.minDelay - timeSinceLastRequest; return { allowed: false, retryAfterMs, remainingTokens: Math.floor(this.tokens), resetTime: new Date(now + retryAfterMs) }; } } // Check if we have tokens available if (this.tokens < 1) { // Calculate when the next token will be available const tokensNeeded = 1 - this.tokens; const msUntilNextToken = tokensNeeded / this.refillRate; return { allowed: false, retryAfterMs: Math.ceil(msUntilNextToken), remainingTokens: 0, resetTime: new Date(now + msUntilNextToken) }; } // Request is allowed return { allowed: true, remainingTokens: Math.floor(this.tokens), resetTime: this.getResetTime() }; } /** * Consume a token for an allowed request * Should be called after checkLimit() returns allowed: true */ consumeToken(): void { const now = Date.now(); this.refillTokens(now); if (this.tokens >= 1) { this.tokens -= 1; this.lastRequest = now; } } /** * Get current rate limit status without consuming a token */ getStatus(): RateLimitStatus { const now = Date.now(); this.refillTokens(now); return { allowed: this.tokens >= 1, remainingTokens: Math.floor(this.tokens), resetTime: this.getResetTime() }; } /** * Reset the rate limiter to full capacity * Useful for testing or manual intervention */ reset(): void { this.tokens = this.maxTokens; this.lastRefill = Date.now(); this.lastRequest = 0; } /** * Refill tokens based on time elapsed */ private refillTokens(now: number): void { const timeSinceLastRefill = now - this.lastRefill; const tokensToAdd = timeSinceLastRefill * this.refillRate; this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd); this.lastRefill = now; } /** * Calculate when the rate limit window will reset */ private getResetTime(): Date { const now = Date.now(); const tokensToFull = this.maxTokens - this.tokens; const msUntilFull = tokensToFull / this.refillRate; return new Date(now + msUntilFull); } /** * Get human-readable rate limit information */ toString(): string { const status = this.getStatus(); return `RateLimit: ${status.remainingTokens}/${this.maxTokens} tokens, ` + `resets at ${status.resetTime.toISOString()}`; } } /** * Factory function to create common rate limiters */ export class RateLimiterFactory { /** * GitHub API rate limiter (60 requests per hour for unauthenticated) */ static createGitHubLimiter(): RateLimiter { return new RateLimiter({ maxRequests: 60, windowMs: 60 * 60 * 1000, // 1 hour minDelayMs: 1000 // 1 second minimum between requests }); } /** * Conservative rate limiter for update checks * Allows 10 checks per hour with 30 second minimum delay */ static createUpdateCheckLimiter(): RateLimiter { return new RateLimiter({ maxRequests: 10, windowMs: 60 * 60 * 1000, // 1 hour minDelayMs: 30 * 1000 // 30 seconds between checks }); } /** * Strict rate limiter for sensitive operations * Allows 5 requests per hour with 1 minute minimum delay */ static createStrictLimiter(): RateLimiter { return new RateLimiter({ maxRequests: 5, windowMs: 60 * 60 * 1000, // 1 hour minDelayMs: 60 * 1000 // 1 minute between requests }); } }

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/DollhouseMCP/DollhouseMCP'

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