Skip to main content
Glama
vnglst

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.