Skip to main content
Glama
Tom-Chencao

Tavily MCP Key Pool

by Tom-Chencao

Tavily MCP Key Pool

README en chino simplificado: README.zh.md

¿Por qué? Si tienes múltiples claves API de Tavily (varias cuentas, un presupuesto de equipo, créditos comprados al por mayor, …) y las usas a través de un agente de codificación de IA, te encontrarás rápidamente con tres problemas:

  1. Cuellos de botella de una sola clave — el límite de tasa de una clave lo ralentiza todo.

  2. Fallos silenciosos — una clave caduca, alcanza una cuota o es revocada, y tus búsquedas simplemente... dejan de funcionar.

  3. Sin visibilidad — no sabes qué claves se están usando ni cuánto.

Este proyecto resuelve los tres: un pequeño servidor MCP que distribuye en round-robin entre tu pool de claves, desactiva automáticamente las claves muertas y expone estadísticas de uso, para que puedas incorporarlo en Claude Desktop, Cursor, DeepSeek Harness o cualquier cliente MCP sin cambiar tu flujo de trabajo.

Un servidor MCP de Tavily con un pool de claves API round-robin respaldado por SQLite, seguimiento de uso incorporado, conmutación por error automática basada en salud y un panel FastAPI independiente. Protocolo MCP estándar — funciona con cualquier cliente compatible con MCP (Claude Desktop, Cursor, DeepSeek Harness, etc.).

Aspectos destacados

  • 🔄 Rotación de claves round-robin entre N claves API de Tavily (SQLite, sin coste de inicio).

  • 📊 Seguimiento de uso: conteo de solicitudes por clave, conteo de errores, créditos consumidos.

  • 🩺 Verificación de salud automática: prueba todas las claves con una búsqueda ligera, desactiva automáticamente las muertas; expone resultados mediante tavily_pool_status.

  • 🛠️ Seis herramientas MCP principales (paridad con Tavily: búsqueda, extracción, rastreo, mapa, investigación) más tavily_pool_status y tavily_research_status (obtención asíncrona).

  • 🌐 Panel FastAPI independiente (con CORS habilitado, solo loopback) con estadísticas, vista por clave, agregar/eliminar/desactivar/activar, y prueba de salud con un clic.

  • 🔌 Plug-and-play para cualquier cliente MCP mediante stdio; la integración con DSH es un parche de una página + un plugin de cliente de ejemplo (ver examples/dsh-integration/).

Cómo se diferencia del tavily-mcp oficial

Característica

tavily-mcp oficial

Este repositorio

Variable de entorno de una sola clave API

Múltiples claves, round-robin

✅ Pool SQLite

Estadísticas de uso por clave

✅ conteo de solicitudes + créditos + errores

Prueba de salud + desactivación automática

Panel independiente

✅ FastAPI en 127.0.0.1:8000

Paridad de herramientas MCP (búsqueda/extracción/rastreo/mapa/investigación)

✅ (más los extras de estado del pool / estado de investigación)

Sondeo de investigación asíncrono

(manual)

✅ incorporado tavily_research + tavily_research_status

Arquitectura

+--------------------------------------------------+
|  MCP clients (Claude Desktop / Cursor / DSH …)   |
+--------+---------------------+-------------------+
         | stdio (JSON-RPC)     | HTTPS / CORS
+--------▼--------------+     +▼-----------------------+
|  mcp_server.py (FastMCP)|     |  dashboard.py (FastAPI) |
|  + key_pool.py (SQLite) |     |  uvicorn 127.0.0.1:8000 |
+----------------------+--+     +-----+----------------+
                       |              |
                       v              v
                tavily_keys.db  <— SQLite-backed pool
                       |
                       v
              Tavily REST API (round-robin over N keys)

Inicio rápido

1. Instalar dependencias

python -m venv .venv
. .venv/bin/activate        # Linux/macOS
# or:  .venv\Scripts\Activate.ps1   (Windows PowerShell)
pip install -r requirements.txt

La restricción fijada de mcp en requirements.txt es <2.0: ver Integración con DSH / Escollo #1 — la ruta de importación de FastMCP se movió en mcp 2.x.

2. Agregar claves API

Crea un keys.txt con una clave por línea:

tvly-xxxxxxxxxxxxxxxx
tvly-yyyyyyyyyyyyyyyy

Luego impórtalas:

python cli.py add --from-file keys.txt

O inicia el panel (siguiente paso) y pégalas en el formulario Agregar claves API. Las claves se almacenan en texto plano en tavily_keys.db (SQLite) para que el pool pueda hacer round-robin sin coste de inicio — ver Seguridad.

3. Iniciar el servidor MCP

Para un servidor MCP stdio directo (cualquier cliente MCP):

./run_mcp.sh                                # Linux/macOS
# or:  .venv\Scripts\python.exe mcp_server.py   (Windows)

El servidor anuncia siete herramientas; los nombres públicos en clientes compatibles con MCP se ven como tavily_search, tavily_extract, etc.

4. Iniciar el panel (opcional, proceso independiente)

./run_dashboard.sh                          # default port 8000
# or:  .venv\Scripts\python.exe -m uvicorn dashboard:app --host 127.0.0.1 --port 8000

Abre http://127.0.0.1:8000 en tu navegador. El panel tiene CORS habilitado para orígenes de loopback, de modo que un panel de configuración incrustado en otra interfaz pueda llamarlo.

Herramientas MCP

Herramienta

Propósito

tavily_search

Búsqueda web (básica/avanzada, tema, rango de tiempo, incluir/excluir dominios, país, etc.)

tavily_extract

Extraer contenido limpio de URLs

tavily_crawl

Rastrear un sitio web y extraer contenido de múltiples páginas

tavily_map

Descubrir URLs en un sitio (más rápido que rastrear)

tavily_research

Investigación profunda de IA (30–120s+; usa sondeo en segundo plano internamente — ver Escollo #2)

tavily_pool_status

Estadísticas del pool: claves activas, total de solicitudes/errores/créditos, desglose de las últimas 24h

tavily_research_status(request_id)

Obtener el resultado de una tarea de investigación asíncrona que agotó el tiempo de espera

CLI

python cli.py list                 # all keys
python cli.py list --active        # only active
python cli.py stats                # JSON dump of pool state
python cli.py health               # probe every active key; deactivate dead ones
python cli.py recent -n 20         # recent request log
python cli.py add tvly-... [...]   # add one or more keys
python cli.py add --from-file keys.txt
python cli.py activate tvly-xx****yy     # masked id, see `list`
python cli.py deactivate tvly-xx****yy --reason "manually disabled"
python cli.py remove tvly-xx****yy

Uso con Claude Desktop / Cursor / otros clientes MCP genéricos

Para cualquier cliente que acepte un comando MCP stdio:

{
  "mcpServers": {
    "tavily": {
      "command": "/absolute/path/to/.venv/bin/python3",
      "args": ["mcp_server.py"],
      "cwd": "/absolute/path/to/this/repo"
    }
  }
}

O HTTP transmisible si tu cliente lo soporta y tú mismo has envuelto el servidor en un transporte HTTP — fuera del alcance de este repositorio.


Integración con DeepSeek Harness (DSH)

Probado con @deepseek-ai/dsh 0.1.0-rc.6 (perfil web).

El DeepSeek Harness (dsh) utiliza el framework de plugins Cordis y viene con un puente oficial de cliente MCP (@deepseek-ai/dsh-mcp-client). Por lo tanto, la integración es muy ligera: una capa de parche de usuario + un plugin de ejemplo del lado del navegador (el examples/dsh-integration/client-tavily-panel/ de este repositorio).

A. Registrar el servidor MCP de Tavily en DSH

Edita ~/.dsh/profiles/web/cordis.patch.yml (la capa de parche de usuario aplicada después de cada paquete). Agrega un nuevo bloque insert — los valores siguientes asumen que el repositorio está en C:\Users\ASUS\.dsh\tavily-pool\:

- insert:
    - id: mcp-tavily
      name: '@deepseek-ai/dsh-mcp-client'
      config:
        transport: stdio
        serverName: tavily
        command: 'C:\Users\ASUS\.dsh\tavily-pool\.venv\Scripts\python.exe'
        args: ['mcp_server.py']
        cwd: 'C:\Users\ASUS\.dsh\tavily-pool'
        # research can take >2 minutes on big topics; the default 30s is too tight
        toolCallTimeoutMs: 600000
        failOnStartupError: false

Verifica la fusión con dsh --profile web --dump-config antes de reiniciar. El servidor MCP aparece entonces como mcp__tavily__tavily_search (etc.) en la lista de herramientas del agente.

B. (Opcional) Incrustar el panel en la configuración de DSH

Copia examples/dsh-integration/client-tavily-panel/ en cualquier lugar del disco. El ejemplo usa el slot settings.section de @deepseek-ai/dsh-client-ui-slots — el plugin registra un panel Tavily 号池 que llama al panel mediante fetch. Para instalarlo:

  1. Coloca el paquete (ej. ~/.dsh/plugins/client-tavily-panel/).

  2. Enlázalo en el node_modules del perfil para que require.resolve pueda encontrarlo (DSH carga los plugins del cliente a través de su cadena de resolución de nombres de paquete):

    New-Item -ItemType Junction `
      -Path "$env:DSH_HOME\profiles\node_modules\dsh-client-tavily-panel" `
      -Target "C:\Users\ASUS\.dsh\plugins\client-tavily-panel"

    Un enlace (junction, no symlink) evita necesitar permisos de administrador. Si omites esto y agregas el paquete localmente con pnpm add, está bien, pero ten cuidado: pnpm puede atascarse con otras dependencias no relacionadas file: / de fuente GitHub en tu perfil.

  3. Agrega una entrada en el roster a cordis.patch.yml:

    - insert:
        - id: client-tavily-panel
          name: 'dsh-client-tavily-panel'
  4. Reinicia dsh web. (Ver Escollo #6 — HMR está deshabilitado intencionalmente para el perfil web; los cambios de parche solo se cargan al reiniciar completamente).

Después del reinicio, abre ⚙️ Configuración — la entrada Tavily 号池 aparece en la navegación izquierda.

Escollos encontrados durante la integración con DeepSeek

Estos son errores reales que yo (el integrador original) encontré. Léelos antes de empezar, en el orden siguiente — cada uno hizo perder tiempo.

Escollo #1: Versionado del SDK mcp

mcp_server.py hace from mcp.server.fastmcp import FastMCP. Ese módulo fue eliminado en mcp 2.0 (la implementación de FastMCP se movió a un paquete separado fastmcp con una API diferente). Si ejecutas pip install mcp y obtienes la última versión, el servidor MCP se niega a iniciar:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'

Fíjalo:

# requirements.txt
mcp>=1.0.0,<2.0.0

Probado con mcp 1.29.0.

Escollo #2: tavily_research es asíncrono y está vinculado a la clave que lo creó

Tres sub-errores en uno:

  • El SDK tavily-python renombró el primer argumento posicional de research() de query a input. Llamar client.research(query=…) falla con missing 1 required positional argument: 'input'.

  • El SDK exige model ∈ {"mini", "pro", "auto"} en tiempo de ejecución, pero la API REST de Tavily acepta model=standard|pro. Pasar standard lanza model must be one of: mini, pro or auto.

  • research() devuelve inmediatamente un sobre con status: pending — el resultado real llega 30–120+ segundos después. Debes sondear get_research(request_id) hasta que status == "completed". De lo contrario, la herramienta siempre devuelve "pending" y tu modelo piensa que la llamada falló.

  • La tarea de investigación está vinculada a la clave API que la creó. Otras claves en el pool no pueden obtener el resultado (devuelve 404). Siempre sondea con la misma instancia de TavilyClientno vuelvas a llamar pool.next_key() en cada iteración de sondeo, o seguirás golpeando claves incorrectas.

El tavily_research de este repositorio ya envuelve el ciclo de vida completo: sondea hasta ~570s, luego devuelve un sobre con status: timeout y el request_id para que el llamador pueda obtenerlo después. Una segunda herramienta, tavily_research_status(request_id), recorre la lista de claves activas para encontrar la clave correcta para una obtención ad hoc — necesaria porque la llamada a la herramienta podría haber agotado el tiempo de espera en un proceso diferente.

Escollo #3: Error de lectura UTF-8 en dashboard.py en Windows

dashboard.py hace:

DASHBOARD_HTML = TPL.read_text()

Path.read_text() usa por defecto locale.getpreferredencoding(), que es GBK en Windows (zh-CN). La plantilla templates/dashboard.html es UTF-8 y contiene caractres CJK, por lo que el panel lanza:

UnicodeDecodeError: 'gbk' codec can't decode byte 0xb6 in position 4308

Solución:

DASHBOARD_HTML = TPL.read_text(encoding="utf-8")

Escollo #4: Rutas multiplataforma en run_*.sh

run_mcp.sh y run_dashboard.sh tienen codificadas .venv/bin/python3 (conveciones de Linux) y nunca fueron probadas en Windows. Los autores también incluyeron una unidad systemd usando /home/user/code/Tavily — claramente solo Linux.

No necesitas ninguno de estos scripts en Windows; simplemente invoca .venv\Scripts\python.exe directamente (ver el YAML arriba). Se mantienen en el repositorio para el caso de uso original de Linux.

Escollo #5: La configuración del parche de DSH solo se carga al inicio

cordis.patch.yml se lee cuando el perfil web arranca. Los cambios no se recargan en caliente — la fila hmr en el parche de la aplicación web está intencionalmente deshabilitada:

- id: hmr
  disabled: true
# TODO: Re-enable shared HMR for Web after its reload lifecycle is tested.

Por lo tanto, después de cada edición de cordis.patch.yml, reinicia dsh web (ver Escollo #6 sobre cómo hacer esto de forma segura).

Usa dsh --profile web --dump-config para verificar que tu parche se fusiona correctamente sin iniciar realmente la GUI. Es mucho más rápido que iniciar, verificar la GUI, cerrar, corregir, repetir.

Escollo #6: Cómo reiniciar dsh web sin matarte

dsh web es el proceso anfitrión que ejecuta esta conversación, incluido tu proceso de herramienta. Si ejecutas ingenuamente

Stop-Process -Id <dsh-web-pid> -Force
Start-Process dsh.cmd web

desde un pwsh que el mismo dsh web generó, te matarás a mitad del comando antes de que la nueva instancia se inicie. La primera vez que lo intenté, la sesión de PowerShell se abortó con exit code 4294967295 y no pasó nada.

La solución: entrega el reinicio al Programador de tareas de Windows, que ejecuta el script bajo svchost (no bajo dsh web):

$script = "$env:TEMP\dsh_restart.ps1"
@"
Start-Sleep -Seconds 8
Stop-Process -Id <dsh-web-pid> -Force
Get-CimInstance Win32_Process |
  Where-Object { `$_.CommandLine -match 'dsh web' } |
  ForEach-Object { Stop-Process -Id `$_.ProcessId -Force }
Start-Sleep -Seconds 3
Start-Process 'C:\…\dsh.cmd' web -WorkingDirectory 'H:\…' -WindowStyle Hidden
"@ | Out-File $script -Encoding utf8

schtasks /create /tn dsh-restart /tr "powershell -NoProfile -File $script" /sc once /st 23:59 /f
schtasks /run /tn dsh-restart
schtasks /delete /tn dsh-restart /f

Entonces tienes ~8 segundos para devolver tu respuesta final antes de que la instancia antigua muera. Dile al usuario que refresque http://127.0.0.1:3080 después de 20–30 segundos.

Escollo #7: Migrar el directorio de herramientas mientras el servidor MCP está en ejecución

El mcp-client de DSH se reconecta ante la pérdida de conexión con retroceso exponencial (initialDelayMs 500, maxAttempts 10). Matar el proceso hijo de Python desencadena una reconexión — que genera un nuevo hijo inmediatamente. Si luego intentas usar Move-Item sobre el directorio, el nuevo .venv\Scripts\python.exe tiene el archivo bloqueado y robocopy falla con [Result: 32] / "siendo usado por otro proceso".

Dos estrategias viables:

  • Copiar primero, luego eliminar el origen. Copy-Item lee archivos bloqueados mediante el uso compartido de archivos de Windows; no necesita acceso exclusivo. Después de que la copia se realice correctamente, mata el servidor MCP antiguo + elimina el origen. .venv es totalmente reubicable siempre que la línea home = de pyvenv.cfg siga apuntando a la misma instalación base de Python.

  • Bucle de matar + robocopy /MOVE hasta que tenga éxito dentro de la ventana de retroceso. Feo pero funciona.

La migración original usaba:

Copy-Item -Path D:\Downloads\Tavily -Destination C:\Users\ASUS\.dsh\tavily-pool -Recurse -Force
# verify copy
Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'python.exe' -and $_.CommandLine -match 'mcp_server' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
# loop until deletion succeeds
for ($i=0; $i -lt 8; $i++) {
  Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'mcp_server' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
  Start-Sleep -Milliseconds 200
  Remove-Item D:\Downloads\Tavily -Recurse -Force -ErrorAction SilentlyContinue
  if (-not (Test-Path D:\Downloads\Tavily)) { break }
  Start-Sleep -Seconds 2
}

Error #8: pnpm add puede detenerse por dependencias no relacionadas

Cuando ejecutas dsh plugin --profile web add <dir> para instalar un plugin local, pnpm resuelve todo el espacio de trabajo del perfil — incluyendo cualquier paquete obtenido de GitHub o HTTP que tu package.json liste. Si tu perfil ya incluye algo como dsh-files: https://codeload.github.com/...tar.gz/... y esa descarga se detiene (firewall, DNS, caché fría, cuota de registro), tu plugin local nunca se instala y pnpm se cuelga durante todo el tiempo de espera.

Solución alternativa: omitir pnpm y crear la resolución tú mismo:

New-Item -ItemType Junction `
  -Path "$env:DSH_HOME\profiles\node_modules\dsh-client-tavily-panel" `
  -Target "<absolute path to your plugin package>"

Los enlaces (junctions, no symlinks) funcionan sin derechos de administrador y se comportan de manera idéntica para require.resolve. La capa de parche entonces referencia el paquete por su campo name, exactamente como si pnpm lo hubiera instalado.

Error #9: Formato del plugin cliente del panel de configuración

Si escribes tu propio plugin cliente de DSH (lado del navegador), el formato en tiempo de ejecución no es ESM, no es Cordis-from-source. El plugin dsh-client-modules aloja un pequeño cargador de módulos en memoria y obtiene cada paquete cliente desde /plugins/<id>/client.js. El paquete debe llamar:

window.__ModuleLoader__.load({
  id: "your-package-name",   // matches package.json "name"
  factory: (require) => {
    var module = { exports: {} };
    var exports = module.exports;
    var react = require("react");           // available
    var jsx = require("react/jsx-runtime"); // available
    // ... define components ...
    function apply(ctx) {
      ctx.slots.inject("settings.section", () => ctx.slots.register({
        name: "settings.section",
        id: "your-id",
        order: 100,
        label: "Your Label"
      }, YourComponent));
    }
    exports.apply = apply;
    exports.inject = ["slots"];             // services you depend on
    return module.exports;
  }
});

Y tu package.json debe incluir:

{
  "main": "lib/index.js",
  "exports": { "./client": { "default": "./lib/client.js" } },
  "dsh": { "client": { "inject": ["@deepseek-ai/dsh-client-ui-slots"], "platform": "web" } }
}

lib/index.js es la entrada del host — se ejecuta del lado del servidor; puede ser un no-op (function apply() {}; export { apply };).


Seguridad

  • Claves en texto plano en reposo. tavily_keys.db almacena tus claves de API de Tavily en texto claro porque el pool basado en SQLite se consulta en cada solicitud. Protege el archivo con permisos del sistema de archivos (Linux: chmod 600). Nunca hagas commit de tavily_keys.db (ver .gitignore).

  • Panel de control solo en loopback por defecto. dashboard.py se vincula a 127.0.0.1:8000. Si lo expones en una LAN, agrega autenticación inmediatamente.

  • CORS está abierto de par en par intencionalmente — el panel está diseñado para ser llamado por UIs integradas en el mismo host. Esto es seguro debido al enlace loopback, pero si cambias la dirección de enlace, restringe CORSMiddleware.allow_origins para que coincida.

  • Rotar una clave filtrada: python cli.py remove tvly-xxxxxxxx****yyyy, revócala en el panel de Tavily, repite para cada fila en el pool.

Solución de problemas

Síntoma

Causa / solución

ModuleNotFoundError: No module named 'mcp.server.fastmcp'

mcp es ≥ 2.0; fijar a <2.0 (Error #1)

TavilyClient.research() missing 1 required positional argument: 'input'

Llamada de estilo antiguo — mcp_server.py ya usa input= (Error #2)

model must be one of: mini, pro or auto

Restricción a nivel de SDK, mapeada a auto en este repositorio (Error #2)

La investigación siempre devuelve pending

¿Llamaste a get_research después de research? Este repositorio lo hace por ti

UnicodeDecodeError: 'gbk' codec can't decode…

Error de lectura de HTML del panel (Error #3); corregido en este repositorio

Archivos node.exe y python.exe bloqueados durante el movimiento

Mata el servidor MCP, copia primero, elimina después (Error #7)

Herramientas registradas pero la sesión de DSH no las ve

¿Reiniciaste dsh web? Los parches solo se cargan al inicio (Error #5)

__DSH_BOOT__ no lista tu plugin

Problema de enlace/resolve (Error #8); verificar con dsh --profile web --dump-config

Créditos

El código de gestión del pool (key_pool.py, dashboard.py, el esqueleto FastMCP mcp_server.py, cli.py) fue escrito originalmente por un autor no atribuido y compartido públicamente. Este repositorio añade:

  • Compatibilidad con mcp 1.x (queryinput, mapeo de model, sondeo de investigación).

  • Una nueva herramienta tavily_research_status para fetch asíncrono.

  • Correcciones multiplataforma para Windows (lectura UTF-8 en dashboard.py).

  • Un plugin cliente de panel de configuración listo para usar para DSH, y el registro de errores de integración anterior.

Si conoces al autor original, por favor abre un issue para que pueda añadir un crédito.

Licencia

MIT. Ver LICENSE.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • One API key for 6 AI models. Pay-per-use. MCP protocol support with web search.

  • Web search for AI agents — one tool across 6 engines, routed to the cheapest + cached.

  • Zenrows MCP server — Fetch, Extract, Batch, and Browser Sessions for AI coding assistants

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Tom-Chencao/a-beginner-s-warehouse'

If you have feedback or need assistance with the MCP directory API, please join our Discord server