{"record":{"id":"617bc4dd25526112","repo":"ruvnet/ruflo","slug":"mcp-server-server-name-is-in-cooldown-http","errorCode":null,"errorMessage":"MCP server \"${server.name}\" is in cooldown (HTTP ${cd.status ?? \"n/a\"}, ${Math.round(remaining / 1000)}s remaining): ${cd.message}","messagePattern":"MCP server \"(.+?)\" is in cooldown \\(HTTP (.+?), (.+?)s remaining\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/server/mcp/clientPool.ts","lineNumber":111,"sourceCode":"\t\tif (Number.isFinite(seconds) && seconds > 0) {\n\t\t\treturn Math.min(seconds * 1000, MAX_RATE_LIMIT_COOLDOWN_MS);\n\t\t}\n\t}\n\treturn DEFAULT_RATE_LIMIT_COOLDOWN_MS;\n}\n\nfunction checkCooldown(key: string, server: McpServerConfig): void {\n\tconst cd = failureCooldown.get(key);\n\tif (!cd) return;\n\tconst remaining = cd.until - Date.now();\n\tif (remaining <= 0) {\n\t\tfailureCooldown.delete(key);\n\t\treturn;\n\t}\n\tif (cd.status === 429) {\n\t\tthrow new McpRateLimitedError(server.name, cd.status, remaining, cd.message);\n\t}\n\tthrow new Error(\n\t\t`MCP server \"${server.name}\" is in cooldown (HTTP ${cd.status ?? \"n/a\"}, ` +\n\t\t\t`${Math.round(remaining / 1000)}s remaining): ${cd.message}`\n\t);\n}\n\nfunction recordFailure(key: string, status: number | undefined, err: unknown): void {\n\tconst message = err instanceof Error ? err.message : String(err);\n\tconst cooldownMs = status === 429 ? extractRetryAfterMs(err) : DEFAULT_RATE_LIMIT_COOLDOWN_MS;\n\tfailureCooldown.set(key, {\n\t\tuntil: Date.now() + cooldownMs,\n\t\tstatus,\n\t\tmessage,\n\t});\n}\n\nexport async function getClient(server: McpServerConfig, signal?: AbortSignal): Promise<Client> {\n\tconst key = keyOf(server);\n\tconst existing = pool.get(key);","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/ruflo/src/ruvocal/src/lib/server/mcp/clientPool.ts#L93-L129","documentation":"Same cooldown mechanism as the 429 case, but the recorded failure was a non-429 definitive HTTP status (401/403/404/500…) recorded by recordFailure. checkCooldown (clientPool.ts:111) throws this summary error for ~5s (DEFAULT_RATE_LIMIT_COOLDOWN_MS) after such a failure, including the original status and message, so rapid retries get a fast, clear signal instead of re-hitting the upstream.","triggerScenarios":"An earlier connect to the MCP server failed definitively (bad auth 401, wrong path 404, upstream 5xx); getClient() is called again for the same url+headers within the 5s cooldown window and gets this error with the remaining seconds.","commonSituations":"Wrong MCP_SERVERS url (missing /mcp path) or expired token causing 401/404, followed by quick retries from the UI or a startup loop; upstream 502 blip plus an aggressive retry.","solutions":["Read the embedded original message/status — fix the root cause (url path, auth headers) in MCP_SERVERS config","Wait out the ~5s cooldown; it clears automatically, or call evictFromPool(server) to clear it immediately after fixing config","Verify the endpoint manually: curl -v with the same URL and headers","For 5xx, check upstream health before retrying"],"exampleFix":"// before\nconst client = await getClient(server); // in cooldown after earlier 401\n\n// after\nimport { evictFromPool, getClient } from \"$lib/server/mcp/clientPool\";\n\n// after fixing the MCP_SERVERS url/headers:\nevictFromPool(server); // drop stale cooldown for the old (broken) config\nconst client = await getClient(server);","handlingStrategy":"retry","validationCode":"null","typeGuard":"function isCooldownError(e: unknown): boolean {\n\treturn e instanceof Error && /is in cooldown \\(HTTP/i.test(e.message);\n}","tryCatchPattern":"try {\n\tclient = await getClient(server);\n} catch (e) {\n\tif (isCooldownError(e)) {\n\t\t// wait out the ~5s window or evict after fixing root cause\n\t\tawait new Promise((r) => setTimeout(r, 5_000));\n\t\treturn getClient(server);\n\t}\n\tthrow e;\n}","preventionTips":["Fix the underlying HTTP failure (auth/URL) — the cooldown only mirrors it","Use evictFromPool(server) after a config fix to skip the stale window","Log the embedded original status/message so the root cause is not lost behind the cooldown wrapper"],"tags":["mcp","cooldown","circuit-breaker","http-error","retry"],"backgroundTag":"circuit-breaker-open","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}