chore: project scaffolding and design docs

This commit is contained in:
Christophe Vila
2026-05-17 19:37:05 +02:00
commit 1b1e90f9d7
5 changed files with 1153 additions and 0 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
GARMIN_EMAIL=your@email.com
GARMIN_PASSWORD=yourpassword

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.env
.garth/
.venv/
__pycache__/
*.pyc
.idea/
dist/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
# MCP Garmin Server — Design Spec
**Date:** 2026-05-17
**Status:** Approved
---
## Overview
A local MCP server that exposes Garmin Connect data to Claude Desktop via the `garminconnect` Python package. The server runs as a single-file FastMCP stdio server, authenticated via environment variables, targeting personal use with Claude Desktop as the sole client.
---
## Architecture
**Approach:** Single-file FastMCP (Option A)
```
mcp-garmin/
├── server.py # FastMCP server — all tools and startup logic
├── pyproject.toml # Dependencies: mcp[cli], garminconnect
└── .env # GARMIN_EMAIL, GARMIN_PASSWORD (not committed)
```
`server.py` initializes a `FastMCP("garmin")` instance, registers all tools, and serves via stdio. A module-level `Garmin` client instance is shared across all tool calls — no re-authentication per call.
**Claude Desktop config** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
"mcpServers": {
"garmin": {
"command": "python",
"args": ["/path/to/mcp-garmin/server.py"],
"env": {
"GARMIN_EMAIL": "your@email.com",
"GARMIN_PASSWORD": "yourpassword"
}
}
}
}
```
---
## Tools
All tools return JSON-serializable dicts. Dates are accepted as `YYYY-MM-DD` strings and default to today when omitted.
### Authentication
| Tool | Parameters | Description |
|------|-----------|-------------|
| `authenticate()` | — | Initiates login from env vars. Returns `"Authenticated"` on success, or the MFA prompt (URL or code instruction) if Garmin requires MFA. |
| `complete_mfa(code)` | `code: str` | Accepts the MFA code and completes login, caching the session in `~/.garth`. |
### Activities
| Tool | Parameters | Description |
|------|-----------|-------------|
| `get_activities(start_date, end_date, limit)` | `start_date: str`, `end_date: str`, `limit: int = 20` | List of activities with type, distance, duration, average HR. |
| `get_activity_details(activity_id)` | `activity_id: str` | Full detail for a single activity: splits, laps, all metrics. |
### Health Stats
| Tool | Parameters | Description |
|------|-----------|-------------|
| `get_sleep(date)` | `date: str = today` | Sleep score, stages (deep/light/REM/awake), total duration. |
| `get_heart_rate(date)` | `date: str = today` | Resting HR and daily HR timeline. |
| `get_stress(date)` | `date: str = today` | Stress level timeline throughout the day. |
| `get_body_battery(date)` | `date: str = today` | Body battery charge/drain values throughout the day. |
| `get_hrv(date)` | `date: str = today` | HRV status and nightly average. |
| `get_spo2(date)` | `date: str = today` | Blood oxygen readings. |
### Steps & Daily Metrics
| Tool | Parameters | Description |
|------|-----------|-------------|
| `get_steps(date)` | `date: str = today` | Step count, daily goal, and distance. |
| `get_daily_stats(date)` | `date: str = today` | Full daily summary: calories, floors, intensity minutes, active time. |
### Gear & Devices
| Tool | Parameters | Description |
|------|-----------|-------------|
| `get_devices()` | — | List of paired Garmin devices with model and firmware version. |
| `get_gear()` | — | Gear items (shoes, bikes) with mileage/usage stats. |
### Profile
| Tool | Parameters | Description |
|------|-----------|-------------|
| `get_user_profile()` | — | Display name, age, weight, HR zones. |
---
## Authentication & Session Management
- `garminconnect` uses `garth` under the hood, which caches OAuth tokens in `~/.garth` after first login.
- **Startup:** The server attempts a silent login using cached `~/.garth` tokens. If valid, all tools are ready immediately. If no cache exists, tools return `"Not authenticated. Ask Claude to call authenticate() first."` until auth is completed.
- **First-time setup:** The user calls `authenticate()` from Claude Desktop chat. If Garmin requires MFA, the prompt (URL or email code instruction) is returned as a string visible in the chat. The user then calls `complete_mfa(code)` with the received code. On success, garth caches the session — subsequent server restarts skip MFA entirely.
- **MFA URL handling:** If garth surfaces a URL as part of the MFA flow, it is returned verbatim in the `authenticate()` response so Claude Desktop renders it as a clickable link.
---
## Error Handling
| Scenario | Behavior |
|----------|----------|
| Missing `GARMIN_EMAIL` or `GARMIN_PASSWORD` env vars | Fail at startup with a clear message; do not start server |
| No cached session and no auth attempted | Tools return `"Not authenticated. Call authenticate() first."` |
| `GarminConnectAuthenticationError` | Return readable error string to the LLM |
| No data for requested date | Return `"No <data type> found for <date>"` |
| Network error | Catch and return readable error string |
No custom retry logic — garth handles token refresh automatically.
---
## Dependencies
- `mcp[cli]` — FastMCP framework
- `garminconnect` — Garmin Connect API wrapper (uses `garth` for auth)
- Python 3.14 (existing venv)
---
## Out of Scope
- Write operations (creating workouts, updating data)
- Multi-user support
- Hosting / remote deployment
- GPS track/polyline data (raw GPX export)

13
pyproject.toml Normal file
View File

@@ -0,0 +1,13 @@
[project]
name = "mcp-garmin"
version = "0.1.0"
description = "MCP server for Garmin Connect"
requires-python = ">=3.11"
dependencies = [
"mcp[cli]",
"garminconnect",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"