local-fitness
Entrenador personal de running auto-alojado vía MCP
local-fitness es un entrenador personal de running auto-alojado que transforma datos de Garmin Connect en informes diarios estructurados, evaluaciones estelares de sesiones y planes adaptativos mediante el Protocolo de Contexto del Modelo (MCP). Arquitectura agent-first: el servidor actúa como capa de transporte determinista; toda la síntesis conversacional ocurre en el cliente agente.
¿Qué es y para quién?
Aplicación auto-alojada para runners que desean coaching personalizado basado en datos reales de su Garmin. Ideal para atletas individuales que valoran privacidad, transparencia algorítmica y control total sobre sus métricas. No es un servicio multi-tenant: una cuenta Garmin, una base SQLite local.
Casos de uso
- Generación automática de informes diarios de estado físico y recuperación (frecuencia cardíaca en reposo, sueño, Body Battery, estrés)
- Evaluación determinista de sesiones con puntuación estelar (1-5 estrellas) basada en desviaciones relativas normalizadas
- Redacción de planes de entrenamiento adaptativos mediante herramientas MCP expuestas a agentes
- Sincronización de calendario Google con el plan activo para gestión de citas
Características
- Daily auto-pull: desde Garmin Connect vía
garminconnect— recupera automáticamente si la máquina estuvo apagada días. - Historical backfill: carga única desde export ZIP oficial de Garmin para razonar sobre toda la historia.
- Pre-computed baselines: 60-day rolling mean/SD para resting HR, sleep, Body Battery, stress.
- Modelo Banister CTL/ATL/TSB: fitness / fatiga / forma calculado determinísticamente.
- Agent-first: coach vive en MCP client (Claude Desktop, Code, Mobile). El servidor no realiza inferencias de IA.
- Scheduled daily brief:
fitness briefgenera JSON y PDF; compositor read-only para automatización segura. - Star-rated report cards:
workout_report_cardjuzga sesiones con 1-5 estrellas, incluyendo splits por milla y gráfico HR/pace en PDF PRESS-themed. - Deterministic interpretation: cada clasificador está implementado en Python (
agent/interpret.py); el modelo solo formula juicios, no los deriva. - Runner-facing units: distancias y pace en millas/min por defecto (
LOCAL_FITNESS_DISPLAY_UNITS). - Privacy by default: base de datos, briefings y logs permanecen locales; gitignored.
Arquitectura
Flujo determinista: Garmin Connect → SQLite (data/fitness.db) → baselines/CTL-ATL-TSB → capa de herramientas MCP. El servidor no ejecuta inferencias; expone recursos y herramientas sobre MCP para que el agente realice síntesis conversacional, redacción de planes y generación de informes.
Garmin Connect ──pull──> SQLite (data/fitness.db) ──> baselines / CTL-ATL-TSB
│
deterministic tool layer ─────┤
│
┌──────────────────────────────────────┴───────────────┐
MCP server scheduled `fitness brief`
(Claude, Codex: composes brief → coach + prompts, tools, briefings/*.json)
resources, write surface
Requisitos reales
- Python 3.12+ y uv (gestor de paquetes)
- Cuenta Garmin Connect con historial
- Acceso a Claude Code o MCP compatible para trabajos programados
- macOS es plataforma primaria (scheduler usa
launchd, credenciales en Keychain). Linux/otros funcionan vía Docker o instalación nativa.
Instalación/despliegue
Local (macOS/Linux)
# 1. Clonar e instalar deps + comando `fitness`
git clone https://github.com/natejswenson/local-fitness.git
cd local-fitness
uv sync
# 2. Configuración (opcional, defaults funcionan)
cp .env.example .env
# 3. Credenciales Garmin y DB inicial
uv run fitness setup
# 4. Pull datos en vivo
eu run fitness pull
# 5. Backfill histórico (opcional, tras export ZIP de Garmin)
uv run fitness backfill ~/Downloads/garmin-export.zip
# 6. Recomputar baselines + training load
uv run fitness recompute-baselines
# 7. Generar briefing
eu run fitness brief
# 8. (macOS) Instalar trabajos programados
./ops/install-launchd.sh
# 9. (Opcional) MCP sobre HTTP
uv run fitness serve # http://127.0.0.1:8765/mcp/
Docker
El Dockerfile usa python:3.12-slim, instala curl, ca-certificates, nodejs, pango, fonts-dejavu para WeasyPrint. Clona @anthropic-ai/claude-code@2.1.119 y @openai/codex@0.157.1. Monta volúmenes en /data, /briefings, /home/app/.garminconnect, /home/app/.claude, /home/app/.codex. Requiere LOCAL_FITNESS_API_TOKEN cuando no está en loopback.
Uso
fitness pull # pull since last success
fitness backfill <zip> # one-time full-history load from Garmin export ZIP
fitness recompute-baselines # rebuild 60-day baselines + CTL/ATL/TSB
fitness recompute-body-battery # re-derive body-battery aggregates from samples
fitness brief # pull + recompute + briefing → briefings/YYYY-MM-DD.json
fitness brief --opus # use Opus model for one run
fitness brief-email # regenerate today's brief and email it
fitness plan-calendar # reconcile Google Calendar to active plan
fitness calendar-auth # one-time Google OAuth flow
fitness config set|get <key> # read/write settings-table key (e.g. user_name)
fitness serve # MCP over HTTP at http://127.0.0.1:8765/mcp/
fitness mcp-stdio # serve tools to MCP client over stdio
fitness status # DB row counts + last ingest run info
MCP — Interacción vía agente
El MCP es la única forma de interactuar con el coach; no hay UI web. Se exponen 48+ herramientas (47 sobre HTTP), 2 prompts y 2 recursos. Conexión local sin token: claude mcp add --transport stdio fitness -- uv run fitness mcp-stdio. Conexión HTTP con token: claude mcp add --transport http fitness https://<host>/mcp/ --header "Authorization: Bearer $TOKEN".
Prompts principales:
coach: ensambla snapshot diario completo (métricas vs baseline, training load, workouts recientes) + persona coach + preferencias guardadas.brief: compone briefing estructurado diario y lo persiste víasave_brief.
Seguridad
El servidor autentica cada ruta no pública (/health excluido) con token bearer LOCAL_FITNESS_API_TOKEN. En hosts no loopback, el token es obligatorio; el servidor se refusa a iniciar sin él. Generar: python -c "import secrets; print(secrets.token_urlsafe(32))". Rotación de token actualiza config del cliente; no hay sesión de navegador que re-promptear.
Actualización/backup
Actualizar: git pull, luego uv sync. Recomputar baselines tras cambios de datos: fitness recompute-baselines.
Backup: copiar data/fitness.db, briefings/, y credenciales (.garminconnect/, .claude/, .codex/). El servidor no ejecuta inferencias; no hay endpoints costosos que drenen suscripción.
Ventajas
- Arquitectura agent-first mantiene inferencia en el cliente, no en servidor
- Análisis determinista en Python con código interpretable y verificable (
agent/interpret.py) - Privacidad por diseño: toda la DB y logs permanecen locales sin salida de datos innecesaria
- Modelo Banister CTL/ATL/TSB implementado determinísticamente, no como black-box
- PDF exports con gráficos HR/pace y splits por milla en formato PRESS-themed
Límites
- No incluye interfaz web; toda interacción requiere cliente MCP compatible
- Funcionalidad limitada en dispositivos Garmin que no reportan HRV Status (ej. Instinct Solar)
- Dependencia de suscripción a Claude para agente interactivo y trabajos programados
- Requiere configuración manual de credenciales Garmin (Keychain o env vars)
Alternativas reales
Soluciones SaaS como TrainingPeaks, Strava Segments Pro o Garmin Connect IQ ofrecen dashboards web y apps móviles nativas. local-fitness se diferencia por: análisis determinista verificable, privacidad total (todo local), arquitectura agent-first que integra con tu stack MCP existente, y capacidad de personalización mediante prompts y herramientas expuestas.
FAQ
- ¿Funciona sin suscripción a Claude?
- Sí, el servidor no ejecuta inferencias. El agente interactivo requiere MCP client con suscripción (Claude Code o compatible).
- ¿Puedo usarlo en Linux?
- Sí, vía instalación nativa o Docker. macOS es plataforma primaria por conveniencia de launchd y Keychain.
- ¿Cómo se calculan las estrellas de los workouts?
- Cada métrica (distance, pace, HR, continuity) reduce a una desviación relativa normalizada que pasa por una curva compartida en escala 1-5 con precisión fraccional.
- ¿Los datos salen de mi máquina?
- No. La DB, briefings y logs permanecen locales; solo el agente lee métricas cuando lo solicita o escribe un briefing.