PrefectHQ/fastmcp · error · RateLimitError
Rate limit exceeded for client: {client_id}
Error message
Rate limit exceeded for client: {client_id} What it means
Raised as RateLimitError by RateLimitingMiddleware.on_request when per-client mode is active and the specific client identified by _get_client_identifier exhausts its token-bucket allowance. Only that client is throttled; other clients are unaffected.
Source
Thrown at fastmcp_slim/fastmcp/server/middleware/rate_limiting.py:171
if inspect.isawaitable(client_id):
return cast(str, await client_id)
return client_id
return "global"
async def on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any:
"""Apply rate limiting to requests."""
if self.global_limit:
# Global rate limiting
allowed = await self.global_limiter.consume()
if not allowed:
raise RateLimitError("Global rate limit exceeded")
else:
# Per-client rate limiting
client_id = await self._get_client_identifier(context)
limiter = self.limiters[client_id]
allowed = await limiter.consume()
if not allowed:
raise RateLimitError(f"Rate limit exceeded for client: {client_id}")
return await call_next(context)
class SlidingWindowRateLimitingMiddleware(Middleware):
"""Middleware that implements sliding window rate limiting.
Uses a sliding window approach which provides more precise rate limiting
but uses more memory to track individual request timestamps.
Example:
```python
from fastmcp.server.middleware.rate_limiting import SlidingWindowRateLimitingMiddleware
# Allow 100 requests per minute
rate_limiter = SlidingWindowRateLimitingMiddleware(
max_requests=100,
window_minutes=1View on GitHub (pinned to 1f02114297)
Solutions
- Back off and retry client-side with exponential backoff inside the window.
- Raise max_requests/window_seconds if the limit is mis-sized for a legitimate client.
- If many users share one IP/client_id, improve client identification (e.g. per-auth-token identity) so limits are per real user.
Example fix
// before: tight retry loop
for attempt in range(1000):
await client.call_tool("process", {"id": attempt})
// after: respect the rate limit
for attempt in range(1000):
try:
await client.call_tool("process", {"id": attempt})
except RateLimitError:
await anyio.sleep(5)
await client.call_tool("process", {"id": attempt}) Defensive patterns
Strategy: retry
Try / catch
import anyio, random
for attempt in range(5):
try:
result = await client.call_tool(name, args)
break
except Exception as e:
if f"Rate limit exceeded for client" in str(e) and attempt < 4:
await anyio.sleep(random.uniform(1, 5) * 2 ** attempt)
continue
raise Prevention
- Add client-side pacing/throttling below the server's per-client cap.
- Use exponential backoff with jitter on 429-style RateLimitError.
- Avoid retry loops without delays that re-trigger the same limiter.
- If many users share one client_id (proxy/NAT), switch identity source or raise the per-client cap.
When it happens
Trigger: RateLimitingMiddleware configured with global_limit=False (per-client mode); a single client_id exceeds max_requests within window_seconds; limiter.consume() returns False for that client's bucket.
Common situations: A runaway script or retry loop hammering the server from one identity; many users behind one proxy so they share one client_id and hit the cap collectively; per-client limits copied from global-appropriate numbers.
Related errors
- Global rate limit exceeded
- Rate limit exceeded: {self.max_requests} requests per {self.
- Server overloaded, please retry
- Server overloaded, please retry
- Authorization failed for tool '{tool_name}': missing context
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/3bbe92df8dcd3c61.
Report an issue: GitHub.