Evidence
src/CodeIndex/Mcp/RateLimiter.cs keeps buckets in:
private readonly Dictionary<string, TokenBucket> _buckets = new(StringComparer.Ordinal);
TryAcquire creates a new bucket for each (tool, caller) key, but there is no TTL, max bucket count, idle cleanup, or compaction path.
Impact
Rate limiting is opt-in, but shared or HTTP MCP deployments can still have many caller identities over time. Stale (tool, caller) buckets remain for the process lifetime, so memory grows with historical caller cardinality rather than active callers.
Expected
Add a bounded eviction strategy, such as idle TTL cleanup, max bucket count with oldest-idle eviction, or periodic prune on TryAcquire. The response behavior should stay deterministic for active callers.
Evidence
src/CodeIndex/Mcp/RateLimiter.cskeeps buckets in:TryAcquirecreates a new bucket for each(tool, caller)key, but there is no TTL, max bucket count, idle cleanup, or compaction path.Impact
Rate limiting is opt-in, but shared or HTTP MCP deployments can still have many caller identities over time. Stale
(tool, caller)buckets remain for the process lifetime, so memory grows with historical caller cardinality rather than active callers.Expected
Add a bounded eviction strategy, such as idle TTL cleanup, max bucket count with oldest-idle eviction, or periodic prune on
TryAcquire. The response behavior should stay deterministic for active callers.