Skip to main content
Glama

runpod-mcp — servidor MCP personalizado para la replicación Learning-to-Swim

Nota independiente: este servidor fue extraído (con su historial completo) del proyecto learning-to-swim-replication. Los enlaces relativos como ../runbook/RUNBOOK.md remiten a ese proyecto padre y solo se resuelven cuando este repositorio está dentro de él (o mediante un symlink); el servidor en sí se ejecuta de forma independiente.

Herramientas con forma de tarea (14) que reflejan el runbook/RUNBOOK.md del proyecto padre en lugar de ~50 espejos genéricos de API. Es un servidor personalizado porque ninguna API de RunPod ejecuta comandos en un pod — el MCP oficial solo cubre el plano de control; ejecutar pod_setup.sh, el barrido de verificación de los ejes y el entrenamiento exige SSH + rsync, codificado aquí con salvaguardas de coste en el código.

Arquitectura

.mcp.json → run.sh (venv bootstrap) → server.py (FastMCP, stdio; thin)
                                        └── runpod_mcp/
                                            config.py     Keychain key fetch + rpa_ scrubber
                                            api.py        REST v1 (pods/volumes/billing) + unauth GraphQL gpuTypes
                                            guardrails.py one-pod-per-vehicle (unknown refused) · 4090-only · no spot · volume required · confirm gate
                                            ssh.py        hardened ssh/scp/rsync; known_hosts_runpod; 60s conn cache
                                            jobs.py       detached jobs: /workspace/jobs/<id>/{cmd.sh,pid,out.log,exit_code,meta.json}
                                            training.py   DR tables (RUNBOOK/yaml-cross-checked) + verbatim train cmd
                                            supervise.py  Mac-side background CLI: launch→poll→pull→sync→spend→stop (reuses tools.*)
                                            watch.py      Mac-side ADVISORY observation CLI: discover job→tail out.log→parse metrics→page on plateau/failure/stall (read-only; never stops pods)
                                            remote/       job_wrapper.sh · idle_watchdog.sh · apply_bluerov2_patch.py
                                            deadman.py    Mac-side stop-pod fuse: arm --vehicle → sleep → stop with retries (per-vehicle pid/summaries)
supervise.sh → caffeinate -i wrapper around  python -m runpod_mcp.supervise
watch.sh     → caffeinate -i wrapper around  python -m runpod_mcp.watch   (live-pod behavior UNVERIFIED — fixture/mock-verified only; see CLAUDE.md §D)
deadman.sh   → caffeinate -i wrapper around  python -m runpod_mcp.deadman (arm/cancel REQUIRE --vehicle; bare status reports all vehicles)

Las arquitecturas clave:

  • Sin estado y por vehículo: «el pod» = lo que GET /pods devuelva que coincida con el nombre configurado del vehículo seleccionado (hippocampuslts-replication, bluerov2lts-replication-bluerov2; el parámetro vehicle de cada herramienta utiliza por defecto hippocampus; stop_pod/terminate_pod lo exigen de forma explícita); consola y MCP siempre coinciden. Solo hay estado local: una caché de 60 segundos (host, port) por Runtime del vehículo.

  • Trabajos asíncronos: una sola llamada SSH ejecuta setsid bash job_wrapper.sh <dir> <pod_id> <ceiling> <auto_stop>; el estado vive en el volumen de red, por lo que sobrevive a reinicios del MCP, a la suspensión del Mac y a paradas del pod. timeout --kill-after impone los topes de tiempo de reloj (exit 124); el sufijo de auto-stop se ejecuta DESPUÉS de que se escriba exit_code, así que un timeout nunca puede anularlo. El id del pod se inyecta como argv (las variables de entorno del contenedor no son fiables en shells BatchMode separados); se obtiene /etc/rp_environment para las credenciales de runpodctl; al armar auto_stop se sondea runpodctl de forma síncrona y si no funciona falla de forma ruidosa. La sonda (2026-08-09) es un diagnóstico de tres vías: las comprobaciones de shell puro no deciden nada (responden a H1-vs-H2 y captura el PATH puro), luego se hace source de /etc/rp_environment de forma incondicional, y el par tras el source lleva el veredicto — NO_RUNPODCTL (binario ausente incluso después del source, salida 90), NO_RUNPODCTL_AUTH_SOURCED (aún rechazado después del source, salida 91), PROBE_OK (éxito solo tras el source; NO_RUNPODCTL_AUTH_BARE es el diagnóstico intermedio que continúa).

  • Watchdog de inactividad: se reinstala en cada transición a running — el borrado del disco del contenedor elimina el material instalado en runtime (el propio idle_watchdog.sh, las librerías apt X11/GL, rsync), por eso se mantiene la instalación en cada transición; runpodctl viene EN LA IMAGEN y vuelve en cada arranque (un borrado restaura el disco desde la imagen, no lo vacía — corregido el 2026-08-09). Cada 5 min: sin pid de trabajo activo

    • sin sesión sshd + /workspace/.keepalive con más de 60 min de antigüedad → runpodctl stop pod. touch /workspace/.keepalive es la vía de escape de la sesión manual. Una instalación correcta informa de armed (stop path unverified) — la sonda certifica lectura (get pod), el watchdog requiere escritura (stop pod); la primera confirmación real es una entrada de parada correcta en /workspace/.idle_watchdog.log. Estado (2026-08-09): la sonda de instalación ha fallado en cada puesta en marcha registrada (rc=91 opaco antes del arreglo) — el watchdog nunca se ha llegado a armar; el defecto 2 llega DIAGNOSTICADO, no CERRADO, y el centinela de la próxima puesta en marcha lo resuelve. idle_watchdog: FAILED ⇒ arma el deadman del Mac antes de cualquier trabajo.

  • Las salvaguardas son código: un pod por vehículo declarado (se rechaza cualquier otro nombre de pod en la cuenta), RTX 4090 ×1, SECURE, interruptible forzado a false, volumen de red obligatorio, terminate_pod exige vehicle explícito más la cadena literal terminate <pod_name del vehículo> (p. ej., terminate lts-replication), un trabajo a la vez por pod salvo con force.

Instalación / registro

Registra el servidor en el .mcp.json de un proyecto (Claude Code) con una ruta absoluta a run.shrun.sh prepara su propio .venv en el primer arranque:

{
  "mcpServers": {
    "runpod": {
      "command": "bash",
      "args": ["/path/to/runpod-mcp/run.sh"]
    }
  }
}

Configuración

  1. Clave de API (nunca en disco/git/argv — solo en el Llavero de macOS; el servidor la lee con security find-generic-password y elimina los valores rpa_ de cualquier error y registro):

    security add-generic-password -a kyle -s runpod-api-key -w '<KEY>'

    (El nombre de cuenta de la consulta está actualmente fijo a kyle en runpod_mcp/config.py — ajústalos conjuntamente si tu cuenta de macOS difiere.)

  2. Clave SSH: debe existir ~/.ssh/id_ed25519(.pub); el .pub se inyecta en la creación del pod mediante la variable de entorno PUBLIC_KEY (lo que las imágenes runpod/pytorch realmente respetan — verificado en vivo; SSH_PUBLIC_KEY también se define como refuerzo extra). SSH directo a root@publicIp:portMappings["22"]; el SSH proxy de RunPod no se usa (no scp). Las claves de host van a un ~/.ssh/known_hosts_runpod dedicado, truncado en cada arranque del pod (la restauración del disco del contenedor regenera las claves de host; las entradas obsoletas solo causan falsos fallos de MITM).

  3. Nada más — run.sh crea .venv/ e instala requirements.txt en el primer arranque (condicionado por una marca).

Pruebas

runpod-mcp/.venv/bin/python -m pytest runpod-mcp/tests -q          # offline (default)
RUNPOD_MCP_LIVE=1 runpod-mcp/.venv/bin/python -m pytest \
    runpod-mcp/tests/test_live.py -q                               # live $0 read-only

Las pruebas offline usan ttpx.MockTransport + un SSH falso con duck typing — sin red, sin clave. Las pruebas en vivo son GETs de solo lectura + un handshake de stdio de MCP a través de run.sh (verifican que se registran las 14 herramientas). Las tablas de DR se comparan de forma cruzada analizando BLUEROV2/config/bluerov2_heavy.yaml, RUNBOOK.md y APPLY.md; el script de parches se ejercita contra extractos fixture confirmados de las fuentes fijadas en 7c5ebe7 (más una prueba condicionada por SHA contra el clon de referencia real cuando está disponible — solo lectura, copias temporales).

test_supervise.py conduce la CLI de supervise con fakes inyectados + un reloj falso (sin esperas reales) y cubre todas las ramas de seguridad: finalización normal, fallo del trabajo, parada forzada por --max-wait, negativa de pod no en ejecución, negativa de lanzamiento, errores de sondeo transitorios, fallo de captura que aun así detiene, --no-stop y se afirma que terminate_pod nunca se llama en ningún caso.

El pytest -q del repositorio raíz ignora esta carpeta (conftest.py collect_ignore) — el venv de la raíz, reducido al mínimo, no tiene mcp/httpx.

Ejecuciones supervisadas (supervise.sh)

Un único comando que encadena una ejecución completa: comprobar que el pod está en ejecución → derivar con dry-run un tope de tiempo de reloj finitolaunch(auto_stop=false) → sondear job_status → traer de forma incondicional /workspace/jobs/<job_id>/ + sync_logs + spend_reportstop_pod → resumen JSON persistente — de modo que el agente lo lanza una sola vez como tarea de fondo y recibe el aviso de finalización. Reutiliza runpod_mcp.tools.* (sin duplicación de lógica, hereda todas las salvaguardas) y nunca llama a terminate_pod. Esta es una CLI del Mac, no una herramienta MCP número 15: una herramienta que sondee durante minutos bloquearía el servidor stdio.

# training run (background task)
supervise.sh --training curee --dr DR_0 --seed 1 \
    [--interval 45] [--max-wait N] [--backstop 300] [--no-stop] \
    [--sync-subdir rsl_rl/warpauv_direct] [--summary-path PATH]

# generic job — --sync-subdir REQUIRED (pass 'none' to skip the analysis sync;
# the job-dir pull always happens); --vehicle routes the pod (default
# hippocampus; --training mode derives it from the training vehicle instead)
supervise.sh --job-name eval --command "…" --workdir /workspace \
    --sync-subdir <dir|none> [--max-runtime-sec N] [--vehicle bluerov2]

Seguridad del gasto: el bucle de sondeo tiene exactamente dos salidas: salida normal → stop_pod; o --max-wait (siempre finito) agotado mientras el estado sigue en running → parada forzada + código de salida no cero + marcador force_stopped en el resumen. Una denegación del lanzamiento → no se detiene (arregla y reintenta), salida 2. El resumen supervise-<job_id>.json, que se escribe en el directorio de logs del vehículo (logs/pod/ para hippocampus, logs/pod/bluerov2/ para bluerov2), es el contrato de recuperación (una sesión posterior concilia el estado de parada a partir de él). Advertencias de vitalidad: caffeinate -i protege contra la suspensión por inactividad pero no contra el cierre de la tapa; la supervivencia del run_in_background_ a través del reap de WarmLifecycle no está verificada — el tope del timeout en el trabajo es el respaldo garantizado; el watchdog de inactividad del pod lo secundaría, pero aún no se ha armado en ninguna puesta en marcha registrada (DIAGNOSTICO, no CERRADO — ver el punto anterior sobre el watchdog de inactividad), así que arma el deadman del Mac cuando ensure_pod reporte idle_watchdog: FAILED.

Cadenas de campaña (CUREE/chains/)

Un script bash por campaña (denominado por el ID de campaña, p. ej. chain-011-CUREE_Adaptive-weights.sh): toda la secuencia de trabajos del lado del pod de la campaña — parches, gates, entrenamientos, evaluaciones, syncs — como enlaces ordenados y fijados por SHA. Las cadenas se lanzan con supervise.sh (que posee la captura-y-parada), nunca a mano; son el registro duradero de lo que ejecutó exactamente una campaña.

Dry runs

ensure_pod, run_pod_setup, run_job, launch_training, apply_bluerov_patches aceptan dry_run=true y devuelven las cargas útiles/ediciones/comandos exactos sin modificar nada ($0). supervise usa esa vía de dry run para derivar su --max-wait finito antes del lanzamiento real.

Imagen de respaldo NGC (cambio manual — leer antes)

nvcr.io/nvidia/isaac-sim:4.5.0 (respaldo de Día 1 en el RUNBOOK) no trae sshd — lo que rompe toda la historia SSH de este servidor. Cambiarla exige un comando docker-start que instale/arranque sshd (no es un cambio de una línea: avisar a Kyle antes de tocar image_name en pod_defaults.yaml.

Riesgos conocidos (aceptados en la planificación)

  • La URL de descarga de IsaacSim 4.5.0 en pod_setup.sh puede devolver 404 — sale a la luz en el rastro del log de job_status; el arreglo es una edición del runbook, no un cambio del MCP.

  • La disponibilidad de la 4090 fluctúa según el DC; el volumen de red fija un DC; gpu_availability(data_center_id=...) y la receta de sarrecuperación sin GPU de ensure_pod lo cubren; en el peor caso, crea un segundo volumen en otro DC.

Licencia

MIT — consulta LICENSE.

-
license - not tested
Not graded
quality - not tested
B
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

  • Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.

  • Create and manage AI agents that collaborate and solve problems through natural language interacti…

  • Build, validate, and deploy multi-agent AI solutions from any AI environment.

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/kyle-nelson-berkeley/runpod-mcp'

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