pypi fartlek-mcpstdioApache-2.0updated 21d ago
A coach's morning report from your Garmin data, for any LLM via MCP.
What can you do with Fartlek?
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ยท contributors:docs/HANDOFF.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_gear |
Shoe/bike mileage vs your Garmin-set retirement limits, and what you're actually rotating | 500 |
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 Desktop โ claude_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.
- On pypi.org โ Account settings โ Publishing โ add a GitHub publisher:
- PyPI project name:
fartlek-mcpยท owner:matisdspยท repo:fartlek - Workflow:
release.ymlยท environment:pypi
- PyPI project name:
- Bump
versioninpyproject.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.
How Fartlek reaches your data โ read this before connecting an account
Garmin has an official developer programme, and Fartlek is not part of it. Like every other open-source Garmin client, it signs in with your own credentials and reads the same endpoints the Garmin Connect apps use. Those endpoints are not published for third-party use, and Garmin's Terms of Use list, among examples of prohibited conduct, "using any process, whether automated or manual, that accesses, copies, or scrapes content from the Site through any means not purposely made available through the Site."
What that means in practice:
- Your account is yours to risk. Garmin can rate-limit, block, or suspend accounts for automated access. Fartlek is deliberately polite โ sequential calls, backoff on 429, and Garmin is contacted only by the sync process, never per question โ but politeness is not permission.
- It can break without warning. Garmin changed its login in March 2026 and broke every third-party client for weeks. This will happen again.
- Read-only, by decision. Fartlek never writes to Garmin: no workouts pushed to your watch, no training plans, no edits to your activities. The two tools that write (
garmin_log,garmin_set_profile) write to the local SQLite store and nothing else. Pushing structured workouts was specified and then dropped โ seedocs/DESIGN.mdยง2.4. Garmin's Training API is the sanctioned route for that, and it requires a cloud-to-cloud integration, which would mean your data leaving your machine. - Nothing is redistributed. Your data stays on your disk. Fartlek's own responses are derived from it and are shown only to you and the LLM client you chose.
If that trade-off is not one you want to make, do not connect an account. This is stated here rather than buried, because it is the kind of thing you should decide before installing, not discover afterwards.
License & trademark
Apache 2.0. 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.
Install
Add Fartlek to your client. Pick the one you use.
claude mcp add fartlek-mcp -- uvx fartlek-mcpcodex mcp add fartlek-mcp -- uvx fartlek-mcpamp mcp add fartlek-mcp -- uvx fartlek-mcp{
"mcpServers": {
"fartlek-mcp": {
"command": "uvx",
"args": [
"fartlek-mcp"
]
}
}
}Add to `claude_desktop_config.json`, then restart Claude Desktop.
{
"mcpServers": {
"fartlek-mcp": {
"command": "uvx",
"args": [
"fartlek-mcp"
]
}
}
}Add to `~/.cursor/mcp.json`, or `.cursor/mcp.json` for a single project.
code --add-mcp '{"name":"fartlek-mcp","command":"uvx","args":["fartlek-mcp"]}'Or add the block manually to `.vscode/mcp.json` under `servers`.
{
"mcpServers": {
"fartlek-mcp": {
"command": "uvx",
"args": [
"fartlek-mcp"
]
}
}
}Add to `~/.codeium/windsurf/mcp_config.json`.
{
"mcpServers": {
"fartlek-mcp": {
"command": "uvx",
"args": [
"fartlek-mcp"
]
}
}
}Add to `cline_mcp_settings.json` via the MCP Servers panel.
{
"mcpServers": {
"fartlek-mcp": {
"command": "uvx",
"args": [
"fartlek-mcp"
]
}
}
}Add to `~/.gemini/settings.json`.
{
"mcpServers": {
"fartlek-mcp": {
"type": "local",
"command": "uvx",
"args": [
"fartlek-mcp"
],
"tools": [
"*"
]
}
}
}Add to `~/.copilot/mcp-config.json`, or run `/mcp add` inside the CLI.
{
"context_servers": {
"fartlek-mcp": {
"command": {
"path": "uvx",
"args": [
"fartlek-mcp"
]
}
}
}
}Add to your Zed `settings.json`.
uvx fartlek-mcpRun `goose configure`, choose **Add Extension โ Command-line Extension**, and paste this command.
Score
39 / 100
Incomplete
- Documentation25/25
- Maintenance25/25
- Trust13/20
- Capability0/15
- Install experience12/15
- Documents what it does and how to connect
- Has a resolvable package or endpoint
- Exposes at least one tool, prompt or resource
- README has substantive content
- Includes a code example
- Documents its configuration
- Mentions credentials or security posture
- Last commit 14 days ago
- Has a release history
- Repository is not archived
- Licensed Apache-2.0
- Namespace verified in the official MCP registry
- Claimed by its owner
- Published under an organisation
- 0 tool(s) documented
- Provides prompt templates
- Provides resources
- 12 documented install method(s)
- Published to a package registry
- Offers a hosted endpoint โ no local install
Version history
| Versions | Published |
|---|---|
| 0.2.2Latest | Jul 24, 2026 |
| 0.2.1 | Jul 24, 2026 |
| 0.2.0 | Jul 23, 2026 |
| 0.1.1 | Jul 22, 2026 |