fitness-mcp
# fitness-mcp
A personal, remote MCP server for fitness data:
- Garmin Connect activity data via [`garminconnect`](https://pypi.org/project/garminconnect/)
- Hevy workout data via the Hevy API
Designed to run on a Raspberry Pi behind nginx at something like:
```text
https://mcp.home.owenrumney.co.uk/mcp
```
## Tools
### Garmin
- `list_garmin_activities`
- `list_garmin_activities_by_date`
- `get_garmin_last_activity`
- `get_garmin_activity`
- `get_garmin_activity_details`
- `get_garmin_activity_splits`
- `get_garmin_activity_weather`
- `get_garmin_activity_types`
### Hevy
- `list_hevy_workouts`
- `get_hevy_workout`
- `list_hevy_routines`
- `list_hevy_routine_folders`
- `list_hevy_exercise_templates`
## Local setup
Requires Python 3.12+.
```bash
uv sync
cp .env.example .env
```
Edit `.env`, then run the Garmin login helper once if your Garmin account needs MFA:
```bash
set -a
source .env
set +a
uv run fitness-mcp-garmin-login
```
Run the server locally over Streamable HTTP:
```bash
uv run fitness-mcp --transport streamable-http --host 0.0.0.0 --port 8000
```
Health check:
```bash
curl http://127.0.0.1:8000/health
```
MCP endpoint:
```text
http://127.0.0.1:8000/mcp
```
If `MCP_AUTH_TOKEN` is set, callers must send:
```http
Authorization: Bearer <MCP_AUTH_TOKEN>
```
## Docker on Raspberry Pi
```bash
cp .env.example .env
mkdir -p data
# edit .env
docker compose build
docker compose run --rm fitness-mcp uv run fitness-mcp-garmin-login
docker compose up -d
```
The compose file publishes only to localhost:
```text
127.0.0.1:8000 -> container:8000
```
Put nginx in front of it and proxy:
```text
https://mcp.home.owenrumney.co.uk/mcp -> http://127.0.0.1:8000/mcp
```
See `deploy/nginx.conf` for an example site config.
## Environment
```env
MCP_AUTH_TOKEN=replace-with-a-long-random-value
GARMIN_EMAIL=you@example.com
GARMIN_PASSWORD=replace-me
GARMIN_TOKEN_STORE=/data/garmin
HEVY_API_KEY=replace-me
HEVY_BASE_URL=https://api.hevyapp.com/v1
```
## Notes
- Garmin Connect uses unofficial/private APIs through `garminconnect`, so MFA friction, rate limits, or upstream breakage are possible.
- The server currently exposes read-only tools only.
- If ChatGPT's MCP connector requires OAuth rather than bearer auth, this server will need an OAuth wrapper in front of the same `/mcp` backend.
TDQS
Scored across 13 tools
Tools are clearly separated by provider (Garmin vs. Hevy) and within each provider, each tool has a distinct purpose (listing, getting details, splits, weather, etc.). No overlapping functionality.
All tools follow a consistent 'verb_provider_noun' pattern in snake_case. Verbs like list, get are used predictably. Only minor deviation is 'get_garmin_last_activity' which still fits the pattern.
13 tools is well-scoped for a fitness server covering two platforms. It covers the essential read operations without being overwhelming or sparse.
The tool surface provides comprehensive read access for both Garmin and Hevy. Missing write operations (create/update/delete) are likely intentional given the server's focus on data retrieval. Minor gap: no tool to list Hevy routine folder contents or search across platforms.