Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

FitMCP

БесплатноНе проверен

A multi-platform fitness MCP server that syncs data from Garmin, Strava, Google Fit, and Suunto into a local DuckDB database and provides analytics tools via MC

GitHubEmbed

Описание

A multi-platform fitness MCP server that syncs data from Garmin, Strava, Google Fit, and Suunto into a local DuckDB database and provides analytics tools via MCP.

README

A generic, multi-platform fitness MCP server. It pulls data from Garmin Connect, Strava, Google Fit, and Suunto, stores everything locally in a single DuckDB file, and exposes analytics as MCP tools over stdio — usable from Claude Desktop, Cursor, Windsurf, VS Code, or any MCP client.

  • Zero running infrastructure — sync is manual, the server runs on demand.
  • Fully extensible — adding a platform = implement one abstract class and register it. The tools layer never changes.
  • Read-only by design — MCP tools only read; fitness_query enforces SELECT-only.

Architecture

server.py        MCP entry point (stdio)         providers/base.py     abstract interface + dataclasses
sync.py          CLI + shared sync engine        providers/garmin.py   Garmin Connect (garth)
db/database.py   DuckDB connection + upserts      providers/strava.py   Strava (OAuth2 + httpx)
db/schema.sql    table definitions               tools/*.py            MCP tools (activities, health, …)

Data flow: providers fetch normalized records → db upserts them into DuckDB → tools query DuckDB and return a uniform JSON envelope.

Every tool returns:

{ "success": true, "data": [...], "error": null, "meta": { "count": 42 } }

Setup

Automated (macOS / Linux)

./setup.sh                  # venv + deps + .env scaffold, then prints next steps

Then edit .env, and optionally let the script do the rest:

./setup.sh --login          # interactive Garmin login (password never stored)
./setup.sh --sync           # initial sync of all platforms
./setup.sh --claude         # install the Claude Desktop MCP config
./setup.sh --dev            # install dev deps and run the test suite
# flags combine: ./setup.sh --dev --login --sync --claude

A Makefile wraps the common actions — make help, make setup, make login, make sync, make serve, make test, make claude-install.

Automated (Windows / PowerShell)

.\setup.ps1                  # venv + deps + .env scaffold (locked to your user), then next steps
.\setup.ps1 -Dev -Login -Sync -Claude   # switches combine, same meaning as above

If PowerShell blocks the script, run it once as:

powershell -ExecutionPolicy Bypass -File .\setup.ps1

Then use the venv directly for the recurring commands:

.\.venv\Scripts\python.exe login.py
.\.venv\Scripts\python.exe sync.py --platform garmin --full-history
.\.venv\Scripts\python.exe scripts\claude_config.py --write   # Claude Desktop config

On Windows the setup script restricts .env to your user account via icacls (the POSIX chmod warning is a no-op there); the garth token cache lives under %USERPROFILE%\.garth, which is already user-scoped by default.

The Claude Desktop entry can be generated or installed on its own:

python scripts/claude_config.py            # print the JSON snippet
python scripts/claude_config.py --write     # merge it into your Claude config (with backup)

Manual

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env        # then fill in credentials

Credentials (.env)

DUCKDB_PATH=./fitness.duckdb

# Garmin Connect (via garth)
[email protected]
GARMIN_PASSWORD=yourpassword
GARTH_HOME=~/.garth          # cached session token lives here

# Strava
STRAVA_CLIENT_ID=...
STRAVA_CLIENT_SECRET=...
STRAVA_REFRESH_TOKEN=...

.env and the DuckDB file are gitignored.

Strava: getting a refresh token (one time)

  1. Create an API application at https://www.strava.com/settings/api. Note the Client ID and Client Secret.

  2. Authorize your own account, requesting the activity:read_all scope. Visit (replace CLIENT_ID):

    https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http://localhost&approval_prompt=force&scope=activity:read_all
    

    After approving, the browser redirects to http://localhost/?...&code=AUTH_CODE&.... Copy AUTH_CODE from the URL.

  3. Exchange the code for tokens:

    curl -X POST https://www.strava.com/oauth/token \
      -d client_id=CLIENT_ID -d client_secret=CLIENT_SECRET \
      -d code=AUTH_CODE -d grant_type=authorization_code
    

    Put the refresh_token from the response into STRAVA_REFRESH_TOKEN. The server refreshes the short-lived access token automatically on every sync.

Garmin uses garth. Preferred login — run it once interactively so your password is never written to disk:

python login.py        # prompts for email/password (+ MFA); caches a token

After that you can leave GARMIN_PASSWORD out of .env; syncs reuse the cached session token. (Setting the password in .env still works as a fallback for non-interactive/headless use.)

Security of credentials & sessions

  • Nothing secret is committed.env, the DuckDB file, and the garth token cache (.garth/, *.token) are all gitignored.
  • Password stays off diskpython login.py reads it via getpass and only persists the resulting session token. Keep GARMIN_PASSWORD blank.
  • Owner-only token cache — after every Garmin login the GARTH_HOME directory and its token files are chmod'd to 0700/0600 (POSIX).
  • Exposure warningsync.py and server.py warn on startup if your .env is group/world-readable, with the exact chmod 600 fix.
  • Treat session tokens like a password — a cached garth token grants access to your Garmin account. If a machine is compromised, delete GARTH_HOME and re-run login.py. OAuth refresh tokens (Strava/Google/Suunto) are likewise sensitive; revoke them in each platform's app settings if leaked.

Syncing data

python sync.py --platform all                          # last 30 days, all platforms
python sync.py --platform garmin                       # garmin | strava | google_fit | suunto
python sync.py --platform garmin --from 2025-01-01 --to 2025-06-30
python sync.py --platform garmin --full-history        # everything from 2010 (first run)

Supported platforms: garmin, strava, google_fit, suunto. Garmin is the richest source (activities, sleep, HRV, body battery, stress); Strava and Suunto provide activities; Google Fit provides activities, sleep, and weight.

After all platforms sync, duplicate workouts (same day + sport, duration and distance within 5%) are de-duplicated: the Garmin record is kept (richer metrics) and the Strava id is merged into its raw_json.

Recurring (automated) sync

After a one-time python login.py, the Garmin token auto-refreshes, so scheduled syncs run unattended for months (re-run login.py only when the long-lived token finally expires). The runner logs each run to logs/sync.log and exits non-zero if any platform errored:

python scripts/scheduled_sync.py --platform all

Windows (Task Scheduler):

.\scripts\register_sync_task.ps1                  # daily 07:00, all platforms
.\scripts\register_sync_task.ps1 -Time 06:30 -Platform garmin
.\scripts\register_sync_task.ps1 -Daily2x         # 07:00 and 19:00
.\scripts\register_sync_task.ps1 -Unregister      # remove it

The task runs as your user via S4U (no stored password, runs whether or not you're logged in) and catches up missed runs. Trigger a test run with Get-ScheduledTask fitnessmcp-sync | Start-ScheduledTask.

macOS/Linux (cron): point cron at the same runner —

0 7 * * *  cd /path/to/fitMCP && .venv/bin/python scripts/scheduled_sync.py --platform all

Running the MCP server

python server.py        # serves over stdio

Claude Desktop config

~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "fitness": {
      "command": "/absolute/path/to/fitness-mcp/.venv/bin/python",
      "args": ["/absolute/path/to/fitness-mcp/server.py"],
      "env": { "PYTHONPATH": "/absolute/path/to/fitness-mcp" }
    }
  }
}

MCP tools

Tool Purpose
fitness_sync(platform) Sync garmin/strava/all into DuckDB
fitness_sync_status() Last sync time + record counts per platform
fitness_get_database_stats() Row counts, date ranges, file size
fitness_get_activities(date_from, date_to, platform, sport_type, limit, offset) Paginated activity list
fitness_get_activity_detail(activity_id) Full detail incl. raw payload
fitness_get_personal_bests(sport_type) Longest, fastest, max HR, most elevation
fitness_get_sleep(date_from, date_to, platform) Sleep records
fitness_get_hrv(date_from, date_to, platform) HRV + body battery
fitness_get_body_battery(date_from, date_to) Body battery trend
fitness_get_recovery_status(date) One-day recovery snapshot
fitness_get_training_load(weeks, platform) Weekly load trend
fitness_get_vo2max_trend(months) VO2max estimates over time
fitness_get_weekly_summary(week_offset) Week totals + sport split
fitness_get_sport_breakdown(date_from, date_to) Time/distance/count per sport
fitness_compare_platforms(metric, date_from, date_to) Same metric per platform
fitness_correlate(metric_a, metric_b, date_from, date_to) Pearson correlation + scatter
fitness_get_trends(metric, date_from, date_to, granularity) Time series by day/week/month
fitness_query(sql) Read-only SELECT against the database

Metrics for fitness_correlate / fitness_get_trends: sleep_score, sleep_duration, hrv, hrv_score, body_battery, training_load, distance, duration, avg_hr, stress, weight.

FitBrain — coaching layer

For a data-driven coaching experience, create a Claude Project named FitBrain and paste prompts/fitbrain_system_prompt.md into its custom instructions (edit the "Who I am"/"Goals" to match you). It makes Claude always check freshness and pull live data before answering, hunt for correlations, flag overtraining signals, and stay direct. See prompts/README.md for setup and example questions.

Tests

pip install -r requirements-dev.txt
PYTHONPATH=. pytest

The suite covers the DuckDB layer (upsert idempotency, dedup, sync log), provider payload parsing for all four platforms, the read-only SQL guard, and every MCP tool end-to-end against a seeded temp database — no network or credentials required. CI runs it on every push and pull request (.github/workflows/tests.yml).

Adding a new platform

  1. Create providers/newplatform.py extending FitnessProvider.
  2. Add credentials to .env.
  3. Register it in sync.py: PROVIDERS["newplatform"] = NewPlatformProvider.

Done — every tool includes it automatically when platform="all".

Project status

Implemented: foundation, all four providers (Garmin, Strava, Google Fit, Suunto), cross-platform dedup, the full tools layer (activities, health, training, analysis, sync), credential/session hardening, automated setup (macOS/Linux/Windows), scheduled recurring sync, the FitBrain coaching prompt, and a pytest suite with CI. Google Fit has no HRV and Suunto exposes only workouts via its public API; both still satisfy the common FitnessProvider interface, so the tools layer treats them uniformly.

What's left is live validation against real accounts (needs your credentials).

from github.com/gzarog/fitMCP

Установка FitMCP

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/gzarog/fitMCP

FAQ

FitMCP MCP бесплатный?

Да, FitMCP MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для FitMCP?

Нет, FitMCP работает без API-ключей и переменных окружения.

FitMCP — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить FitMCP в Claude Desktop, Claude Code или Cursor?

Открой FitMCP на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare FitMCP with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории data