micro/go-micro · warning
rate limit exceeded for tool %s
Error message
rate limit exceeded for tool %s
What it means
Each tool may have a rate limiter configured via RateLimitConfig (token bucket: RequestsPerSecond + Burst). allowRate checks the limiter before executing the tool; when the bucket has no tokens left it returns "rate limit exceeded for tool <name>", which invokeTool surfaces (typically as HTTP 429) instead of calling the tool.
Source
Thrown at gateway/mcp/mcp.go:977
if s.opts.AuditFunc != nil {
s.opts.AuditFunc(record)
}
}
// allowRate checks if the tool call is allowed under the configured rate limit.
// Returns nil if allowed, non-nil error if rate-limited.
func (s *Server) allowRate(toolName string) error {
if s.opts.RateLimit == nil {
return nil
}
s.limitersMu.RLock()
limiter, ok := s.limiters[toolName]
s.limitersMu.RUnlock()
if !ok {
return nil
}
if !limiter.Allow() {
return fmt.Errorf("rate limit exceeded for tool %s", toolName)
}
return nil
}
// allowCircuit checks if the tool call is allowed by the circuit breaker.
// Returns nil if allowed, non-nil error if the circuit is open.
func (s *Server) allowCircuit(toolName string) error {
if s.opts.CircuitBreaker == nil {
return nil
}
s.breakersMu.RLock()
cb, ok := s.breakers[toolName]
s.breakersMu.RUnlock()
if !ok {
return nil
}
return cb.Allow()
}View on GitHub (pinned to 24529f1404)
Solutions
- Back off and retry with exponential backoff / respect Retry-After semantics instead of immediate retries.
- Increase RateLimitConfig.RequestsPerSecond and Burst in Options for the affected tool if the limit is too strict.
- Spread load across tools or stagger agent requests; cache tool results where possible.
- Remove the limiter for that tool (no RateLimitConfig entry) if rate limiting is not desired — allowRate returns nil when no limiter exists.
Example fix
// before
opts.RateLimits["expensive_tool"] = mcp.RateLimitConfig{RequestsPerSecond: 1, Burst: 1}
// after
opts.RateLimits["expensive_tool"] = mcp.RateLimitConfig{RequestsPerSecond: 10, Burst: 20} // plus client-side backoff on 429 Defensive patterns
Strategy: retry
Try / catch
for attempt := 0; attempt < 5; attempt++ {
result, err := callTool(name, args)
if err != nil && strings.Contains(err.Error(), "rate limit exceeded") {
time.Sleep(backoff(attempt)) // e.g. 100ms, 400ms, 1.6s...
continue
}
return result, err
} Prevention
- Implement exponential backoff with jitter on rate-limit errors in tool callers.
- Size RequestsPerSecond/Burst to match real (including agent-driven) traffic.
- Distribute load: stagger requests, cache results, avoid retry storms.
- Monitor per-tool 429s and raise limits proactively.
When it happens
Trigger: A client sends more requests per second to a specific tool than its configured RequestsPerSecond/Burst allows, exhausting the token bucket; bursts larger than Burst arrive in a short window.
Common situations: LLM agents retrying aggressively or looping on a tool; load tests hammering one endpoint; several clients sharing a single limiter and collectively exceeding the budget; Burst set too low for legitimate batch jobs.
Related errors
- x402 response already written
- circuit breaker half-open (probe limit reached)
- registry is required
- failed to discover services: %w
- name is required
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/678438a5bba4d3b2.
Report an issue: GitHub.