Skip to main content
Glama
rate-limiter.tsβ€’6.32 kB
/** * Rate limiter utility for Attio MCP server * Provides simple rate limiting functionality for API endpoints */ /** * Configuration for the rate limiter */ export interface RateLimiterConfig { /** Maximum number of requests allowed in the time window */ maxRequests: number; /** Time window in milliseconds */ windowMs: number; /** Whether to track requests by IP address (default: true) */ trackByIp?: boolean; /** Optional key function to determine the rate limiting key */ keyFn?: (req: Record<string, unknown>) => string; } /** * Interface for request objects with IP tracking capabilities */ interface RequestWithIpInfo { ip?: string; connection?: { remoteAddress?: string; }; headers?: { 'x-forwarded-for'?: string; }; } /** * Interface for Express-like request object */ interface ExpressRequest extends Record<string, unknown> { ip?: string; connection?: { remoteAddress?: string; }; headers?: Record<string, string>; } /** * Interface for Express-like response object */ interface ExpressResponse { setHeader: (name: string, value: string | number) => void; status: (code: number) => ExpressResponse; json: (data: Record<string, unknown>) => void; } /** * Rate limiter implementation */ export class RateLimiter { private requests: Map<string, { count: number; resetTime: number }>; private config: RateLimiterConfig; /** * Create a new rate limiter * * @param config - Rate limiter configuration */ constructor(config: RateLimiterConfig) { this.requests = new Map(); this.config = { maxRequests: config.maxRequests, windowMs: config.windowMs, trackByIp: config.trackByIp !== false, // Default to true keyFn: config.keyFn, }; } /** * Check if a request is allowed * * @param req - Request object (with IP address or other identifying info) * @returns Object with allowed status and rate limit info */ check(req: Record<string, unknown>): { allowed: boolean; remaining: number; resetTime: number; msUntilReset: number; } { const now = Date.now(); // Get the key to track this request by const key = this.getKey(req); // Get or create record for this key let record = this.requests.get(key); if (!record || now > record.resetTime) { // If no record exists or the window has passed, create a new one record = { count: 0, resetTime: now + this.config.windowMs, }; this.requests.set(key, record); } // Check if request is allowed const allowed = record.count < this.config.maxRequests; // Increment counter if allowed if (allowed) { record.count++; } // Return result return { allowed, remaining: Math.max(0, this.config.maxRequests - record.count), resetTime: record.resetTime, msUntilReset: Math.max(0, record.resetTime - now), }; } /** * Cleanup old entries to prevent memory leaks */ cleanup(): void { const now = Date.now(); for (const [key, record] of Array.from(this.requests.entries())) { if (now > record.resetTime) { this.requests.delete(key); } } } /** * Get the key to track a request by * * @param req - Request object * @returns Key for rate limiting */ private getKey(req: Record<string, unknown>): string { // Use custom key function if provided if (this.config.keyFn) { return this.config.keyFn(req); } // Track by IP if configured if (this.config.trackByIp) { const reqWithIp = req as RequestWithIpInfo; const ip = reqWithIp.ip || reqWithIp.connection?.remoteAddress || reqWithIp.headers?.['x-forwarded-for'] || 'unknown'; return `ip:${ip}`; } // Default to a static key (not recommended for production) return 'global'; } } // Global rate limiter instances const rateLimiters: Map<string, RateLimiter> = new Map(); /** * Get or create a rate limiter for a specific endpoint * * @param endpoint - Endpoint to rate limit * @param config - Rate limiter configuration * @returns Rate limiter instance */ export function getRateLimiter( endpoint: string, config: RateLimiterConfig ): RateLimiter { // Check if limiter already exists let limiter = rateLimiters.get(endpoint); if (!limiter) { // Create new limiter limiter = new RateLimiter(config); rateLimiters.set(endpoint, limiter); } return limiter; } /** * Middleware for rate limiting Express requests * * @param config - Rate limiter configuration * @returns Express middleware function */ export function rateLimiterMiddleware(config: RateLimiterConfig) { const limiter = new RateLimiter(config); // Schedule cleanup every windowMs to prevent memory leaks setInterval(() => limiter.cleanup(), config.windowMs); return (req: ExpressRequest, res: ExpressResponse, next: () => void) => { const result = limiter.check(req); // Add rate limit headers res.setHeader('X-RateLimit-Limit', config.maxRequests); res.setHeader('X-RateLimit-Remaining', result.remaining); res.setHeader('X-RateLimit-Reset', result.resetTime); if (!result.allowed) { // Return 429 Too Many Requests res.status(429).json({ error: 'Too many requests', message: `Rate limit exceeded. Try again in ${Math.ceil( result.msUntilReset / 1000 )} seconds.`, retryAfter: Math.ceil(result.msUntilReset / 1000), }); return; } next(); }; } /** * Rate limiting for filter operations * * @param req - Request object * @param endpoint - Endpoint being accessed * @returns Object with allowed status and rate limit info */ export function checkFilterRateLimit( req: Record<string, unknown>, endpoint: string ): { allowed: boolean; remaining: number; resetTime: number; msUntilReset: number; } { // Configuration for filter endpoints const config: RateLimiterConfig = { maxRequests: 60, // 60 requests windowMs: 60 * 1000, // per minute trackByIp: true, // track by IP address }; // Get limiter for this endpoint const limiter = getRateLimiter(`filter:${endpoint}`, config); // Check rate limit return limiter.check(req); }

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/kesslerio/attio-mcp-server'

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