Skip to content

feat: add multi API key load balancing with quota-aware routing and failover - #185

Open
touful wants to merge 4 commits into
tavily-ai:mainfrom
touful:feat/multi-key-loadbalancer
Open

feat: add multi API key load balancing with quota-aware routing and failover#185
touful wants to merge 4 commits into
tavily-ai:mainfrom
touful:feat/multi-key-loadbalancer

Conversation

@touful

@touful touful commented Jul 10, 2026

Copy link
Copy Markdown

Summary

Adds multi API key load balancing to the MCP server, enabling transparent pooling of multiple Tavily API keys with quota-aware routing, automatic exhausted-key detection, and failover — eliminating service interruptions caused by single-key quota exhaustion.

Motivation

Single free-tier API keys (1,000 credits/month) are insufficient for heavy usage. Currently, when a key's quota runs out, users must manually swap the key and restart the service, causing downtime. The existing TAVILY_API_KEY env var only supports a single key, and suffixed variants (TAVILY_API_KEY_*) are silently ignored.

This PR introduces a KeyManager that pools multiple keys and automatically routes requests to the key with the most remaining quota, detects and cools down exhausted keys (HTTP 432/433), and fails over to other keys on errors — all transparent to the MCP client.

Changes

  • New file src/keyManager.ts: KeyManager class managing the key pool — loads keys from env, queries /usage for remaining quota, selects keys by highest-remaining-quota-first strategy, handles error codes (432/433 cool down, 429 retry-after, 401 remove), periodic refresh every 5 min.
  • Modified src/index.ts: Integrates KeyManager; new makeAuthenticatedRequest method unifies key selection + failover retry across all endpoints; research polling now uses the same key as the initial request to avoid 404 task loss.

Configuration

Three supported env var formats (by priority):

  1. TAVILY_API_KEYS — comma-separated keys (recommended): "key1,key2,key3"
  2. TAVILY_API_KEY_<suffix> — multiple suffixed vars (backward-compatible with existing configs)
  3. TAVILY_API_KEY — single key (fully backward-compatible, degrades to original behavior)

Load Balancing Strategy

  • Quota-first: Queries GET /usage per key at startup + every 5 min, selects the key with the most limit - usage remaining.
  • Degraded round-robin: When limit is null (free-tier tvly-dev keys), falls back to randomized round-robin (per user request: "use quota if available, otherwise average").
  • Error-driven detection (primary for free keys): On HTTP 432/433 → cool down key 1h, failover to next key; 429 → read retry-after header, temporary cool down (cap 5 min); 401 → permanently remove key.
  • Failover retry: makeAuthenticatedRequest retries with other keys until success or all keys exhausted.

Backward Compatibility

  • All 5 tools (search/extract/crawl/map/research) unchanged in params and behavior.
  • TAVILY_HUMAN_ID, DEFAULT_PARAMETERS, and keyless mode all preserved.
  • stdio MCP protocol unchanged.

Testing

  • 61 unit tests (vitest), 93%+ line coverage.
  • MCP stdio integration test: initialize handshake, tools/list (5 tools), real tavily_search call verified.
  • Real-key validation: 3 tvly-dev keys (one quota-exhausted) — exhausted key correctly detected via 432 and cooled down, requests routed to available keys.

Known Limitations

  • Free-tier tvly-dev keys return limit: null from /usage, so proactive quota detection is unavailable for them; exhausted-key detection relies on 432 error responses (passive). The degraded round-robin + error-driven failover still prevents service interruption.
  • selectKey() is non-atomic; acceptable for stdio MCP's serial request model.

@touful
touful requested a review from a team July 10, 2026 07:49
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for this team, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for this team, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant