AgentStack
MCP verified MIT Self-run

Garmin Grafana Mcp Server

mcp-ghighi3f-garmin-grafana-mcp-server · by ghighi3f

Bring your Garmin data to AI. An MCP server that bridges the garmin-grafana InfluxDB ecosystem with AI agents.

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add mcp-ghighi3f-garmin-grafana-mcp-server

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Garmin Grafana Mcp Server? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

garmin-grafana-mcp-server

An optional, self-hosted Model Context Protocol (MCP) server that exposes your Garmin training data to AI assistants. It is designed as a companion add-on to the arpanghosh8453/garmin-grafana project and reads directly from the InfluxDB instance that garmin-grafana already manages.

This server is a pure data access layer. It returns raw numbers from InfluxDB. All interpretation, analysis, and planning is delegated to the AI model consuming the tools — this server makes no assumptions about your fitness goals.


Table of Contents

  • [How it fits into the ecosystem](#how-it-fits-into-the-ecosystem)
  • [🪄 See it in action: The AI Coach](#-see-it-in-action-the-ai-coach)
  • [Prerequisites](#prerequisites)
  • [Quick Start (Docker Compose)](#quick-start-docker-compose)
  • [Deployment (Docker — recommended)](#deployment-docker--recommended)
  • [Local Development](#local-development)
  • [Connecting to an MCP Client](#connecting-to-an-mcp-client)
  • [MCP Tools](#mcp-tools)
  • [Example prompts](#example-prompts)
  • [Configuration reference](#configuration-reference)
  • [Project structure](#project-structure)
  • [Schema compatibility](#schema-compatibility)
  • [Troubleshooting](#troubleshooting)
  • [License](#license)

How it fits into the ecosystem

Garmin Device
      │
      ▼
garmin-grafana         ← you already have this running
  ├─ Garmin API sync
  ├─ InfluxDB (stores your data)
  └─ Grafana (dashboards)

garmin-grafana-mcp-server   ← this repo adds this
  └─ Reads from the same InfluxDB
  └─ Exposes MCP tools to AI clients (Claude, Copilot, etc.)

🪄 See it in action: The AI Coach

Because this MCP server provides structured, context-aware data to LLMs like Claude, it acts as a world-class personal coach.

Here is an actual, unedited response from Claude (via Copilot) summarizing a ride by cross-referencing six different MCP tools simultaneously (Activity Details, Training Status, Sleep/Recovery, Training Zones, and Stress):

> Prompt: "I just came back from my long Z2 ride. Please assess it in detail against our training plan!"

View Claude's Analysis (Click to expand)

1. SCORECARD vs PLAN

| Metric | Target | Actual | Grade | |---|---|---|---| | Duration | 120–150 min | 137.5 min | ✅ Perfect | | Time in Z1+Z2 | — | 99.4% (130.1 min) | ✅ Excellent | | Max HR | ≤130 (ceiling) | 135 bpm | ⚠️ Brief breach | | HR drift |


Prerequisites

| Requirement | Notes | |---|---| | garmin-grafana | Must already be running and populated | | Docker & Docker Compose | For the recommended deployment path | | Python 3.11+ | For local development only |


Quick Start (Docker Compose)

If you just want to run the pre-built image alongside your existing garmin-grafana stack, create a docker-compose.yml with the snippet below. No need to clone this repository.

services:
  garmin-grafana-mcp-server:
    image: ghcr.io/ghighi3f/garmin-grafana-mcp-server:latest
    container_name: garmin-grafana-mcp-server
    restart: unless-stopped
    ports:
      - "8765:8765"
    environment:
      # ── Required ─────────────────────────────────────────────
      # Connection to the InfluxDB instance managed by garmin-grafana.
      INFLUXDB_HOST: influxdb            # container name on the shared Docker network
      INFLUXDB_PORT: 8086
      INFLUXDB_DATABASE: GarminStats     # database name from your garmin-grafana .env
      INFLUXDB_USERNAME: admin           # InfluxDB v1 credentials
      INFLUXDB_PASSWORD: adminpassword   # ← change to your actual password

      # ── InfluxDB v2 only (uncomment if you run v2) ──────────
      # INFLUXDB_VERSION: 2
      # INFLUXDB_TOKEN: "your-influxdb-token"
      # INFLUXDB_ORG: "your-org"

      # ── Optional: override schema names ─────────────────────
      # Only needed if your garmin-grafana uses non-default measurement
      # or field names. The defaults match the standard garmin-grafana schema.
      #
      # Measurement names:
      # MEASUREMENT_ACTIVITIES: ActivitySummary
      # MEASUREMENT_DAILY_STATS: DailyStats
      # MEASUREMENT_SLEEP_SUMMARY: SleepSummary
      # MEASUREMENT_ACTIVITY_SESSION: ActivitySession
      # MEASUREMENT_ACTIVITY_LAP: ActivityLap
      # MEASUREMENT_RESTING_HR: DailyStats
      # MEASUREMENT_HRV: HRV_Intraday
      # MEASUREMENT_VO2_MAX: VO2_Max
      # MEASUREMENT_RACE_PREDICTIONS: RacePredictions
      # MEASUREMENT_BODY_COMPOSITION: BodyComposition
      #
      # Field names:
      # FIELD_RESTING_HR: restingHeartRate
      # FIELD_HRV: hrvValue
      # FIELD_VO2_MAX_RUNNING: VO2_max_value
      # FIELD_VO2_MAX_CYCLING: VO2_max_value_cycling
      # FIELD_RACE_5K: time5K
      # FIELD_RACE_10K: time10K
      # FIELD_RACE_HALF: timeHalfMarathon
      # FIELD_RACE_MARATHON: timeMarathon
      # FIELD_WEIGHT: weight
      # FIELD_HR_ZONE_1: hrTimeInZone_1
      # FIELD_HR_ZONE_2: hrTimeInZone_2
      # FIELD_HR_ZONE_3: hrTimeInZone_3
      # FIELD_HR_ZONE_4: hrTimeInZone_4
      # FIELD_HR_ZONE_5: hrTimeInZone_5
      #
      # Training Status & Readiness (garmin-grafana v0.4.0+):
      # MEASUREMENT_TRAINING_STATUS: TrainingStatus
      # MEASUREMENT_TRAINING_READINESS: TrainingReadiness
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8765/health')"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

networks:
  default:
    external: true
    name: garmin-grafana_default   # ← must match your garmin-grafana network

Then start it:

docker compose up -d
curl http://localhost:8765/health

> Tip: The only variables most users need to change are INFLUXDB_PASSWORD and possibly INFLUXDB_DATABASE. All schema variables have sensible defaults that match the standard garmin-grafana InfluxDB schema out of the box.


Deployment (Docker — recommended)

This is the recommended way to run the server alongside your existing garmin-grafana stack.

1. Clone this repository

git clone https://github.com/ghighi3f/garmin-grafana-mcp-server.git
cd garmin-grafana-mcp-server

2. Configure environment variables

cp .env.example .env

Edit .env to match your garmin-grafana setup. The most important values:

| Variable | Default | Description | |---|---|---| | INFLUXDB_HOST | influxdb | Container name of the InfluxDB service inside the garmin-grafana network. Override in .env only if your service is named differently. | | INFLUXDB_PORT | 8086 | InfluxDB port (usually unchanged) | | INFLUXDB_DATABASE | GarminStats | Database name set in your garmin-grafana config | | INFLUXDB_USERNAME | admin | InfluxDB username from your garmin-grafana .env | | INFLUXDB_PASSWORD | (see .env.example) | InfluxDB password from your garmin-grafana .env | | INFLUXDB_VERSION | 1 | 1 for InfluxDB v1; 2 for InfluxDB v2 | | MCP_PORT | 8765 | Port the MCP server listens on | | QUERY_TIMEZONE | UTC | IANA timezone — must match USER_TIMEZONE in garmin-grafana (see [Timezone](#timezone)) |

> Note: INFLUXDB_HOST is overridden to influxdb directly in docker-compose.yml so the container resolves the InfluxDB service by its Docker network name. You do not need to set it in .env for the Docker deployment.

3. Identify your garmin-grafana Docker network

The docker-compose.yml in this repo connects to the same Docker network that garmin-grafana creates. By default that network is named garmin-grafana_default.

Verify the network name:

docker network ls | grep garmin

If the name differs from garmin-grafana_default, edit the networks section at the bottom of docker-compose.yml:

networks:
  default:
    external: true
    name: your-actual-network-name   # ← change this

4. Start the server

docker compose up -d

Verify it is running:

curl http://localhost:8765/health

Expected response:

{
  "influxdb": "connected",
  "last_activity_timestamp": "2026-03-17T07:30:00+00:00",
  "measurements_found": ["ActivitySummary", "DailyStats", "SleepSummary", "..."],
  "mcp_endpoint": "http://localhost:8765/mcp",
  "sse_endpoint": "http://localhost:8765/sse"
}

Local Development

If you want to run the MCP server directly on your host machine (outside Docker) during development, the server process needs to reach InfluxDB on port 8086. Since InfluxDB is inside a Docker network, you must expose its port to the host.

Step 1: Expose InfluxDB port in your garmin-grafana docker-compose.yml

Open the garmin-grafana docker-compose.yml and add a ports mapping to the influxdb service:

# In your garmin-grafana docker-compose.yml:
services:
  influxdb:
    image: influxdb:1.8
    ports:
      - "8086:8086"   # ← add this line
    # ... rest of your config

Then restart the garmin-grafana stack:

docker compose down && docker compose up -d

Step 2: Set up the Python environment

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

For InfluxDB v2, also install:

pip install influxdb-client

Step 3: Configure for local access

cp .env.example .env

Edit .env and set:

INFLUXDB_HOST=localhost   # reach InfluxDB via the exposed host port
INFLUXDB_PORT=8086

Step 4: Run the server

python server.py

You should see:

============================================================
  Garmin MCP Server
============================================================
  InfluxDB   : localhost:8086/GarminStats
  Measurements: ['ActivitySummary', 'DailyStats', ...]
  Transports : HTTP + SSE (always active)
  /mcp  (Streamable HTTP) : http://localhost:8765/mcp
  /sse  (SSE, deprecated) : http://localhost:8765/sse
  /health                 : http://localhost:8765/health
============================================================

Connecting to an MCP Client

All HTTP transports are always active simultaneously — no MCP_TRANSPORT configuration needed for HTTP deployments. Point your client at the right URL and go.


Perplexity Mac / Legacy SSE clients

> Note: SSE transport is deprecated in the MCP specification. Kept for backward compatibility — prefer Streamable HTTP for new integrations.

{
  "mcpServers": {
    "garmin": {
      "type": "sse",
      "url": "http://:8765/sse"
    }
  }
}

ChatGPT / VS Code / Modern clients (Streamable HTTP)

{
  "mcpServers": {
    "garmin": {
      "type": "http",
      "url": "http://:8765/mcp"
    }
  }
}

Replace ` with the IP or hostname of the machine running the server (e.g. 192.168.1.100, pi5.local, or localhost`).


Claude Desktop / Cursor / Windsurf / Claude Code (Local stdio)

With Docker (server reads InfluxDB via the shared garmin-grafana network):

{
  "mcpServers": {
    "garmin": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "--network", "garmin-grafana_default",
        "--env-file", "/path/to/.env",
        "ghcr.io/ghighi3f/garmin-grafana-mcp-server:latest",
        "python", "server.py"
      ],
      "env": { "MCP_TRANSPORT": "stdio" }
    }
  }
}

Without Docker (local Python, INFLUXDB_HOST=localhost in .env):

{
  "mcpServers": {
    "garmin": {
      "command": "python",
      "args": ["/path/to/garmin-grafana-mcp-server/server.py"],
      "env": {
        "MCP_TRANSPORT": "stdio",
        "INFLUXDB_HOST": "localhost",
        "INFLUXDB_PORT": "8086",
        "INFLUXDB_DATABASE": "GarminStats",
        "INFLUXDB_USERNAME": "admin",
        "INFLUXDB_PASSWORD": "your-password"
      }
    }
  }
}

Or from the command line:

MCP_TRANSPORT=stdio python server.py

> Tip: The startup banner is written to stderr in stdio mode so it never interferes with the MCP protocol on stdout.


No configuration needed

All HTTP transports (SSE + Streamable HTTP) are always active on the same port. MCP_TRANSPORT only needs to be set for stdio (subprocess) clients.


MCP Tools

get_last_activity

Returns the single most recent Garmin activity with all available fields. No input parameters.

get_recent_activities

Returns activities from the last N days with a summary.

| Parameter | Type | Default | Range / Values | |---|---|---|---| | days | int | 7 | 1–90 | | sport_type | str | "all" | Any Garmin sport type (e.g. "running", "cycling", "hiking", "trail_running", "strength_training"). Supports partial matching — "cycling" also matches "indoor_cycling". "all" = no filter. | | limit | int | 20 | 1–100 |

get_weekly_load_summary

Groups activities into ISO calendar weeks with resting HR and HRV.

| Parameter | Type | Default | Range | |---|---|---|---| | weeks | int | 4 | 1–16 |

get_daily_recovery

Merges SleepSummary + DailyStats per date for a holistic recovery view.

| Parameter | Type | Default | Range | |---|---|---|---| | days | int | 7 | 1–14 |

get_activity_details

Detailed breakdown of a single activity: HR zones, training effect, per-lap splits, and analysis hints (fastest/slowest lap, HR drift). For activities with a power meter, also returns max_power, avg_power_pedaling_only, and standing_duration_seconds per lap.

| Parameter | Type | Description | |---|---|---| | activity_id | str | Activity ID from get_recent_activities |

get_fitness_trend

Long-term fitness trajectory: VO2max, race predictions, weight, resting HR — sampled weekly.

| Parameter | Type | Default | Range | |---|---|---|---| | weeks | int | 12 | 4–52 |

get_training_zones

HR zone distribution and polarization analysis (low/moderate/high intensity breakdown).

| Parameter | Type | Default | Range / Values | |---|---|---|---| | days | int | 30 | 7–180 | | sport_type | str | "all" | Any Garmin sport type (e.g. "running", "cycling", "swimming", "hiking"). Supports partial matching. "all" = no filter. |

explore_schema

Discover available InfluxDB measurements, fields, and tags at runtime. Useful for verifying field names before querying or debugging measurement mismatches.

| Parameter | Type | Optional | Description | |---|---|---|---| | measurement_name | str | Yes | Inspect a specific measurement (e.g. "ActivitySummary"). Omit to list all measurements. |

Returns (no measurement specified):

{"measurements": ["ActivitySummary", "DailyStats", "SleepSummary", "..."]}

Returns (measurement specified):

{
  "measurement": "ActivitySummary",
  "fields": [
    {"field": "distance", "type": "float"},
    {"field": "elapsedDuration", "type": "integer"},
    "..."
  ],
  "tags": ["userId", "deviceId", "..."]
}

get_stress_body_battery

Daily stress breakdown and body battery trend over 7–30 days. Surfaces systemic fatigue patterns that get_daily_recovery buries inside a per-day blob.

| Parameter | Type | Default | Range | |---|---|---|---| | days | int | 7 | 7–30 |

Returns:

  • Per-day: stress minutes (high/medium/low/rest) + body battery (at wake, high, low, drained, charged)
  • Summary: period averages + trend direction ("improving" / "declining" / "stable" for body battery; "improving" / "worsening" / "stable" for stress)

get_personal_records

All-time personal records grouped by sport type. For each record, returns the value, unit, and the activityid, date, and activityname of the record-setting activity.

| Parameter | Type | Default | Range / Values | |---|---|---|---| | sport_type | str | "all" | Any Garmin sport type (e.g. "running", "cycling"). Supports partial matching. `"all

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.