Skip to main content
Glama

Curupira

by drzln
limiter.ts5.08 kB
/** * @fileoverview Rate limiter implementation * * This file provides in-memory and distributed rate limiting. */ import type { RateLimitConfig, RateLimitContext, RateLimitInfo, RateLimitStore, RateLimitStoreResult, RateLimitHeaders } from './types.js' import { DEFAULT_RATE_LIMITS } from './types.js' import { SecurityErrors } from '../../errors/index.js' /** * In-memory rate limit store */ export class MemoryRateLimitStore implements RateLimitStore { private counters = new Map<string, { count: number; resetTime: Date }>() private cleanupInterval: NodeJS.Timeout constructor(cleanupIntervalMs = 60000) { // Periodic cleanup this.cleanupInterval = setInterval(() => { this.clean() }, cleanupIntervalMs) } async increment(key: string): Promise<RateLimitStoreResult> { const now = new Date() const existing = this.counters.get(key) if (!existing || existing.resetTime <= now) { // New window const resetTime = new Date(now.getTime() + 900000) // 15 minutes default this.counters.set(key, { count: 1, resetTime }) return { count: 1, resetTime } } // Increment existing existing.count++ return { count: existing.count, resetTime: existing.resetTime } } async decrement(key: string): Promise<void> { const existing = this.counters.get(key) if (existing && existing.count > 0) { existing.count-- } } async reset(key: string): Promise<void> { this.counters.delete(key) } async clean(): Promise<void> { const now = new Date() for (const [key, value] of this.counters.entries()) { if (value.resetTime <= now) { this.counters.delete(key) } } } destroy(): void { clearInterval(this.cleanupInterval) this.counters.clear() } } /** * Rate limiter */ export class RateLimiter { private readonly config: Required<RateLimitConfig> private readonly store: RateLimitStore constructor(config: Partial<RateLimitConfig> = {}) { this.config = { ...DEFAULT_RATE_LIMITS.standard, ...config, keyGenerator: config.keyGenerator || this.defaultKeyGenerator, skip: config.skip || (() => false) } as Required<RateLimitConfig> this.store = this.config.store || new MemoryRateLimitStore() } /** * Check rate limit */ async check(context: RateLimitContext): Promise<RateLimitInfo> { // Check if should skip if (await this.config.skip(context)) { return { limit: this.config.max, remaining: this.config.max, reset: new Date(Date.now() + this.config.windowMs) } } // Generate key const key = this.config.keyGenerator(context) // Increment counter const result = await this.store.increment(key) // Calculate remaining const remaining = Math.max(0, this.config.max - result.count) const limited = result.count > this.config.max const info: RateLimitInfo = { limit: this.config.max, remaining, reset: result.resetTime } if (limited) { info.retryAfter = Math.ceil( (result.resetTime.getTime() - Date.now()) / 1000 ) } return info } /** * Create rate limit headers */ createHeaders(info: RateLimitInfo): RateLimitHeaders { const headers: RateLimitHeaders = {} if (this.config.headers) { headers['X-RateLimit-Limit'] = String(info.limit) headers['X-RateLimit-Remaining'] = String(info.remaining) headers['X-RateLimit-Reset'] = String( Math.floor(info.reset.getTime() / 1000) ) if (info.retryAfter) { headers['Retry-After'] = String(info.retryAfter) } } if (this.config.draft) { // IETF draft spec headers headers['RateLimit'] = `limit=${info.limit}, remaining=${info.remaining}, reset=${Math.floor( info.reset.getTime() / 1000 )}` headers['RateLimit-Policy'] = `${info.limit};w=${Math.floor( this.config.windowMs / 1000 )}` } return headers } /** * Default key generator */ private defaultKeyGenerator(context: RateLimitContext): string { // Prefer user ID, then session ID, then IP if (context.userId) { return `user:${context.userId}` } if (context.sessionId) { return `session:${context.sessionId}` } if (context.ip) { return `ip:${context.ip}` } return 'global' } /** * Reset rate limit for context */ async reset(context: RateLimitContext): Promise<void> { const key = this.config.keyGenerator(context) await this.store.reset(key) } } /** * Create rate limiter */ export function createRateLimiter( config?: Partial<RateLimitConfig> ): RateLimiter { return new RateLimiter(config) } /** * Create rate limiters for different tiers */ export function createTieredRateLimiters() { return { standard: createRateLimiter(DEFAULT_RATE_LIMITS.standard), strict: createRateLimiter(DEFAULT_RATE_LIMITS.strict), websocket: createRateLimiter(DEFAULT_RATE_LIMITS.websocket) } }

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/drzln/curupira'

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