mattrguerra/meridian-orders-mcpmainPrivate on GitHub, published here in full

rateLimiterDO.ts

Language TypeScript1.6 KB43 lines

mcp-server/src/rateLimiterDO.ts

  1. import { DurableObject } from "cloudflare:workers";
  2. import { PLINTH_RATE_LIMIT, reserveSlot, sleep, type RateLimiter } from "./lib/rateLimit.js";
  3. /**
  4. * Shared rate-limiter Durable Object (Workers only).
  5. *
  6. * Every Plinth request across ALL McpAgent sessions and isolates routes through
  7. * ONE instance, `idFromName("plinth-global")`, so the global 10/10s budget holds
  8. * no matter how many users or parallel tool calls are in flight. A per-isolate
  9. * limiter would let each session spend the full budget independently and blow
  10. * the API limit.
  11. *
  12. * State is in-memory: a rate limiter needs no durability. Worst case, an
  13. * eviction resets the window and permits one small burst, which is harmless.
  14. * The reserve computation is synchronous (no await), so it is atomic even when
  15. * the DO handles concurrent RPC calls.
  16. */
  17. export class RateLimiterDO extends DurableObject {
  18. private slots: number[] = [];
  19. /** Reserve one slot; returns the ms the caller must wait before its request. */
  20. reserve(): number {
  21. const { delayMs, slots } = reserveSlot(
  22. this.slots,
  23. Date.now(),
  24. PLINTH_RATE_LIMIT.max,
  25. PLINTH_RATE_LIMIT.windowMs,
  26. );
  27. this.slots = slots;
  28. return delayMs;
  29. }
  30. }
  31. /** Workers RateLimiter: delegates scheduling to the one shared limiter DO. */
  32. export class WorkerRateLimiter implements RateLimiter {
  33. constructor(private readonly ns: DurableObjectNamespace<RateLimiterDO>) {}
  34. async acquire(): Promise<void> {
  35. const stub = this.ns.get(this.ns.idFromName("plinth-global"));
  36. const delayMs = await stub.reserve();
  37. if (delayMs > 0) await sleep(delayMs);
  38. }
  39. }