Skip to content

feat: two-layer rate limiting for MCP servers#1802

Draft
simplesagar wants to merge 2 commits intomainfrom
sagar/rate-limiting
Draft

feat: two-layer rate limiting for MCP servers#1802
simplesagar wants to merge 2 commits intomainfrom
sagar/rate-limiting

Conversation

@simplesagar
Copy link
Member

Summary

  • Layer 1 (Platform): HTTP middleware enforces a default 600 req/min limit on all MCP POST endpoints, returning HTTP 429 when exceeded. Keyed on MCP slug extracted from URL path (zero DB cost). Supports per-slug overrides via platform_rate_limits table with Redis-cached lookups.
  • Layer 2 (Customer): Per-MCP-server rate limits configurable via rate_limit_rpm on the toolsets table. Returns JSON-RPC error code -32029 (not HTTP 429) to maintain MCP protocol compatibility. Reads from already-loaded toolset data — no extra DB query.
  • Fail-open: Both layers allow requests through when Redis is unavailable, logging warnings.
  • Observability: OTel counters (mcp.ratelimit.platform.check, mcp.ratelimit.customer.check) with layer/key/outcome attributes. Standard X-RateLimit-* response headers on all checked requests.

New packages

Package Purpose
server/internal/ratelimit Core Redis fixed-window counter, config cache, header helpers
server/internal/middleware Platform rate limit HTTP middleware

Schema changes

  • toolsets.rate_limit_rpm — nullable integer column for customer-configured limits
  • platform_rate_limits — new table for per-slug/project/org override limits

Architecture decisions

  • Platform layer keys on URL-extracted slug (no DB lookup) for minimal latency
  • Customer layer uses toolset.ID as Redis key to avoid cross-org collisions
  • Fixed-window counter chosen for simplicity; documented 2x burst behavior at window boundaries
  • RateLimitConfigLoader is an interface for testability

Known follow-ups (not in this PR)

  • Wire up admin API for platform_rate_limits CRUD + cache invalidation
  • Thread JSON-RPC request id into rate limit error responses
  • Consider Layer type parameter on Allow() to make key namespacing structural

Test plan

  • Unit tests for extractMCPKey URL path parsing (8 cases)
  • Integration test: platform rate limit returns 429 with correct headers
  • Integration test: customer rate limit returns JSON-RPC error at HTTP 200
  • Verify fail-open behavior when Redis is down
  • Manual test via dashboard: set rate_limit_rpm on a toolset and confirm enforcement

🤖 Generated with Claude Code

Add platform-level and customer-configurable rate limiting for MCP endpoints.

Layer 1 (platform): HTTP middleware returns 429 for all MCP POST requests,
keyed on MCP slug extracted from URL path. Uses Redis fixed-window counter
with configurable per-slug overrides cached from platform_rate_limits table.

Layer 2 (customer): Per-MCP-server rate limit via rate_limit_rpm column on
toolsets table. Returns JSON-RPC error -32029 (not HTTP 429) to maintain
MCP protocol compatibility. Reads from already-loaded toolset data (no
extra DB query).

Both layers fail open when Redis is unavailable. Includes OTel counters
for observability and standard rate limit response headers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gram-docs-redirect Ready Ready Preview, Comment Mar 8, 2026 2:49am

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 8, 2026

⚠️ No Changeset found

Latest commit: 9e383b5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Introduce ratelimit.Limiter interface so middleware and MCP service can
be unit tested with mock rate limiters instead of requiring Redis.

Tests added:
- writeJSONRPCRateLimitError: response shape, min retryAfterMs clamping
- checkCustomerRateLimit: nil limiter, invalid RPM, zero RPM, allowed,
  blocked, fail-open on error
- RateLimitMiddleware: skip GET, skip non-MCP, allowed with headers,
  blocked with 429, fail-open, config override, authenticated route key
- resolveLimit: nil loader, no override, with override, config error
- RecordRateLimitCheck: valid counter, nil counter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant