rateLimit()
Rate limiting per tool. Session-scoped by default, with optional Store-based distributed mode.
Import
import { rateLimit } from "@lynq/lynq/rate-limit";Usage
server.tool("search", rateLimit({ max: 10 }), config, handler);Options
| Option | Type | Default | Description |
|---|---|---|---|
max | number | (required) | Maximum calls per window |
windowMs | number | 60000 | Window duration in milliseconds |
message | string | "Rate limit exceeded. Max {max} calls per {windowMs/1000}s." | Custom error message |
store | boolean | false | Use persistent Store for distributed rate limiting |
perUser | boolean | false | Scope rate limiting per user (implies store: true) |
Example
import { createMCPServer, text } from "@lynq/lynq";
import { rateLimit } from "@lynq/lynq/rate-limit";
const server = createMCPServer({ name: "my-app", version: "1.0.0" });
server.tool(
"search",
rateLimit({ max: 5, windowMs: 30_000 }),
{ description: "Search the database" },
async (args) => {
const results = await db.search(args.query);
return text(JSON.stringify(results));
},
);The first 5 calls within any 30-second window succeed. The 6th call returns an error. The counter resets when the window expires.
Store-Based (Distributed)
By default, rate limits are session-scoped -- each connection has its own counter. This means a client can bypass the limit by simply reconnecting (creating a new session). For production rate limiting, enable store:
rateLimit({ max: 100, windowMs: 60_000, store: true })With store: true:
- State is stored in
c.store(async, persistent) instead ofc.session(sync, connection-scoped) - Rate limits are shared across all sessions
- State survives reconnections
Per-User Rate Limiting
Scope limits per user with perUser. This automatically enables store: true:
rateLimit({ max: 10, perUser: true })The rate limit key is prefixed with the user ID resolved from session.get("user"). Each user has an independent counter. If no user is in the session, falls back to "anon".
Anonymous users share a single bucket
When perUser: true is set but no user is in the session, all anonymous requests share the same "anon" rate limit bucket. Ensure session.set("user", ...) is called before rate-limited tools if per-user isolation is required.
Under the hood
Uses the onCall hook. In session mode, stores { count, resetAt } via c.session.get/set with key rateLimit:{toolName}. In store mode, uses c.store.get/set with key rateLimit:{toolName} or rateLimit:{userId}:{toolName} (perUser). Store entries are set with a TTL matching windowMs for automatic cleanup.