apache/shardingsphere · warning · MCPUnavailableException

Completion request limit of %d per minute exceeded for MCP s

Error message

Completion request limit of %d per minute exceeded for MCP session `%s`; retry after the current 60-second window ends.

What it means

MCPCompletionRateLimiter enforces a fixed window of max completion requests per 60 seconds per session; the exception (MCPUnavailableException) is thrown when a session's requestCount already reached maxRequestsPerWindow inside the current window. The window is keyed by sessionId and resets when now >= startedAt + 60s, or when releaseSession removes the session entry. The message includes the configured limit and the offending session id and instructs the client to retry after the window ends.

Source

Thrown at mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/completion/MCPCompletionRateLimiter.java:59

    private final Clock clock;
    
    private final Map<String, CompletionWindow> sessionWindows = new ConcurrentHashMap<>();
    
    MCPCompletionRateLimiter() {
        this(MCPRuntimeProtectionPolicy.getMaxCompletionRequestsPerMinute(), Clock.systemUTC());
    }
    
    void acquire(final String sessionId) {
        Instant now = clock.instant();
        sessionWindows.compute(sessionId, (ignored, currentWindow) -> acquire(sessionId, currentWindow, now));
    }
    
    private CompletionWindow acquire(final String sessionId, final CompletionWindow currentWindow, final Instant now) {
        if (null == currentWindow || !now.isBefore(currentWindow.startedAt().plus(WINDOW_DURATION))) {
            return new CompletionWindow(now, 1);
        }
        if (currentWindow.requestCount() >= maxRequestsPerWindow) {
            throw new MCPUnavailableException(String.format(
                    "Completion request limit of %d per minute exceeded for MCP session `%s`; retry after the current 60-second window ends.", maxRequestsPerWindow, sessionId));
        }
        return new CompletionWindow(currentWindow.startedAt(), currentWindow.requestCount() + 1);
    }
    
    void releaseSession(final String sessionId) {
        sessionWindows.remove(sessionId);
    }
    
    private record CompletionWindow(Instant startedAt, int requestCount) {
    }
}

View on GitHub (pinned to e952770a21)

Solutions

  1. Throttle client-side to the configured N requests per 60 seconds per session (debounce completion triggers).
  2. Wait until the current 60-second window closes, then retry; the counter resets at window boundaries.
  3. Ask the operator to raise maxRequestsPerWindow if legitimate workloads exceed it.
  4. Call releaseSession (server closes session) when done so window state is cleaned up.

Example fix

// before: fire a completion on every input event
input.onChange(() => mcp.callTool('completion', args)); // exceeds N/min -> 429-style error

// after: debounce and cap to the server limit
let pending = 0;
input.onChange(debounce(async () => {
  if (pending >= LIMIT_PER_MIN) return; // wait for window reset
  pending++;
  try { await mcp.callTool('completion', args); } finally {}
}, 250));
Defensive patterns

Strategy: retry

Validate before calling

// Client-side fixed-window limiter matching the server contract
function makeLimiter(max, windowMs = 60_000) {
  let startedAt = 0, count = 0;
  return {
    tryAcquire(now = Date.now()) {
      if (now - startedAt >= windowMs) { startedAt = now; count = 0; }
      return count++ < max;
    },
    retryAfterMs(now = Date.now()) { return Math.max(0, startedAt + windowMs - now); }
  };
}
const limiter = makeLimiter(SERVER_COMPLETION_LIMIT);
if (!limiter.tryAcquire()) scheduleRetry(limiter.retryAfterMs());

Try / catch

try {
  return await mcp.callTool('completion', args);
} catch (e) {
  if (/Completion request limit of \d+ per minute exceeded/.test(e.message)) {
    await sleep(msUntilNextMinuteBoundary());  // window is a fixed 60s from first request
    return mcp.callTool('completion', args);
  }
  throw e;
}

Prevention

When it happens

Trigger: Issuing more completion/complete requests than the configured limit for one session within a sliding start-of-window 60-second period, e.g. an LLM client that fans out one completion per keystroke or per argument without client-side throttling.

Common situations: Aggressive editor-style autocomplete wired to the MCP completion tool; batch scripts looping completion calls; low maxRequestsPerWindow configured server-side; a stale window not released after a session closed (missing releaseSession) making the next session's count appear exhausted only if ids collide — normally just genuine overuse.

Related errors


AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14). Data as JSON: /api/errors/bca6a1f9b9d42da2. Report an issue: GitHub.