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

  1. Back off and retry with exponential backoff / respect Retry-After semantics instead of immediate retries.
  2. Increase RateLimitConfig.RequestsPerSecond and Burst in Options for the affected tool if the limit is too strict.
  3. Spread load across tools or stagger agent requests; cache tool results where possible.
  4. 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

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


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/678438a5bba4d3b2. Report an issue: GitHub.