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=1

View on GitHub (pinned to 1f02114297)

Solutions

  1. Back off and retry client-side with exponential backoff inside the window.
  2. Raise max_requests/window_seconds if the limit is mis-sized for a legitimate client.
  3. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/3bbe92df8dcd3c61. Report an issue: GitHub.