Skip to main content

Fartlek — a coach's morning report from your Garmin data, for any LLM via MCP

Project description

Fartlek

A coach's morning report from your Garmin data, for any LLM via MCP.

Every other Garmin MCP server hands the LLM a filing cabinet of raw JSON — one night of sleep is ~52K tokens, one activity stream ~155K. The model can't read it, so it skims and improvises. Fartlek does the synthesis server-side: computed sports-science metrics (CTL/ATL/TSB, ACWR, monotony, calibrated training load), personal baselines with significance floors, safety alerts — delivered as compact, verdict-first reports the model can actually reason about.

The token contract (v0.1): calling every tool in the catalog once, at default arguments, costs under 9K tokens — a sixth of one raw Garmin sleep payload. Excluding the garmin_raw escape hatch, the whole synthesis surface sums to under 4K. Hard caps are enforced per response by the renderer, with disclosed truncation.

Status: v0.1 (Phase 1). 8 synthesis tools; the trend suite (weekly review, multi-week load, fitness/race outlook, recovery audit) ships with v0.2. Design: docs/DESIGN.md · plan: ROADMAP.md.

The tools

Tool What it answers Cap
garmin_brief "How am I today — can I train hard?" Fused GREEN/AMBER/RED verdict vs your own baselines 600
garmin_activities Browse the log, get activity IDs 1,300
garmin_activity One session in depth: reps, fade, comparison to your most similar past session 1,000–4,000
garmin_athlete Reference card: zones, PRs, goal, data coverage 600
garmin_set_profile Tell it your goal race / phase / availability (local only) 200
garmin_log Log RPE, wellness, illness/injury — the athlete outranks the sensors 120
garmin_sync Force refresh / deepen history backfill 150
garmin_raw Bounded, compacted escape hatch to named raw sources 5,000

First call on a fresh install runs the cold start automatically (~30 API calls, ≈1 minute): 180 days of history, warm CTL/ATL from day 0, then background sleep/HRV backfill.

Quickstart

Install with either:

# uv (recommended) — runs without cloning
uvx fartlek-mcp

# or pipx
pipx install fartlek-mcp

Or clone and run from source (requires Python ≥ 3.12 and uv):

git clone https://github.com/matisdsp/fartlek && cd fartlek
uv sync

# One-time Garmin login (email/password + MFA if enabled).
# Credentials are never stored; OAuth tokens go to ~/.fartlek/tokens/.
uv run fartlek auth

# Optional but recommended: warm the local store now instead of on first use
uv run fartlek sync --nights 60

uv run fartlek doctor   # check everything is healthy

Then point your MCP client at the server.

Any MCP-compatible client works — the server speaks standard JSON-RPC over stdio, so it is client-agnostic. The snippets below are just the per-client config formats; Claude Desktop, Claude Code, Cursor, Continue, Cline, Windsurf, Zed, VS Code (Copilot Chat), and Gemini CLI all work. The universal invocation is uvx fartlek-mcp.

Claude Code — from this directory, .mcp.json is picked up automatically. From anywhere else:

claude mcp add fartlek -- uvx fartlek-mcp

Claude Desktopclaude_desktop_config.json:

{
  "mcpServers": {
    "fartlek": {
      "command": "uvx",
      "args": ["fartlek-mcp"]
    }
  }
}

Cursor.cursor/mcp.json, same command/args block as above.

Continue / Cline / Windsurf / Zed — same pattern: wherever the client keeps its MCP server list, add a fartlek entry with command: "uvx", args: ["fartlek-mcp"]. Most editors adopt the Claude Desktop format verbatim.

Any other stdio MCP client — invoke the server binary directly:

fartlek-mcp          # speaks JSON-RPC over stdin/stdout

Ask things like "can I go hard today?", "analyze my last run", "how did I sleep this week?" — and tell it how sessions felt: your reported RPE and illness notes gate the readiness verdict.

Docker

Build and run locally:

docker build -t fartlek-mcp .
# Tokens and store are persisted in ./fartlek-data on the host
mkdir -p fartlek-data
docker run -i --rm -v "$PWD/fartlek-data:/data" fartlek-mcp

For fartlek auth, run it interactively once to populate the volume, then use the image as the MCP server:

docker run -it --rm -v "$PWD/fartlek-data:/data" --entrypoint fartlek fartlek-mcp auth

CLI

Command What it does
fartlek auth one-time Garmin Connect login (MFA supported), tokens stored locally
fartlek sync [--nights N] manual sync (tier 0+1, optional N-night sleep/HRV backfill)
fartlek doctor check tokens, Garmin connectivity, local store health
fartlek accounts list local accounts
fartlek export [dir] export the store (consistent SQLite snapshot + CSV per table)
fartlek reset wipe all local tokens and data (asks confirmation)

Environment: GARMINTOKENS overrides the token location, FARTLEK_HOME the data directory (default ~/.fartlek).

Releasing to PyPI (maintainers)

Releases are published via trusted publishing (OIDC) — no API tokens anywhere.

  1. On pypi.org → Account settings → Publishing → add a GitHub publisher:
    • PyPI project name: fartlek-mcp · owner: matisdsp · repo: fartlek
    • Workflow: release.yml · environment: pypi
  2. Bump version in pyproject.toml, commit, then tag and push:
git tag v0.1.0 && git push origin v0.1.0

The release workflow builds, runs tests, and uploads to PyPI. A published version can't be overwritten — to fix a mistake, bump to the next patch (0.1.1).

Privacy

Local-first: stdio transport, your credentials and health data never leave your machine. The server only talks to Garmin's API with your own tokens, sequentially and rate-limited. fartlek export gives you everything; fartlek reset removes everything.

License & trademark

MIT. Fartlek is an independent open-source project, not affiliated with, endorsed by, or sponsored by Garmin Ltd. "Garmin" is used only to describe compatibility with Garmin Connect data.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fartlek_mcp-0.1.0.tar.gz (207.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fartlek_mcp-0.1.0-py3-none-any.whl (99.6 kB view details)

Uploaded Python 3

File details

Details for the file fartlek_mcp-0.1.0.tar.gz.

File metadata

  • Download URL: fartlek_mcp-0.1.0.tar.gz
  • Upload date:
  • Size: 207.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fartlek_mcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e911399d128e2a2f04a037cd7e75db9883b9af2394f19e39bc9ed936a30e527a
MD5 4d62055d016e708d70f0377039ba3e55
BLAKE2b-256 f3b568be8cf9c6e59883faac3a150c6305c12c9d94f84fb83af53a33d82252da

See more details on using hashes here.

File details

Details for the file fartlek_mcp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fartlek_mcp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 99.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.31 {"installer":{"name":"uv","version":"0.11.31","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fartlek_mcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ffbf58adf5154ad3fbc0b9bbf849638b625f11c05273821f02dd829ad8c5868c
MD5 f8596641d550fc54676290573208d0d9
BLAKE2b-256 802073e5adc9babb2a75ee3a56475e983ab68e330dfda562f482448203dfe303

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page