# How to Handle Rate Limiting in MCP Implementations: A Complete Guide

> Learn how to handle rate limiting in MCP implementations with this guide. Discover token-bucket strategies, Redis, and decorators for effective quota management and error handling.

- Repository: [Composio/awesome-codex-skills](https://github.com/composiohq/awesome-codex-skills)
- Tags: how-to-guide
- Published: 2026-04-26

---

**Implement token-bucket rate limiting using Redis to enforce per-user or per-tool quotas (such as 100 requests per minute), wrap MCP tool calls in decorator or middleware guards that return standardized error responses when limits are exceeded, and expose rate limit metadata in tool annotations so LLMs can plan requests accordingly.**

Handling rate limiting in MCP implementations is essential to prevent individual clients from overwhelming external APIs and exhausting server resources. The `ComposioHQ/awesome-codex-skills` repository provides authoritative architectural patterns demonstrating how to safeguard Model Context Protocol servers using Redis-backed token buckets and standardized MCP error responses. According to the MCP best practices guide and concrete API examples in the repository, developers should enforce strict quotas while maintaining clear communication channels with LLM clients about throughput constraints.

## Architectural Approach to Rate Limiting

The `ComposioHQ/awesome-codex-skills` repository outlines a five-step architectural pattern for handling rate limiting in MCP implementations. As explicitly stated in [`mcp-builder/reference/mcp_best_practices.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/mcp_best_practices.md) (line 42), developers should "Consider rate limiting for resource-intensive operations" and "Rate limit requests" to prevent service abuse and protect downstream resources.

### Define Rate Limit Policies Per Tool or User

Establish token-bucket policies based on authentication status and external API costs. The repository recommends a concrete quota of **100 requests per minute per user** for standard operations, as illustrated in [`notion-spec-to-implementation/examples/api-feature.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/notion-spec-to-implementation/examples/api-feature.md) (line 69). Resource-intensive external APIs may require stricter limits (e.g., 10 req/min), while high-throughput internal tools can sustain more permissive thresholds.

### Persist Counters in Redis

Use **Redis** as the rate-limiting data store to leverage atomic increment operations and automatic TTL expiration. Store counters in keys formatted as `rate:{user_id}:{tool_name}` or `rate:{user_id}:{window_timestamp}`. This approach ensures the token bucket refills automatically when the time window expires, eliminating the need for explicit cleanup tasks and preventing race conditions through atomic `INCR` operations.

### Wrap Tool Implementations with Rate-Limit Guards

Implement guards as **Python decorators** or **TypeScript middleware** that intercept tool calls before execution. These guards check the current bucket count using `redis.incr()`; if the limit is exceeded, they immediately return an MCP error response with `isError: true` without invoking the external API, preventing unnecessary resource consumption and costly out-of-quota calls.

### Expose Limits in Tool Metadata

Include a `rateLimit` field in tool `annotations` to communicate quotas to LLM clients. As implemented in the repository examples, this metadata allows language models to understand expected throughput before invocation, enabling intelligent request batching and backoff strategies that reduce unintentional throttling and improve user experience.

### Log and Monitor Denials

Record every rate-limit hit and denial to detect abuse patterns and tune policies dynamically. The best practices guide in [`mcp-builder/reference/mcp_best_practices.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/mcp_best_practices.md) (line 44) specifically recommends logging security events, including rate limit violations, to maintain observability over client behavior and identify potential misuse patterns.

## Code Implementation Examples

The following examples demonstrate token-bucket rate limiting using Redis in Python and TypeScript MCP servers.

### Python FastMCP Implementation

In `FastMCP` implementations, implement rate limiting as a decorator that wraps tool functions. The decorator checks a Redis counter keyed to the user and current time window, returning a standard `CallToolResult` with `isError: true` when the bucket empties:

```python
import time
import aioredis
from mcp.server.fastmcp import FastMCP
from mcp.types import CallToolResult, TextContent

redis = aioredis.from_url("redis://localhost")
REQUESTS_PER_MINUTE = 100
WINDOW_SECONDS = 60

def rate_limit(key: str):
    async def wrapper(func):
        async def inner(*args, **kwargs):
            now = int(time.time())
            bucket_key = f"rate:{key}:{now // WINDOW_SECONDS}"
            count = await redis.incr(bucket_key)
            if count == 1:
                # first hit, set TTL for the window

                await redis.expire(bucket_key, WINDOW_SECONDS)
            if count > REQUESTS_PER_MINUTE:
                return CallToolResult(
                    isError=True,
                    content=[TextContent(type="text", text="Rate limit exceeded (100 req/min).")]
                )
            return await func(*args, **kwargs)
        return inner
    return wrapper

mcp = FastMCP("example_mcp")

@mcp.tool(
    annotations={
        "title": "Fetch Weather",
        "readOnlyHint": True,
        "openWorldHint": True,
        "rateLimit": "100 req/min"
    }
)
@rate_limit("weather")
async def fetch_weather(location: str) -> str:
    """Call an external weather API."""
    # ... perform HTTP request ...

    return "Sunny, 23°C"

```

### TypeScript MCP SDK Implementation

For TypeScript servers using the MCP SDK, implement rate limiting as middleware that executes before the tool handler. The middleware increments a Redis counter and returns an error object compatible with the MCP protocol when limits are exceeded:

```typescript
import { Server } from "@modelcontextprotocol/sdk";
import Redis from "ioredis";

const redis = new Redis();
const REQUESTS_PER_MINUTE = 100;
const WINDOW_MS = 60_000;

function rateLimit(toolName: string) {
  return async (ctx: any, next: () => Promise<any>) => {
    const user = ctx.auth?.userId ?? "anonymous";
    const bucketKey = `rate:${user}:${toolName}`;
    const count = await redis.incr(bucketKey);
    if (count === 1) {
      await redis.pexpire(bucketKey, WINDOW_MS);
    }
    if (count > REQUESTS_PER_MINUTE) {
      return {
        isError: true,
        content: [{ type: "text", text: "Rate limit exceeded (100 req/min)." }],
      };
    }
    return next();
  };
}

const server = new Server({ name: "example-mcp" });

server.registerTool({
  name: "search_web",
  description: "Perform a web search.",
  inputSchema: {
    type: "object",
    properties: { query: { type: "string" } },
    required: ["query"],
  },
  annotations: {
    title: "Web Search",
    readOnlyHint: true,
    openWorldHint: true,
    rateLimit: "100 req/min",
  },
  handler: async (params) => {
    // actual search logic
    return { content: [{ type: "text", text: "Results…" }] };
  },
  middleware: [rateLimit("search_web")],
});

```

## Implementation Checklist

Follow these steps to implement rate limiting in your MCP server according to the `ComposioHQ/awesome-codex-skills` patterns:

1. **Define Policy Constants** – Set `REQUESTS_PER_MINUTE = 100` and choose between per-user or per-tool limits based on API costs and authentication tiers.

2. **Configure Redis Bucket** – Use keys formatted as `rate:{user_id}:{tool_name}` with atomic `INCR` and `EXPIRE` (or `PEXPIRE`) operations to maintain the token bucket and enable automatic window rotation.

3. **Create Guard Decorator/Middleware** – Implement the guard to check the bucket, increment counters, and short-circuit execution when counters exceed limits before calling external APIs.

4. **Standardize Error Responses** – Return MCP error objects with `isError: true` and descriptive 429-style messages indicating the specific limit exceeded and retry guidance.

5. **Annotate Tool Metadata** – Add `rateLimit: "100 req/min"` to tool `annotations` fields so LLM clients discover quotas before invocation and can plan request patterns.

6. **Enable Structured Logging** – Emit logs for every rate-limit hit and denial as recommended in [`mcp-builder/reference/mcp_best_practices.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/mcp_best_practices.md) (line 44), including user identifiers and tool names for abuse analysis.

## Key Source References

The architectural patterns above derive from specific authoritative files in the repository:

- **[`mcp-builder/reference/mcp_best_practices.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/reference/mcp_best_practices.md)** – Contains explicit guidance to "Consider rate limiting for resource-intensive operations" (line 42) and security logging recommendations (line 44).
- **[`mcp-builder/SKILL.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/mcp-builder/SKILL.md)** – Lists rate limiting and pagination patterns as core implementation concerns for production MCP servers.
- **[`notion-spec-to-implementation/examples/api-feature.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/notion-spec-to-implementation/examples/api-feature.md)** – Demonstrates concrete rate limits of 100 requests per minute per user and their inclusion in generated implementation plans (line 69).

## Summary

- **Token-bucket architecture**: Use Redis with atomic `INCR` and TTLs to enforce **100 requests per minute per user** quotas without manual cleanup or race conditions.
- **Guard pattern**: Wrap MCP tool calls in Python decorators or TypeScript middleware to validate limits before executing external API requests.
- **Standard errors**: Return `isError: true` with descriptive 429 messages when rate limits are exceeded, allowing LLMs to implement backoff and retry logic.
- **Metadata exposure**: Include `rateLimit` annotations in tool definitions so clients can plan request patterns and avoid unnecessary throttling.
- **Observability**: Log all rate-limit denials as security events to detect abuse patterns and dynamically tune policies based on actual usage.

## Frequently Asked Questions

### What is the recommended default rate limit for MCP tools?

The `ComposioHQ/awesome-codex-skills` repository suggests a default of **100 requests per minute per user** for standard MCP tools, as demonstrated in [`notion-spec-to-implementation/examples/api-feature.md`](https://github.com/ComposioHQ/awesome-codex-skills/blob/main/notion-spec-to-implementation/examples/api-feature.md) (line 69). High-cost external APIs may require stricter limits, while internal read-only operations may tolerate higher throughput based on infrastructure capacity.

### Should I implement rate limiting per user or per tool?

Implement **per-user rate limiting** for multi-tenant servers to prevent individual clients from exhausting shared quotas, and **per-tool limits** for resource-intensive operations that could overwhelm external APIs regardless of the requesting user. The best practices guide recommends evaluating both dimensions based on API costs, authentication status, and observed abuse patterns.

### How should an MCP server respond when a rate limit is exceeded?

Return a standard MCP error response with `isError: true` and a `content` array containing a text explanation of the 429-style error, such as "Rate limit exceeded (100 req/min)." This signals to the LLM that it should back off and retry later, rather than treating the response as successful tool output or attempting immediate retries that would compound the issue.

### Where should rate limit counters be stored?

Use **Redis** as the persistent store for rate limit counters because it supports atomic `INCR` operations and automatic TTL expiration via `EXPIRE` or `PEXPIRE`. This ensures the token bucket refills correctly without race conditions, manual cleanup tasks, or database overhead, making it ideal for high-throughput MCP implementations.