Rebasely Beta: data may be purged

examples/MCP-tools Public

Markdown · 1.7 KiB · at 2090b14

id: rate-limits
type: concept
owner: justin
component: rate-limiter
describes:
  - src/rate-limiter/**
reviewed: 2026-10-15

Rate limits

Each client gets a token bucket: 30 tool calls to start, one more every two seconds, never more than 30. [[0004-per-client-rate-limit]] has the reasoning.

Where the pieces are

  • src/rate-limiter/bucket.ts: the arithmetic. take(bucket, now) refills by the time elapsed, spends one token if there is one, and returns the new bucket and a decision: { ok: true, remaining } or { ok: false, retryAfterSeconds }. Pure, so test/rate-limit.test.ts drives it with a fake clock.
  • src/rate-limiter/index.ts: the RateLimiter Durable Object, bound as RATE_LIMITER. Its one RPC method, take(), loads the bucket from storage, runs the arithmetic, saves the result and returns the decision.
  • src/worker.ts: names the object by the caller's cf-connecting-ip and turns a refusal into the message the model sees. See [[http-transport]].

Behavior worth knowing

  • A refused call does not spend anything, so a client that waits the time it was told always gets through.
  • A clock that steps backwards adds no tokens.
  • After each call the object sets an alarm for the moment its bucket would be full. If nothing else happens first, the alarm deletes the object's storage, and the next call starts from a fresh, full bucket, which is the same state.
  • Durable Objects handle one request at a time, so concurrent calls from one client cannot both spend the last token.

Changing the limit

Edit LIMIT in bucket.ts. Buckets already stored keep their token count and refill toward the new capacity. Update the numbers in [[0004-per-client-rate-limit]] only by writing a new decision that supersedes it.