{"id":"2fb034a9-6115-42f2-9086-f44dcfe267ea","slug":"rifusaki-catence","kind":"mcp","name":"catence","summary":"Local MCP server for normalized Garmin, Intervals.icu, and Strava data.","owner":"rifusaki","repoUrl":"https://github.com/rifusaki/catence","license":"AGPL-3.0","licenseTier":"strong-copyleft","trust":"community","stars":0,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-08-06T17:52:53.000Z","addedAt":"2026-10-14T11:11:40.645Z","logoUrl":"https://avatars.githubusercontent.com/u/46846373?u=5fb3c71425a75e1dc9d3dab46ca8bfaaf378c27d&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["cloud-infra","data-analytics"],"tags":[],"runtime":"node","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-17T20:45:56.024Z","sample":false,"url":"https://conduits.dev/tools/rifusaki-catence","language":"TypeScript","transports":["stdio"],"install":{"npm":"catence"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# Catence\n\nLocal MCP server for primary Garmin data, with added Intervals.icu metrics and Strava enrichment.\n\n![Catence](docs/assets/image.png)\n\n## ... what?\n\nAs a context, I'm both an endurance athlete and a data junkie. I already got a coach for all the serious stuff, but I wanted to hook up some—any—LLM to the insane amount of data collected from Garmin, derived on Intervals.icu (I don't pay for TrainingPeaks) and comparable efforts on Strava segments. Naturally, the first answer was a local MCP server for my current tooling.\n\n## What is included\n\n- Garmin, Intervals.icu, and Strava ingestion with source-aware normalization.\n- Read-only MCP tools, plus explicitly named, lock-guarded write tools for Strava hydration and detached syncs.\n- One shared Catence agent can serve several isolated athlete stores. Every personal-data MCP call names an `athleteId`; Catence never silently combines athletes.\n- A password-protected Chainlit Console with an authenticated dashboard, a data-sync button with live progress, and in-app model management.\n- A generated demo catalog for safe evaluation in Glama, desktop MCP clients, or local development.\n\nCatence helps explore recovery, training load, trends, activity detail, swimming/cycling/running progress, segments, gear, and data quality. It reports the evidence and coverage it can see; it does not diagnose or prescribe training.\n\n## Requirements\n\n- Node.js 22+\n- Python 3.12+ and [uv](https://docs.astral.sh/uv/) for Garmin and Strava provider workers\n- Provider credentials for the athletes you choose to sync\n\n## Quick start\n\nInstall Catence and create the first athlete. The default catalog home is `~/.catence`; set `CATENCE_HOME` or pass `--home` to use a different location.\n\nEvery `catence-data` command requires `--athlete <id>`; there is no default athlete. Pair it with `--home <dir>` to select the catalog when not using the default home.\n\n```sh\nnpm install --global catence@latest          # or catence@beta\ncatence-data setup --athlete alex --label \"Alex\"\n```\n\nStore each provider value through stdin so it does not enter shell history. Values are written to the selected athlete's owner-only local secret file.\n\n```sh\nprintf %s 'alex@example.com' | catence-data --athlete alex secret set --provider garmin --field email --value-stdin\nprintf %s 'your-garmin-password' | catence-data --athlete alex secret set --provider garmin --field password --value-stdin\nprintf %s 'intervals-api-key' | catence-data --athlete alex secret set --provider intervals --field apiKey --value-stdin\nprintf %s '12345' | catence-data --athlete alex secret set --provider intervals --field athleteId --value-stdin\n```\n\nSync and create retrieval context:\n\n```sh\ncatence-data --athlete alex sync --provider all\ncatence-data --athlete alex build-retrieval-index\n```\n\nStart the stdio MCP server:\n\n```sh\ncatence\n```\n\nAn agent first calls `list_athletes`, then includes `athleteId` with every data tool. This is intentional: a shared agent may access the stores you configured, but no tool implicitly aggregates or crosses between athletes.\n\n### Add another athlete\n\n```sh\ncatence-data athlete add --id sam --label \"Sam\"\nprintf %s 'sam@example.com' | catence-data --athlete sam secret set --provider garmin --field email --value-stdin\nprintf %s 'sam-password' | catence-data --athlete sam secret set --provider garmin --field password --value-stdin\ncatence-data --athlete sam sync --provider garmin\ncatence-data athlete list\n```\n\nGarmin, Intervals, and Strava client credentials are isolated per athlete. Strava OAuth tokens remain in that athlete's own store. The old 0.1 single-store directory cannot be migrated safely: create a fresh home and re-sync each athlete.\n\n### Common operations\n\n```sh\ncatence-data --athlete alex status\ncatence-data --athlete alex sync --provider intervals\ncatence-data --athlete alex sync --provider garmin --from 2025-07-29\ncatence-data --athlete alex backfill --provider garmin --from 2020-01-01 --refresh\ncatence-data --athlete alex retry --run <run-id>\ncatence-data --athlete alex progress --watch\ncatence-data --athlete alex auth strava --callback\ncatence-data --athlete alex disconnect strava\ncatence-data update --check\ncatence-data update\n```\n\n## Documentation\n\n| Document | Covers |\n| --- | --- |\n| [Deployment: local MCP](docs/deployment/local-mcp.md) | Install, catalog, secrets, sync, background progress, stdio/HTTP MCP, demo, common operations |\n| [Deployment: local Console](docs/deployment/local-console.md) | Local web chat setup, model config, doctor, updates, troubleshooting |\n| [Deployment: Docker](docs/deployment/docker.md) | One-container stack: deploy script, `.env`, sync.sh, MCP exposure (SSH/Tailscale), Cloudflare Tunnel |\n| [Configuration](docs/configuration.md) | Complete `config.json` schema: rate limits, Strava budget, Console profiles, environment variables |\n| [LLM providers](docs/llm-providers.md) | Model providers (OpenAI, Anthropic, OpenAI-compatible/Azure, Opencode Go/Zen), reasoning effort |\n| [Architecture](docs/architecture.md) | Design rationale, layers, data flow, serving |\n| [MCP activity retrieval](docs/mcp-activity-retrieval.md) | Implementation contract for answering questions, current tools, planned endpoints |\n| [Distribution](docs/distribution.md) | Release artifacts: npm, PyPI, APM, MCPB |\n\n## MCP clients and HTTP\n\nFor a packaged installation, point a client at `catence`:\n\n```sh\ncodex mcp add catence -- catence\nclaude mcp add --transport stdio catence -- catence\n```\n\nFor a source checkout:\n\n```sh\ncodex mcp add catence -- npm --prefix /absolute/path/to/catence run mcp\n```\n\nOptional local Streamable HTTP MCP and dashboard APIs:\n\n```sh\ncatence serve --host 127.0.0.1 --port 8787\n```\n\n`GET /api/v1/athletes` returns IDs and labels only. `GET /api/v1/dashboard` requires `athleteId`, for example `http://127.0.0.1:8787/api/v1/dashboard?athleteId=alex&days=28`. Browser origins must be listed with `--allow-origin`; the packaged Console instead proxies the dashboard through its authenticated same-origin route. The MCP server has no authentication of its own.\n\n## Configuration\n\n[`config.example.json`](config.example.json) documents rate limits, Strava budgets, and Console model profiles. Keep per-athlete provider values in `catence-data secret set`, not in `.env` or `config.json`. The full `config.json` schema —including per-model reasoning effort— is documented in [`docs/configuration.md`](docs/configuration.md) and [`docs/llm-providers.md`](docs/llm-providers.md).\n\nFor source development:\n\n```sh\nnpm ci\nnpm run check\nnpm test\nUV_CACHE_DIR=$PWD/.cache/uv uv run --project console --group dev python -m pytest console/tests -q\n```\n\n## Data and caveats\n\nThe current available sources respond to my own used platforms. I used some wrappers:\n\n- [cyberjunky/python-garminconnect](https://github.com/cyberjunky/python-garminconnect)\n- [paladini/node-intervals-icu](https://github.com/paladini/node-intervals-icu)\n- [stravalib/stravalib](https://github.com/stravalib/stravalib)\n\nStrava is only used for segments and gear.\n\n### Caveats\n\nThere are two caveats to the data fetching, both coming from the fact that this was created to be used by a single person. Accessing the full Garmin API requires applying via a company—which I don't have—and getting Strava data for multiple users is another headache I don't want to get into.\n\n- I don't use the official Garmin Connect API. The library uses email/pwd.\n- Connecting Strava requires the user to be a Strava Premium subscriber and create an [API application](https://stravalib.readthedocs.io/en/latest/get-started/authenticate-with-strava.html). The rotating token is stored locally in\n`<data-dir>/secrets/strava.json`.\n"}