garmin-mcp-server
by vnglst
README.md
# Garmin MCP Server
A Model Context Protocol (MCP) server that gives compatible AI clients access to Garmin Connect activity and health data stored in SQLite, plus tools for managing Garmin workouts.
## Setup for Claude Desktop
### 1. Install Dependencies
```bash
npm install
```
### 2. Configure Garmin Credentials
Create a `.env` file in the project root:
```bash
cp .env.example .env
```
Edit `.env` and add your Garmin Connect credentials:
```
GARMIN_USERNAME=your-email@example.com
GARMIN_PASSWORD=your-password
```
### 3. Download Your Garmin Data
Create the local data directory and download your activities:
```bash
mkdir -p data
npm run download
```
This downloads your activities and lap data from Garmin Connect to a local SQLite database at `data/garmin-data.db`.
### 4. Configure Claude Desktop
First, get the absolute path to this project:
```bash
pwd
```
This will output something like `/Users/yourusername/Code/garmin-mcp-server`.
**macOS:** Edit `~/Library/Application Support/Claude/claude_desktop_config.json`
**Linux:** Edit `~/.config/Claude/claude_desktop_config.json`
**Windows:** Run `cd` to get the path, then edit `%APPDATA%\Claude\claude_desktop_config.json`
Add this configuration (replace the path with your actual project path):
```json
{
"mcpServers": {
"garmin-mcp-server": {
"command": "npx",
"args": ["-y", "tsx", "/Users/yourusername/Code/garmin-mcp-server/src/index.ts"],
"cwd": "/Users/yourusername/Code/garmin-mcp-server"
}
}
}
```
Replace `/Users/yourusername/Code/garmin-mcp-server` with your actual path from the `pwd` command.
### 5. Restart Claude Desktop
After restarting, you can ask Claude:
- "Sync my latest Garmin activities"
- "Sync my daily resting heart rate"
- "Show me my 5 most recent runs"
- "What's my average pace this month?"
- "Analyze my running performance trends"
- "How many kilometers did I run this year?"
## Features
- **Local SQLite database** - Activity, lap, and daily heart-rate data for fast access
- **Incremental sync** - Update Garmin data through an MCP client or the command line
- **Read-only SQL tools** - Analyze training data with custom `SELECT` queries
- **Workout management** - Create, rename, schedule, unschedule, and delete Garmin workouts
- **Two transports** - Local stdio and self-hosted Streamable HTTP
- **Optional HTTP authentication** - Protect remote access with a bearer token
## Self-Hosting with Docker
Deploy your own instance using Docker for use with Claude, ChatGPT, or any MCP-compatible client.
### Quick Start with Docker Compose
1. Clone this repository:
```bash
git clone https://github.com/vnglst/garmin-mcp-server.git
cd garmin-mcp-server
```
2. Create a `.env` file and replace the example values:
```bash
cat > .env <<'EOF'
GARMIN_USERNAME=your-email@example.com
GARMIN_PASSWORD=your-password
API_KEY=your-secret-api-key
EOF
```
3. Create the initial database on the host. The production image does not include the download CLI, so the database must exist before the container starts:
```bash
npm ci
mkdir -p data
npm run download
```
4. Create the container, copy the database into its persistent volume, and start it:
```bash
docker compose create
docker compose cp ./data/garmin-data.db garmin-mcp-server:/app/data/garmin-data.db
docker compose up -d
```
The Compose configuration builds the image from the checked-out source. The server runs at `http://localhost:3000` with:
- MCP endpoint: `POST /mcp`
- Health check: `GET /health`
### Coolify Deployment
1. Create a new service from Git repository
2. Set the following environment variables:
- `GARMIN_USERNAME`: Your Garmin Connect email
- `GARMIN_PASSWORD`: Your Garmin Connect password
- `API_KEY`: Secret key for authentication
- `PORT`: 3000 (default)
3. Configure health check path: `/health`
4. Mount persistent storage at `/app/data` and seed it with `garmin-data.db`
5. Deploy
### Using the Pre-built Image
Initialize a named volume from a database created with `npm run download` before starting the image:
```bash
docker volume create garmin-data
docker run --rm \
-v "$PWD/data:/source:ro" \
-v garmin-data:/target \
alpine cp /source/garmin-data.db /target/garmin-data.db
```
```bash
docker run -d \
--name garmin-mcp-server \
-p 3000:3000 \
-e GARMIN_USERNAME=your-email@example.com \
-e GARMIN_PASSWORD=your-password \
-e API_KEY=your-secret-key \
-v garmin-data:/app/data \
ghcr.io/vnglst/garmin-mcp-server:latest
```
### MCP Client Configuration
Configure your MCP client to connect to the HTTP endpoint:
```json
{
"mcpServers": {
"garmin": {
"url": "https://your-server.example.com/mcp",
"transport": "http",
"headers": {
"Authorization": "Bearer your-api-key"
}
}
}
}
```
### Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `GARMIN_USERNAME` | For sync/workout tools | Garmin Connect email |
| `GARMIN_PASSWORD` | For sync/workout tools | Garmin Connect password |
| `API_KEY` | No | Bearer token required by HTTP clients; strongly recommended outside an isolated local environment |
| `PORT` | No | Enables HTTP mode on this port; defaults to `3000` in Docker |
| `HTTP_MODE` | No | Set to `true` to enable HTTP mode without setting `PORT` |
| `CORS_ORIGIN` | No | Comma-separated allowed browser origins; CORS headers are omitted by default |
| `MAX_SESSIONS` | No | Maximum active HTTP MCP sessions (default: `100`) |
| `MAX_QUERY_CHARS` | No | Maximum SQL query length (default: `50000`) |
## Available MCP Tools
The server exposes activity-data tools and Garmin Connect workout-management tools.
### 1. `get-schema`
Get the database schema to understand available data fields.
**Example usage in Claude:**
- "What data fields are available in my running database?"
- "Show me the database schema"
### 2. `run-query`
Execute SELECT queries against your activities database.
**Example usage in Claude:**
- "Show me my 10 most recent runs"
- "What's my average heart rate this month?"
- "Find all runs longer than 10km"
- "Calculate my total distance this year"
**Security:** One `SELECT` or `WITH ... SELECT` statement is allowed per call. Write statements and SQLite administrative statements are blocked.
### 3. `sync-activities`
Download and sync new activities from Garmin Connect to the local database.
**Example usage in Claude:**
- "Sync my latest Garmin activities"
- "Update my running data"
- "Check for new activities"
Returns a summary showing:
- Number of new activities downloaded
- Total activities in the database
- Date of your latest activity
### 4. `sync-health`
Incrementally sync daily heart-rate summaries from Garmin Connect. The first sync
downloads the latest 30 days; later syncs refresh the newest stored day and add
new days through today. Use `startDate` and `endDate` in `YYYY-MM-DD` format to
backfill a specific range.
The `daily_heart_rate` table includes resting, minimum, maximum, and seven-day
average resting heart rate. Metrics unavailable from a device are stored as
`NULL` and can be queried with `run-query`.
### Workout management
The following tools manage structured workouts in Garmin Connect:
| Tool | Description |
|------|-------------|
| `list-workouts` | List saved workouts |
| `get-workout` | Get a workout and all of its structured steps |
| `create-workout` | Create a workout from a Garmin workout JSON object |
| `rename-workout` | Rename a workout without changing its steps |
| `delete-workout` | Permanently delete a saved workout |
| `schedule-workout` | Add a workout to the Garmin calendar |
| `list-scheduled-workouts` | List calendar entries for a month |
| `get-scheduled-workout` | Get a calendar workout by schedule ID |
| `unschedule-workout` | Remove a calendar entry without deleting the workout |
Examples:
- "Create a 5 x 3-minute running interval workout and schedule it tomorrow"
- "Rename my VO2 max workout"
- "Remove the duplicate scheduled workout but keep the saved workout"
Workout creation, renaming, scheduling, unscheduling, and deletion modify your
Garmin Connect account. `delete-workout` removes the saved workout itself;
`unschedule-workout` removes only one calendar entry.
## What Data You Get
The SQLite database contains three tables as data is synced:
- `activities` for activity summaries and performance metrics
- `activity_laps` for per-lap pace, heart rate, cadence, running dynamics, elevation, and location metrics
- `daily_heart_rate` for daily minimum, maximum, resting, and seven-day-average resting heart rate
Use the `get-schema` tool to inspect the exact current columns. Activity data includes:
**Basic Metrics:**
- Activity ID, name, description, timestamps (local & GMT)
- Activity type, location name
- Distance, duration, elapsed duration, moving duration
- Calories, steps, lap count
**Heart Rate Data:**
- Average/max heart rate
- Lactate threshold BPM
- Time in each HR zone (1-5)
- VO2 Max value
**Speed & Pace:**
- Average/max speed
- Fastest splits (1K, 5K, 10K, mile)
**Running Dynamics:**
- Average/max stride length
- Average/max cadence (including double cadence)
- Average vertical oscillation
- Average ground contact time
- Vertical ratio, vertical speed
**Training Load & Intensity:**
- Activity training load
- Training effect (aerobic/anaerobic)
- Vigorous/moderate intensity minutes
**Elevation:**
- Elevation gain/loss
- Min/max elevation
**Power Metrics:**
- Average/max power
- Grit and Flow scores
## How It Works
1. **Initial Setup:** Run `npm run download` to download all your activities from Garmin Connect into a local SQLite database
2. **MCP integration:** The server exposes tools that compatible clients can use to query your data
3. **Stay updated:** Use the `sync-activities` and `sync-health` tools to download new data anytime
4. **Flexible queries:** The client can run read-only SQL queries to analyze your data
## Development
```bash
# Download/update activities from Garmin
npm run download
# Build TypeScript to JavaScript
npm run build
# Run the unit test suite
npm test
# Run the stdio and HTTP smoke test (requires a build and local database)
npm run test:mcp
# Run server directly (for testing)
npm start
```
**Note:** When using with Claude Desktop via the tsx configuration, you don't need to build - changes to TypeScript files are automatically picked up.
## Troubleshooting
### Authentication Issues
- Verify credentials in `.env` file (GARMIN_USERNAME and GARMIN_PASSWORD)
- Ensure you can log into connect.garmin.com manually
- Try logging in via web browser first
- Check that your password doesn't contain special characters that need escaping
### Database Not Found
- Run `npm run download` first to create the database
- Check that `data/garmin-data.db` exists in your project directory
- If the database may be corrupted, back it up before recreating it with `npm run download`
### Sync Not Working in Claude
- Restart Claude Desktop after configuration changes
- Check Claude Desktop logs for error messages
- Verify the paths in `claude_desktop_config.json` are absolute, not relative
- Ensure `.env` file exists in the project root directory
### Query Errors
- Only SELECT queries are allowed (INSERT, UPDATE, DELETE are blocked)
- Use the `get-schema` tool to see available table columns
- Check SQL syntax is correct
## Security & Privacy
For a local stdio installation:
- Credentials are loaded from the local `.env` file and sent to Garmin Connect for authentication.
- Synced data is stored in the local SQLite database.
- Query access is read-only.
For a self-hosted HTTP installation, credentials and data reside on the host running the container, and MCP responses travel between that host and the configured client. Set a strong `API_KEY`, use HTTPS at the reverse proxy, restrict network access, and protect the persistent volume and `.env` file. Workout tools can modify the connected Garmin account; review destructive tool calls before approving them.
## Technical Details
**Architecture:**
- TypeScript-based MCP server
- SQLite database for activity, lap, and health storage
- Direct integration with Garmin Connect API (via `garmin-connect` library)
- Runs via `tsx` for development (no build step needed with Claude Desktop)
- MCP SDK with stdio and Streamable HTTP transports
**Database:**
- Location: `data/garmin-data.db`
- Format: SQLite 3
- Updates: Incremental (only new activities downloaded)
- Contains: activity summaries, activity laps, and synced daily heart-rate summaries from Garmin Connect
## Requirements
- **Node.js 20 or 22** (the versions exercised in CI)
- **npm** (comes with Node.js)
- **Garmin Connect account** with running/fitness activities
- **An MCP-compatible client** such as Claude Desktop, or an HTTP MCP client for self-hosting
- **Garmin device** that syncs to Garmin Connect (watch, bike computer, etc.)
## Contributing
Feel free to open issues or submit pull requests for:
- Bug fixes
- New data fields to track
- Additional MCP tools
- Documentation improvements
## License
MIT
---
**Note:** This project is not affiliated with, endorsed by, or sponsored by Garmin Ltd. or Garmin International, Inc.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing