PrefectHQ/fastmcp · error · RateLimitError

Global rate limit exceeded

Error message

Global rate limit exceeded

What it means

Raised as RateLimitError by RateLimitingMiddleware.on_request when the global token-bucket limiter's consume() returns False, meaning the server-wide request budget for the current window is exhausted. This applies to all clients collectively, not one caller.

Source

Thrown at fastmcp_slim/fastmcp/server/middleware/rate_limiting.py:164

                self.burst_capacity, self.max_requests_per_second
            )

    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 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:

View on GitHub (pinned to 1f02114297)

Solutions

  1. Raise max_requests or window_seconds to match legitimate traffic volume.
  2. Switch to per-client limiting (omit global_limit) so one noisy client cannot starve everyone.
  3. Add client-side retry with backoff honoring the rate-limit response before resuming.

Example fix

// before
mw = RateLimitingMiddleware(max_requests=100, window_seconds=60)

// after: scale the cap to observed traffic, or go per-client
mw = RateLimitingMiddleware(max_requests=5000, window_seconds=60)
# or per-client mode:
mw = RateLimitingMiddleware(max_requests=100, window_seconds=60, global_limit=False)
Defensive patterns

Strategy: retry

Try / catch

import anyio
for attempt in range(5):
    try:
        result = await client.call_tool(name, args)
        break
    except Exception as e:
        if "Global rate limit exceeded" in str(e) and attempt < 4:
            await anyio.sleep(2 ** attempt)
            continue
        raise

Prevention

When it happens

Trigger: RateLimitingMiddleware(max_requests=N, window_seconds=W) configured without per-client limits, and more than N requests arrive within any W-second window regardless of source.

Common situations: Load tests or bursts of traffic exceeding a conservative global cap; multiple services sharing one FastMCP server; window_seconds set far below actual traffic volume.

Related errors


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