{"record":{"id":"678438a5bba4d3b2","repo":"micro/go-micro","slug":"rate-limit-exceeded-for-tool-s","errorCode":null,"errorMessage":"rate limit exceeded for tool %s","messagePattern":"rate limit exceeded for tool (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gateway/mcp/mcp.go","lineNumber":977,"sourceCode":"\tif s.opts.AuditFunc != nil {\n\t\ts.opts.AuditFunc(record)\n\t}\n}\n\n// allowRate checks if the tool call is allowed under the configured rate limit.\n// Returns nil if allowed, non-nil error if rate-limited.\nfunc (s *Server) allowRate(toolName string) error {\n\tif s.opts.RateLimit == nil {\n\t\treturn nil\n\t}\n\ts.limitersMu.RLock()\n\tlimiter, ok := s.limiters[toolName]\n\ts.limitersMu.RUnlock()\n\tif !ok {\n\t\treturn nil\n\t}\n\tif !limiter.Allow() {\n\t\treturn fmt.Errorf(\"rate limit exceeded for tool %s\", toolName)\n\t}\n\treturn nil\n}\n\n// allowCircuit checks if the tool call is allowed by the circuit breaker.\n// Returns nil if allowed, non-nil error if the circuit is open.\nfunc (s *Server) allowCircuit(toolName string) error {\n\tif s.opts.CircuitBreaker == nil {\n\t\treturn nil\n\t}\n\ts.breakersMu.RLock()\n\tcb, ok := s.breakers[toolName]\n\ts.breakersMu.RUnlock()\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn cb.Allow()\n}","sourceCodeStart":959,"sourceCodeEnd":995,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/gateway/mcp/mcp.go#L959-L995","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nopts.RateLimits[\"expensive_tool\"] = mcp.RateLimitConfig{RequestsPerSecond: 1, Burst: 1}\n// after\nopts.RateLimits[\"expensive_tool\"] = mcp.RateLimitConfig{RequestsPerSecond: 10, Burst: 20} // plus client-side backoff on 429","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt := 0; attempt < 5; attempt++ {\n    result, err := callTool(name, args)\n    if err != nil && strings.Contains(err.Error(), \"rate limit exceeded\") {\n        time.Sleep(backoff(attempt)) // e.g. 100ms, 400ms, 1.6s...\n        continue\n    }\n    return result, err\n}","preventionTips":["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."],"tags":["mcp","rate-limiting","throttling"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}