MCP Hub
Back to servers

livetrack-mcp

An autonomous MCP server that polls Garmin LiveTrack data during races, stores time-series metrics in SQLite, and triggers periodic Claude analysis for real-time coaching feedback.

glama
Updated
Apr 15, 2026

livetrack-mcp

Autonomous MCP server that polls Garmin LiveTrack, stores time-series in SQLite, and triggers Claude analysis every 10 minutes via claude-runner.

Architecture

Garmin LiveTrack URL
    │
    │  (poll every 60 s)
    ▼
livetrack-mcp (port 38100)
    ├── poller.py      — fetch trackpoints from Garmin API
    ├── store.py       — SQLite time-series persistence (/data/livetrack.db)
    ├── tracker.py     — asyncio scheduling + race-end detection
    └── analyzer.py    — build prompt, call claude-runner
         │
         │  POST /run (fire-and-forget)
         ▼
    claude-runner (port 38095)
         │
         │  claude -p <analysis prompt>
         ▼
    Claude (sonnet)
         ├── analyze timeseries
         ├── curl POST /control if thresholds need adjustment
         └── mcp__telegram__send_message → coaching push

Key design: livetrack-mcp is fully autonomous — no Claude session needs to stay alive during the race. Claude is called as a stateless analysis function every 10 minutes. If claude-runner is temporarily unavailable, the next analysis cycle retries automatically.

MCP Tools

ToolDescription
start_tracking(url, race_config)Start polling a LiveTrack share URL
stop_tracking()Stop polling (also auto-stops at race end)
get_tracking_status()Active state, elapsed time, stale time, poll errors
get_timeseries(minutes=10)Recent data from SQLite
update_thresholds(updates)Update HR/power thresholds mid-race
trigger_analysis()Manual on-demand analysis, bypassing schedule

Custom HTTP Endpoints

EndpointMethodDescription
/controlPOSTUpdate thresholds mid-race (called by Claude via curl in Bash tool)
/healthGETHealth check — tracking state + store stats

/control usage (from Claude's analysis prompt)

curl -sf -X POST http://localhost:38100/control \
  -H 'Content-Type: application/json' \
  -d '{"power_max": 150}'

Allowed fields: hr_max, hr_min, power_max, power_min, cadence_min, run_hr_max, run_hr_min, run_cadence_min

race_config Fields

FieldTypeDefaultDescription
hr_maxintCycling HR ceiling (bpm)
hr_minintCycling HR floor
power_maxintERG power ceiling (watts)
power_minintERG power floor
cadence_minintMinimum cycling cadence (rpm)
run_hr_maxintRun HR ceiling
run_hr_minintRun HR floor
run_cadence_minintMinimum run cadence (spm)
poll_interval_secsint60How often to poll LiveTrack
analyze_interval_secsint600How often to trigger Claude analysis
analyze_window_minint10Data window passed to Claude (minutes)

Example race_config for a full triathlon

{
  "race_name": "CT2026",
  "race_type": "triathlon",
  "hr_max": 144,
  "hr_min": 115,
  "power_max": 165,
  "cadence_min": 82,
  "run_hr_max": 152,
  "run_hr_min": 125,
  "run_cadence_min": 165,
  "poll_interval_secs": 60,
  "analyze_interval_secs": 600,
  "analyze_window_min": 10
}

Race-End Detection

The server auto-stops when:

  • No new trackpoints for ≥ 15 minutes (STALE_STOP_MIN)
  • AND total elapsed time ≥ 30 minutes (MIN_ELAPSED_MIN)

This handles the Garmin 24-hour URL delay: the URL remains valid after the race, but new trackpoints stop arriving when the athlete finishes. The 30-minute minimum prevents false stops at the start when GPS data is sparse.

Configuration (Environment Variables)

VariableDefaultDescription
PORT38100Server port
HOST0.0.0.0Bind address
MCP_PATH/mcpMCP endpoint path
DB_PATH/data/livetrack.dbSQLite database path
CLAUDE_RUNNER_URLhttp://localhost:38095claude-runner base URL
RUNNER_WORKSPACEtrainingWorkspace for claude-runner tasks
LOG_LEVELINFOLogging level
OTEL_EXPORTER_OTLP_ENDPOINTOpenTelemetry collector URL (optional)

Deployment

cd ~/ai-platform/mcps

# Build and start
docker compose up -d --build livetrack-mcp

# Logs
docker compose logs -f livetrack-mcp

# Restart
docker compose restart livetrack-mcp

# Health check
curl http://localhost:38100/health

Typical Session (via Claude in training workspace)

# Start tracking
use_mcp_tool livetrack-mcp start_tracking \
  url="https://livetrack.garmin.com/session/.../token/..." \
  race_config={"hr_max": 144, "power_max": 165, "run_hr_max": 152}

# Check status
use_mcp_tool livetrack-mcp get_tracking_status

# Manual analysis trigger
use_mcp_tool livetrack-mcp trigger_analysis

# Stop (or let it auto-stop)
use_mcp_tool livetrack-mcp stop_tracking

Project Structure

livetrack_mcp/
├── Dockerfile
├── pyproject.toml
├── README.md
└── src/livetrack_mcp/
    ├── __init__.py
    ├── __main__.py
    ├── otel.py        # OpenTelemetry setup
    ├── poller.py      # Garmin LiveTrack URL parsing + HTTP fetch
    ├── store.py       # SQLite time-series (sqlite3 + asyncio.to_thread)
    ├── tracker.py     # Scheduling (asyncio.create_task) + race-end detection
    ├── analyzer.py    # Prompt builder + claude-runner caller
    └── server.py      # FastMCP tools + /control + /health

Reviews

No reviews yet

Sign in to write a review