PrefectHQ/fastmcp · error · RateLimitError
Rate limit exceeded: {self.max_requests} requests per {self.
Error message
Rate limit exceeded: {self.max_requests} requests per {self.window_seconds // 60} minutes for client: {client_id} What it means
Raised as RateLimitError by SlidingWindowRateLimitingMiddleware.on_request when the client's sliding-window count shows it already made max_requests within window_seconds. Unlike the token-bucket middleware, the message echoes the configured limit and window in minutes.
Source
Thrown at fastmcp_slim/fastmcp/server/middleware/rate_limiting.py:238
)
async def _get_client_identifier(self, context: MiddlewareContext) -> str:
"""Get client identifier for rate limiting."""
if self.get_client_id:
client_id = self.get_client_id(context)
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 sliding window rate limiting to requests."""
client_id = await self._get_client_identifier(context)
limiter = self.limiters[client_id]
allowed = await limiter.is_allowed()
if not allowed:
raise RateLimitError(
f"Rate limit exceeded: {self.max_requests} requests per "
f"{self.window_seconds // 60} minutes for client: {client_id}"
)
return await call_next(context)
View on GitHub (pinned to 1f02114297)
Solutions
- Pace client requests to stay under max_requests / window_seconds.
- Increase max_requests or window_seconds to fit legitimate workload.
- Catch RateLimitError and delay until the sliding window frees capacity before retrying.
Example fix
// before
for doc in docs:
await client.call_tool("index", {"doc": doc}) # 500 docs, 100/min cap
// after: pace to the limit
import anyio
for doc in docs:
await client.call_tool("index", {"doc": doc})
await anyio.sleep(0.7) Defensive patterns
Strategy: retry
Validate before calling
max_requests = 100 window_seconds = 60 max_rps = max_requests / window_seconds # pace client below this assert client_rps <= max_rps, "client pacing exceeds server sliding-window limit"
Try / catch
import anyio
try:
result = await client.call_tool(name, args)
except Exception as e:
if "Rate limit exceeded" in str(e):
# wait out the window before retrying
await anyio.sleep(window_seconds)
result = await client.call_tool(name, args)
else:
raise Prevention
- Compute and respect requests/second implied by max_requests/window_seconds.
- Remember window_seconds must be a multiple of 60 for accurate minute display in the message.
- Batch work in chunks sized to fit within one window.
- Monitor per-client request rates and alert before hitting the cap.
When it happens
Trigger: Any request arriving when limiter.is_allowed() is False: the calling client_id has hit max_requests within the trailing window_seconds (note the message divides by 60, so windows that are not whole minutes display a floored minute count).
Common situations: Batch jobs exceeding e.g. '100 requests per minute'; window_seconds like 90 displayed as '1 minute' in the message confusing clients about the real window; clients without request pacing.
Related errors
- Global rate limit exceeded
- Rate limit exceeded for client: {client_id}
- 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/4537190f315a43a7.
Report an issue: GitHub.