Garmin MCP Server
Connects to Garmin Connect to access fitness and health data, including activity listings, detailed activity information, health metrics (steps, heart rate, sleep), and body composition data.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Garmin MCP Server show me my steps from yesterday"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Garmin MCP Server
This Model Context Protocol (MCP) server connects to Garmin Connect and exposes your fitness and health data to Claude and other MCP-compatible clients.
Garmin's API is accessed via the awesome python-garminconnect library.
Features
List recent activities with pagination support
Get detailed activity information
Edit activities: name, type, description/notes, event type, perceived effort (RPE), feel, and strength exercise sets
Access health metrics (steps, heart rate, sleep, stress, respiration)
View body composition data
Track training status and readiness
Access cycling FTP and lactate threshold metrics
Manage gear and equipment, including free-text notes returned by
get_gearAccess workouts and training plans
Inspect detailed workout step structures, including repeat groups and swim pace targets
Weekly health aggregates (steps, stress, intensity minutes)
Advanced cycling analytics: power zones, FIT file analysis, DI2 electronic shift intelligence
Training load trend (CTL/ATL/TSB), HRV trend, VO2 max trend, respiration rate trend
Power Duration Curve, climb detection with VAM, cardiac drift (aerobic decoupling), W/kg calculations
Tool Coverage
This MCP server implements 110+ tools covering ~90% of the python-garminconnect library (v0.3.2):
✅ Activity Management (21 tools) - includes write tools for type, description, event type, perceived effort, feel, and exercise sets
✅ Health & Wellness (34 tools) - includes custom lightweight summary tools
✅ Training & Performance (13 tools) - includes CTL/ATL/TSB, HRV, VO2 max, and respiration trends
✅ Workouts (8 tools)
✅ Devices (8 tools) - includes sending a workout to a device
✅ Gear Management (5 tools)
✅ Weight Tracking (5 tools)
✅ Challenges & Badges (10 tools)
✅ Nutrition (9 tools) - food logs, meals, custom foods, food logging, and multi-day intake summaries
✅ Women's Health (3 tools)
✅ User Profile (3 tools)
✅ High-Level Workout Builders (4 tools) - create and schedule workouts without writing JSON
✅ Courses (5 tools) - list / get details / upload GPX as course / download GPX / delete course
✅ Activity Analysis (2 tools) - FIT file parsing, Power Duration Curve; requires power meter and/or Di2
✅ Activity File Downloads (2 tools) - download activity files in FIT, GPX, TCX, or CSV format
Note: Activity Analysis tools require a compatible power meter (e.g., Garmin Rally, Favero Assioma, PowerTap P1) and/or Shimano Di2 / SRAM eTap electronic shifting. The
fitparsedependency is installed automatically.
Gear Notes
Each item in the gear array returned by get_gear includes a notes field
containing the free-text Notes value shown in Garmin Connect. Gear without a
Notes value returns null; all existing gear fields remain unchanged.
Activity File Downloads
Two tools let you download a raw activity file to disk:
download_activity_file(activity_id, format="fit", output_dir=None)— downloads the activity and saves it to the configured directory.formatacceptsfit(default),gpx,tcx, orcsv.set_fit_download_dir(path)— sets and persists the default download directory (written to the config file).
Where files are saved (precedence):
output_dirargument — one-off override, not persisted.GARMIN_FIT_DOWNLOAD_DIRenvironment variable.Persisted config set via
set_fit_download_dir.
First-run behavior: if no directory is configured, download_activity_file returns status: "needs_setup". The assistant will ask where you want to save files (suggesting the current directory as default), call set_fit_download_dir to persist your choice, and then retry the download automatically.
Strength Exercise Sets
set_activity_exercise_sets(activity_id, sets) writes structured sets onto a recorded
strength activity, the same per-set grid the Garmin Connect "Edit sets" screen saves, so
they appear in Garmin's exercise history and volume charts. It replaces every existing
set, including watch-detected ones; read them first with get_activity_exercise_sets
if you want to keep any.
Each set takes a category and optional exercise_name using Garmin's keys from
Exercises.json, plus
optional reps, weight_kg or weight_lb (omit for bodyweight), duration_seconds and
rest_seconds. Garmin rejects unknown keys and leaves the activity unchanged.
If the watch recorded the right sets but misidentified the exercises, pass
keep_timing: true: the recorded start times, durations and rest periods are kept and only
the exercise, reps and weight are replaced, in order. The number of sets must match the
activity's recorded active sets.
{
"activity_id": 12345678901,
"sets": [
{"category": "DEADLIFT", "exercise_name": "BARBELL_DEADLIFT", "reps": 5, "weight_kg": 150, "rest_seconds": 180},
{"category": "PULL_UP", "reps": 8}
]
}Intentionally Skipped Endpoints
Some endpoints are not implemented due to performance or complexity considerations:
High Data Volume:
get_activity_details()- Returns large GPS tracks and chart data (50KB-500KB). Useget_activity()for summaries instead.
Specialized Workout Formats:
upload_running_workout(),upload_cycling_workout(),upload_swimming_workout()- Sport-specific workout uploads. Useupload_workout()for general workouts.
Maintenance & Destructive Operations:
delete_blood_pressure()- Destructive operations require careful consideration.delete_activity()is implemented with a confirmation gate: call once to preview, then again withconfirm=trueto delete. Deletions are logged to stderr. There is no undo.Internal/Auth methods:
login(),resume_login(),connectapi(),download()- Handled automatically by the library.
If you need any of these endpoints, please open an issue.
Related MCP server: Garmin MCP Server
Tool Filtering
This server registers 155 tools by default, which is a lot of context for an LLM to carry in every session. You can expose only the tools you need with three optional environment variables:
Env var | Effect |
| A built-in subset by area: |
| Comma-separated allowlist — if set, only these tools are registered. With a profile, these are added to it instead. |
| Comma-separated denylist — listed tools are skipped. Ignored if an allowlist is set, unless a profile is also set; then it removes tools from the profile. |
Tool names are case-insensitive. With none of them set, all tools register (unchanged default behaviour). Names that match no tool are ignored with a warning on stderr, which makes typos easy to spot. An unknown profile name stops the server at startup with the list of known profiles.
Profiles
Profile | Tools | What it covers |
| 20 | Recent activities with splits, daily stats, sleep, HRV, stress, body battery, training readiness and status |
| 53 | Every health_wellness tool plus weight, body composition, blood pressure, hydration, women's health, fitness age, HRV and respiration trends |
| 41 | Activity list, detail, splits and edits; FIT analysis and power curve; gear; courses |
| 26 | Workout library, upload, schedule and delete; the high-level builders; the calendar |
| 38 | Training status, load, effect, VO2max, FTP, lactate threshold, endurance and hill scores, readiness, recovery time, personal records, race predictions, goals, badges |
| 23 | Food log, custom foods, daily nutrition settings, hydration, energy balance |
Every profile also includes the identity and unit tools (get_full_name, get_heart_rate_zones, get_unit_system, get_user_profile, get_userprofile_settings), so the
client can label the numbers it gets back.
Example — a coaching session that needs training metrics and the workout tools:
"env": {
"GARMIN_TOOL_PROFILE": "training,workouts"
}Example — the health profile plus recent activities, without the write tools:
"env": {
"GARMIN_TOOL_PROFILE": "health",
"GARMIN_ENABLED_TOOLS": "get_activities,get_activity",
"GARMIN_DISABLED_TOOLS": "add_weigh_in,add_weigh_in_with_timestamps,delete_weigh_ins,add_body_composition,add_hydration_data,set_blood_pressure"
}Example — hand-pick tools without a profile:
"env": {
"GARMIN_ENABLED_TOOLS": "get_sleep_data,get_stress_summary,get_activities"
}High-level workout tools
These builder tools let an LLM create and schedule workouts without writing raw Garmin JSON.
create_walk_run_workout
Creates a walk/run interval workout with optional heart-rate zone target.
{
"name": "W3 Mié 2:2",
"run_seconds": 120,
"walk_seconds": 120,
"repeats": 9,
"warmup_min": 10,
"cooldown_min": 8,
"hr_zone": "Z3"
}Returns: {"status": "success", "workout_id": 1234567890, ...}
create_z2_walk_workout
Creates a steady Z2 walking workout.
{
"name": "Z2 Walk 45m",
"duration_min": 45,
"hr_min": 110,
"hr_max": 130
}Returns: {"status": "success", "workout_id": 1234567890, ...}
create_strength_workout
Creates a strength workout from a list of exercises. Each becomes a reps-based step, with the
name kept in the step description. The name is also sent as exerciseName, but Garmin only
retains that when it matches one of its own exercise keys (e.g. FARMERS_CARRY) — any other
value is accepted and then stored empty.
category is optional and passed straight through. Omit it and the key is left out of the
payload entirely, which Garmin accepts. Supply it and it must be one of Garmin's exercise
categories — anything else, including OTHER and UNASSIGNED, is rejected with
400 - Invalid category. The full list is published at
Exercises.json.
{
"name": "Full Body A",
"exercises": [
{"name": "Sentadillas", "sets": 3, "reps": 12, "rest_seconds": 90},
{"name": "Flexiones", "sets": 3, "reps": 15, "rest_seconds": 60},
{"name": "Peso muerto", "sets": 3, "reps": 10, "rest_seconds": 90},
{"name": "Farmers Carry 40m", "sets": 3, "reps": 1, "rest_seconds": 90, "category": "CARRY"}
]
}Returns: {"status": "success", "workout_id": 1234567890, ...}
schedule_week
Schedules multiple workouts in one call.
{
"week": [
{"date": "2026-05-12", "workout_id": 1234567890},
{"date": "2026-05-14", "workout_id": 1234567891}
]
}Returns: {"status": "complete", "scheduled": [...]}
Full flow example
create_walk_run_workout(name="W3 Mié 2:2", run_seconds=120, walk_seconds=120,
repeats=9, warmup_min=10, cooldown_min=8)
→ workout_id = 1560092011
schedule_workout(workout_id=1560092011, date="2026-05-06")
→ OKAfter syncing your watch, the workout appears on the Forerunner 965 calendar.
Raw upload_workout end conditions
When building custom workout JSON for upload_workout or upload_workouts, the
endCondition.conditionTypeId and endCondition.conditionTypeKey must match
Garmin's canonical mapping. Garmin treats the numeric conditionTypeId as the
source of truth; if the key and ID conflict, Garmin stores the condition that
matches the ID.
For example, this is invalid for a heart-rate end condition because ID 4 is
calories, not heart.rate:
{
"endCondition": {
"conditionTypeId": 4,
"conditionTypeKey": "heart.rate"
},
"endConditionValue": 145
}Use ID 6 for heart rate:
{
"endCondition": {
"conditionTypeId": 6,
"conditionTypeKey": "heart.rate"
},
"endConditionValue": 145
}For a zone-based heart-rate end condition, use endConditionZone (1-5) on the
same endCondition object and omit endConditionValue. If both are sent,
Garmin keeps the zone and drops the value (verified against the production API
on 2026-09-01):
{
"endCondition": {
"conditionTypeId": 6,
"conditionTypeKey": "heart.rate",
"endConditionZone": 2
}
}Common end-condition IDs:
ID | Key |
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
Raw upload_workout target types
When building raw Garmin workout JSON, targetType.workoutTargetTypeId and
targetType.workoutTargetTypeKey must use Garmin's canonical mapping. Garmin
treats the numeric ID as authoritative: a mismatched payload such as
{"workoutTargetTypeId": 6, "workoutTargetTypeKey": "heart.rate"} is stored as
pace.zone, because ID 6 means pace.zone.
For a custom heart-rate range, use target type ID 4 with heart.rate.zone and
put the bpm range in targetValueOne / targetValueTwo. These value fields
belong on the workout step, alongside targetType; do not nest them inside the
targetType object:
{
"targetType": {
"workoutTargetTypeId": 4,
"workoutTargetTypeKey": "heart.rate.zone"
},
"targetValueOne": 143,
"targetValueTwo": 157
}The same shape applies to a custom running pace range. Pace bounds use meters per second:
{
"targetType": {
"workoutTargetTypeId": 6,
"workoutTargetTypeKey": "pace.zone"
},
"targetValueOne": 1.9607843,
"targetValueTwo": 2.0833333
}That example represents 8:00–8:30 min/km. The lower numeric bound is listed
first for consistency with the heart-rate example; Garmin normalizes either
bound order. Garmin silently discards values nested inside targetType, leaving
a pace target with no active range. The upload tools repair that unambiguous
nesting mistake, but reject the request if nested and step-level values conflict.
For a named Garmin HR zone, use the same target type with zoneNumber instead:
{
"targetType": {
"workoutTargetTypeId": 4,
"workoutTargetTypeKey": "heart.rate.zone"
},
"zoneNumber": 3
}Use either zoneNumber or targetValueOne / targetValueTwo on a target, not
both. Garmin treats the named zone as authoritative and silently discards a
coexisting custom range, so the upload tools reject that ambiguous shape.
One-click Install (Claude Desktop)
The easiest way to add this server to Claude Desktop is via the .dxt Desktop Extension file — no JSON editing required.
Download and install
Download the latest
garmin-mcp.dxtfrom the Releases page.Drag the
.dxtfile into the Claude Desktop window, or double-click it, or go to Settings → Extensions → Install Extension and select the file.Claude Desktop will prompt you for optional configuration (token path, email, password).
First-time authentication
The extension installs and runs the server automatically, but you must authenticate with Garmin once before data can be fetched:
uvx --python 3.12 --from git+https://github.com/Taxuspt/garmin_mcp garmin-mcp-authThis saves OAuth tokens to ~/.garminconnect. After that the server works without any credentials in the config.
Note: Tokens are valid for approximately 6 months. Re-run
garmin-mcp-authwhen they expire.
Build the .dxt yourself
bash scripts/build_dxt.sh # produces garmin-mcp.dxt in the repo rootSetup
Quick Start for MCP Clients
The easiest way to use this MCP server with Claude Desktop, Codex, or another MCP client is to authenticate once before adding the server to your configuration.
Prerequisites
Python 3.12+
Garmin Connect account
MFA may be required if enabled on your account
Step 1: Pre-authenticate (One-time)
Before adding the server to your MCP client, authenticate once in your terminal:
# Install and run authentication tool
uvx --python 3.12 --from git+https://github.com/Taxuspt/garmin_mcp garmin-mcp-auth
# You'll be prompted for:
# - Email (or set GARMIN_EMAIL env var)
# - Password (or set GARMIN_PASSWORD env var)
# - MFA code (if enabled on your account)
# OAuth tokens will be saved to ~/.garminconnectYou can verify your credentials at any time with
uv run garmin-mcp-auth --verifyNote: You can also set credentials via environment variables:
GARMIN_EMAIL=your@email.com GARMIN_PASSWORD=secret garmin-mcp-authIf you don't have MFA enabled you can also skip garmin-mcp-auth and pass GARMIN_EMAIL and GARMIN_PASSWORD as env variables directly to your MCP client, if supported. For better security, prefer the pre-authentication flow above and keep credentials out of MCP client configuration.
Step 2: Configure Claude Desktop
Add to your Claude Desktop MCP settings WITHOUT credentials:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"garmin": {
"command": "uvx",
"args": [
"--python",
"3.12",
"--from",
"git+https://github.com/Taxuspt/garmin_mcp",
"garmin-mcp"
]
}
}
}Important: No GARMIN_EMAIL or GARMIN_PASSWORD needed in config! The server uses your saved tokens.
Step 3: Restart your MCP client
Your Garmin data is now available to your MCP client.
For Codex and other clients, see the examples below.
Using more than one Garmin account
A single server process is bound to one Garmin account. To use several accounts
at once, run one server instance per account, each with its own token
directory, selected with the GARMINTOKENS environment variable.
GARMINTOKENS defaults to ~/.garminconnect. Point it somewhere else and the
server reads and writes tokens there instead, leaving the default store
untouched.
Step 1: Authenticate each account into its own directory
garmin-mcp-auth --token-path ~/.garminconnect-alice
garmin-mcp-auth --token-path ~/.garminconnect-bob--token-path also accepts $GARMINTOKENS, so GARMINTOKENS=~/.garminconnect-alice garmin-mcp-auth
is equivalent.
Step 2: Declare one server per account
{
"mcpServers": {
"garmin-alice": {
"command": "uvx",
"args": ["--python", "3.12", "--from", "git+https://github.com/Taxuspt/garmin_mcp", "garmin-mcp"],
"env": {
"GARMINTOKENS": "${HOME}/.garminconnect-alice"
}
},
"garmin-bob": {
"command": "uvx",
"args": ["--python", "3.12", "--from", "git+https://github.com/Taxuspt/garmin_mcp", "garmin-mcp"],
"env": {
"GARMINTOKENS": "${HOME}/.garminconnect-bob"
}
}
}
}Each server logs the directory it authenticates from on startup, so you can confirm the wiring:
Trying to login to Garmin Connect using token data from directory '/home/you/.garminconnect-alice'...Notes
No silent fallback. If
GARMINTOKENSpoints at a directory with no valid tokens and login fails, the server logs the failure and tool calls fail; HTTP/readyzremains unavailable (503). The server does not switch to the default token store, so a misconfigured second server cannot silently reuse the first account's session.${HOME}is expanded even when an MCP client passes it through unresolved, and~works on Windows viaUSERPROFILE.Restrict write tools on secondary accounts. Tools such as
upload_workoutandschedule_workoutwrite to whichever account the server is bound to. PairGARMINTOKENSwithGARMIN_ENABLED_TOOLS(see Tool Filtering) to make an account read-only:"env": { "GARMINTOKENS": "${HOME}/.garminconnect-bob", "GARMIN_ENABLED_TOOLS": "get_activities,get_activities_by_date,get_activity,get_activity_splits" }Token directories hold long-lived credentials. They are created with owner-only permissions; keep them out of shared or synced folders.
Development Setup
Install the required packages on a new environment:
uv syncRunning the Server
Configuration
Your Garmin Connect credentials are read from environment variables:
GARMIN_EMAIL: Your Garmin Connect email addressGARMIN_EMAIL_FILE: Path to a file containing your Garmin Connect email addressGARMIN_PASSWORD: Your Garmin Connect passwordGARMIN_PASSWORD_FILE: Path to a file containing your Garmin Connect passwordGARMIN_IS_CN: Set totrueto use Garmin Connect China (garmin.cn) instead of the international version (default:false)GARMIN_FIT_DOWNLOAD_DIR: Default directory for downloaded activity files. When set, skips the first-run setup prompt indownload_activity_file.GARMIN_FIT_CONFIG: Path to the persisted download-directory config file (default:~/.garminconnect_fit_config.json).
File-based secrets are useful in certain environments, such as inside a Docker container. Note that you cannot set both GARMIN_EMAIL and GARMIN_EMAIL_FILE, similarly you cannot set both GARMIN_PASSWORD and GARMIN_PASSWORD_FILE.
Transport
By default the server communicates over stdio, which is what Claude Desktop, the MCP Inspector, and most local clients expect. To serve over HTTP instead (e.g. when running in a container or Kubernetes), set the transport via environment variables:
GARMIN_MCP_TRANSPORT:stdio(default),streamable-http, orsseGARMIN_MCP_HOST: bind address for HTTP transports (default127.0.0.1; set to0.0.0.0only together withGARMIN_MCP_HTTP_TOKENor behind an authenticating reverse proxy)GARMIN_MCP_PORT: bind port for HTTP transports (default8000)GARMIN_MCP_HTTP_TOKEN: shared secret for the HTTP transports. When set, every request to/mcp,/sseand/messages/must carryAuthorization: Bearer <token>; anything else gets401./healthzand/readyzstay open for probes. See Protecting the HTTP endpoint.GARMIN_MCP_STATELESS_HTTP: set totrueto runstreamable-httpwithout sessions (defaultfalse). The MCP SDK drops a stateful session after 30 minutes idle, so the first call after an idle gap gets404 Session not found; some clients (including claude.ai custom connectors) report that as a failed tool call rather than re-initializing and retrying. Stateless mode has no sessions to expire. No tool in this server depends on session state, so nothing is lost. Recommended for long-lived remote deployments.GARMIN_MCP_CALL_TIMEOUT: per-request timeout in seconds for calls to Garmin (default90). Garmin's API occasionally stalls a single request indefinitely; without this bound the call hangs until the MCP client's own timeout fires and reports the whole server as unresponsive. On timeout the tool returns a clear, retry-able error instead. Set to0to disable the bound.
GARMIN_MCP_TRANSPORT=streamable-http garmin-mcpWhen an HTTP transport is selected:
MCP clients connect to the
/mcppath (e.g.http://localhost:8000/mcp).GET /healthzreturnsokas soon as the server is up — use it as a liveness probe.GET /readyzreports whether Garmin login has finished — use it as a readiness probe. It returns200 {"status": "ready"}once login succeeded, and503with{"status": "pending"}while login is still running or{"status": "failed"}if it failed (re-rungarmin-mcp-authand restart).
Protecting the HTTP endpoint
The HTTP transports expose full read/write access to the Garmin account, so the server binds to loopback by default. For anything reachable from other machines, set a shared secret and have the client send it as a bearer token:
export GARMIN_MCP_HTTP_TOKEN="$(openssl rand -hex 32)"
GARMIN_MCP_TRANSPORT=streamable-http GARMIN_MCP_HOST=0.0.0.0 garmin-mcp# rejected: 401 {"error": "unauthorized", ...}
curl -i http://localhost:8000/mcp
# accepted (the MCP handshake then proceeds as usual)
curl -i -H "Authorization: Bearer $GARMIN_MCP_HTTP_TOKEN" http://localhost:8000/mcpMost MCP clients accept custom headers for remote servers (Claude Desktop and
claude.ai connectors, the MCP Inspector, and the SDK clients all do); put the
same Authorization: Bearer … header there. The comparison is constant-time.
/healthz and /readyz never require the token, since they only report that
the process is up and whether the Garmin login finished.
Without the token the server still starts, but if GARMIN_MCP_HOST is not a
loopback address it prints a warning on stderr that the account is exposed. A
reverse proxy with its own authentication (nginx, Traefik, Authelia, etc.)
remains a fine alternative; the token is the small, built-in option for the
common self-hosted case.
Garmin Connect China (garmin.cn)
If you use Garmin Connect China (garmin.cn) instead of the international version, set the GARMIN_IS_CN environment variable to true:
# Pre-authenticate with Garmin Connect China
GARMIN_IS_CN=true garmin-mcp-auth
# Or use the CLI flag
garmin-mcp-auth --is-cnFor Claude Desktop, add GARMIN_IS_CN to the env section:
{
"mcpServers": {
"garmin": {
"command": "uvx",
"args": [
"--python",
"3.12",
"--from",
"git+https://github.com/Taxuspt/garmin_mcp",
"garmin-mcp"
],
"env": {
"GARMIN_IS_CN": "true"
}
}
}
}For Docker, add GARMIN_IS_CN=true to your .env file or uncomment it in docker-compose.yml.
Testing the server locally with MCP Inspector
The Inspector runs directly through npx without requiring installation. Run from the project root:
npx @modelcontextprotocol/inspector uv run garmin-mcpYou'll be able to inspect and test the tools.
With Claude Desktop
Create a configuration in Claude Desktop:
Edit your Claude Desktop configuration file:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
You have two options to run the MCP locally with Claude.
Directly from github without cloning the repo:
Add this server configuration:
{
"mcpServers": {
"garmin": {
"command": "uvx",
"args": [
"--python",
"3.12",
"--from",
"git+https://github.com/Taxuspt/garmin_mcp",
"garmin-mcp"
],
"env": {
"GARMIN_EMAIL": "YOUR_GARMIN_EMAIL",
"GARMIN_PASSWORD": "YOUR_GARMIN_PASSWORD"
}
}
}
}You might have to add the full path to uvx you can check the full path with which uvx
Restart Claude Desktop
Directly from your local copy of the repository:
Add this server configuration:
{
"mcpServers": {
"garmin-local": {
"command": "uv",
"args": [
"--directory",
"<full path to your local repository>/garmin_mcp",
"run",
"garmin-mcp"
]
}
}
}Restart Claude Desktop
With Codex
Codex uses TOML for MCP server configuration. Add one of the following entries to ~/.codex/config.toml after authenticating with garmin-mcp-auth.
You can also ask your MCP-capable client to set this up for you. For example:
Install the Garmin MCP server from https://github.com/Taxuspt/garmin_mcp, authenticate with garmin-mcp-auth, and add it to my MCP configuration without storing my Garmin email or password.Directly from GitHub without cloning the repo
[mcp_servers.garmin]
command = "uvx"
args = [
"--python",
"3.12",
"--from",
"git+https://github.com/Taxuspt/garmin_mcp",
"garmin-mcp"
]Directly from your local copy of the repository
[mcp_servers.garmin-local]
command = "uv"
args = [
"--directory",
"/full/path/to/garmin_mcp",
"run",
"garmin-mcp"
]Restart your MCP client after saving the file.
With opencode
opencode auto-loads a project-level opencode.json when launched from a repository root, so contributors who clone this repo get the Garmin MCP wired up against the local source with no extra config.
From a clone of this repository (recommended for development)
This repo ships an opencode.json that runs the MCP via uv run garmin-mcp, so it always tracks the working tree.
git clone https://github.com/Taxuspt/garmin_mcp.git
cd garmin_mcp
uv sync # install dependencies
garmin-mcp-auth # one-time Garmin login (skip if ~/.garminconnect already exists)
opencode # launches with the garmin MCP attachedVerify the server is connected:
opencode mcp list
# ● ✓ garmin connected
# uv run garmin-mcpFrom any other directory (GitHub install)
Add the server to your global opencode config at ~/.config/opencode/opencode.json after running garmin-mcp-auth:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"garmin": {
"type": "local",
"command": [
"uvx",
"--python",
"3.12",
"--from",
"git+https://github.com/Taxuspt/garmin_mcp",
"garmin-mcp"
],
"enabled": true,
"timeout": 30000
}
}
}Restart opencode after saving the file. The first uvx invocation downloads and caches the package, so the initial startup may take a few seconds.
With Docker
Docker provides an isolated and consistent environment for running the MCP server.
Quick Start with Docker Compose (Recommended)
Create a
.envfile with your credentials:
echo "GARMIN_EMAIL=your_email@example.com" > .env
echo "GARMIN_PASSWORD=your_password" >> .envStart the container:
docker compose up -dView logs to monitor the server:
docker compose logs -f garmin-mcpUsing Docker Directly
# Build the image
docker build -t garmin-mcp .
# Run the container
docker run -it \
-e GARMIN_EMAIL="your_email@example.com" \
-e GARMIN_PASSWORD="your_password" \
-v garmin-tokens:/root/.garminconnect \
garmin-mcpUsing File-Based Secrets (More Secure)
For enhanced security, especially in production environments, use file-based secrets instead of environment variables:
Create a secrets directory and add your credentials:
mkdir -p secrets
echo "your_email@example.com" > secrets/garmin_email.txt
echo "your_password" > secrets/garmin_password.txt
chmod 600 secrets/*.txtEdit docker-compose.yml and uncomment the secrets section:
services:
garmin-mcp:
environment:
- GARMIN_EMAIL_FILE=/run/secrets/garmin_email
- GARMIN_PASSWORD_FILE=/run/secrets/garmin_password
secrets:
- garmin_email
- garmin_password
secrets:
garmin_email:
file: ./secrets/garmin_email.txt
garmin_password:
file: ./secrets/garmin_password.txtStart the container:
docker compose up -dHandling MFA with Docker
If you have multi-factor authentication (MFA) enabled on your Garmin account:
Run the authentication CLI with the service's token volume:
docker compose run --rm --entrypoint garmin-mcp-auth garmin-mcpWhen prompted, enter your MFA code:
Garmin Connect MFA required. Please check your email/phone for the code.
Enter MFA code: 123456The OAuth tokens will be saved to the Docker volume (
garmin-tokens), so you won't need to re-authenticate on subsequent runs.After MFA setup, you can run the container normally:
docker compose up -dDocker Volume Management
The OAuth tokens are stored in a persistent Docker volume to avoid re-authentication:
# List volumes
docker volume ls
# Inspect the tokens volume
docker volume inspect garmin_mcp_garmin-tokens
# Remove the volume (will require re-authentication)
docker volume rm garmin_mcp_garmin-tokensUsing with Claude Desktop via Docker
To use the Dockerized MCP server with Claude Desktop, you can configure it to communicate with the container. However, note that MCP servers typically communicate via stdio, which works best with direct process execution. For Docker-based deployments, consider using the standard uvx method shown in the With Claude Desktop section instead.
Usage Examples
Once connected in Claude, you can ask questions like:
"Show me my recent activities"
"What was my sleep like last night?"
"How many steps did I take yesterday?"
"Show me the details of my latest run"
"Analyze my last ride's power zones and compare to my training zones"
"Show me my CTL, ATL, and TSB trend for the last 6 weeks"
"What was my power duration curve from yesterday's ride? Estimate my FTP."
"Analyze the FIT data from my last cycling activity — how was my shifting quality on the climbs?"
"Show me my HRV trend for the last 2 weeks and flag any recovery concerns"
"What's my season best 20-minute power and when did I set it?"
Troubleshooting
get_training_effect returns HTTP 403 for a valid activity
Garmin's activity-details endpoint (/activity-service/activity/{id}) can
return 403 Forbidden even when the same activity is visible in list tools.
get_training_effect now falls back to activity list search, which still
includes aerobic/anaerobic training effect for recent activities.
If the activity is older than the recent-search window, list the activity with
get_activities / get_activities_by_date and retry, or use those list fields
directly.
get_goals returns no goals that exist in Garmin Connect
Garmin's goal-service only returns goals created in Connect's current Goals UI
(named distance/time targets such as a monthly cycling goal) when the request
sends Sec-Fetch-Site: same-origin and uses a 1-based start. With
start=0, it returns an empty list. python-garminconnect's get_goals() does not do both
(through 0.3.16), so get_goals here calls /goal-service/goal/goals
directly the way Connect's Goals page does, and uses the library call only as
a fallback.
"Failed to spawn process: No such file or directory"
If Claude Desktop can't find uvx, it's because uvx is not in the PATH that Claude Desktop uses. To fix this:
Find where
uvxis installed:
which uvxUse the full path in your configuration. For example, if
uvxis at/Users/username/.cargo/bin/uvx:
{
"mcpServers": {
"garmin": {
"command": "/Users/username/.cargo/bin/uvx",
"args": [
"--python",
"3.12",
"--from",
"git+https://github.com/Taxuspt/garmin_mcp",
"garmin-mcp"
]
}
}
}Windows: Smart App Control blocks uv / uvx
On Windows 11 with Smart App Control enabled, uv.exe / uvx.exe may be blocked when launching Garmin_MCP — including when uv tries to load an unsigned garmin-mcp.exe from a local .venv.
This is not the same as Microsoft Defender Antivirus quarantine. Smart App Control is under:
Windows Security → App & browser control → Smart App Control
Symptoms
Smart App Control notification when running
uv,uvx, or Claude Desktop withcommand: "uvx"Claude Desktop fails to spawn the server even though
uvxappears installedEvent Viewer → Applications and Services Logs → Microsoft → Windows → CodeIntegrity → Operational shows CodeIntegrity Error 3077 mentioning
uv.exeandgarmin-mcp.exe(policy / Enterprise signing level)
Confirm
Smart App Control is On (Evaluation or Enforcement).
Check the CodeIntegrity Operational log for event 3077 around the failed launch.
Defender “Virus & threat protection” history may be empty — that does not rule SAC out.
Recoveries (prefer keeping Smart App Control on)
Install or reinstall
uvvia a packaged channel, then verify in PowerShell:winget install --id=astral-sh.uv -e # or: scoop install main/uv uvx --versionPoint Claude Desktop at the full path to
uvx.exe(same idea as the PATH troubleshooting above), for example:"command": "C:\\Users\\<you>\\.local\\bin\\uvx.exe"If Enforcement still blocks loading
garmin-mcp.exe, you may need an admin/policy exception for that binary path. Turning Smart App Control off globally is a last resort, not the default advice.If local uvx remains blocked, use the Docker Compose install path instead.
Login Issues
If you encounter login issues:
Verify your credentials are correct
Check if Garmin Connect requires additional verification
Ensure the garminconnect package is up to date
Logs
For other issues, check the Claude Desktop logs at:
macOS:
~/Library/Logs/Claude/mcp-server-garmin.logWindows:
%APPDATA%\Claude\logs\mcp-server-garmin.log
Garmin Connect Multi-Factor Authentication (MFA)
Understanding MFA with MCP Servers
MCP servers run as background processes without direct terminal access. If your Garmin account has MFA enabled, you must authenticate once using the pre-authentication tool before the server can run.
Recommended: Pre-Authentication Tool
The easiest way to handle MFA is using the dedicated authentication tool:
garmin-mcp-authThis saves OAuth tokens to ~/.garminconnect for future use. The server will automatically use these tokens when running in Claude Desktop or other MCP clients.
Additional Options:
# Use environment variables for credentials
GARMIN_EMAIL=you@example.com GARMIN_PASSWORD=secret garmin-mcp-auth
# Verify existing tokens
garmin-mcp-auth --verify
# Show where tokens are looked up, what is on disk, and whether they work
garmin-mcp-auth --diagnose
# Force re-authentication (e.g., when tokens expire)
garmin-mcp-auth --force-reauth
# Use custom token location
garmin-mcp-auth --token-path ~/.garmin_tokensAlternative: Manual First Run
You can provide credentials from files to the authentication CLI:
# Store credentials in files for security
echo "your_email@example.com" > ~/.garmin_email
echo "your_password" > ~/.garmin_password
chmod 600 ~/.garmin_email ~/.garmin_password
# Run the authentication CLI before starting the server
GARMIN_EMAIL_FILE=~/.garmin_email GARMIN_PASSWORD_FILE=~/.garmin_password \
uvx --python 3.12 --from git+https://github.com/Taxuspt/garmin_mcp garmin-mcp-auth
# Enter MFA code when prompted
# Tokens will be saved automatically
# Now add to Claude Desktop config without credentialsAfter initial authentication, configure Claude Desktop without credentials (tokens are already saved):
{
"mcpServers": {
"garmin": {
"command": "uvx",
"args": [
"--python",
"3.12",
"--from",
"git+https://github.com/Taxuspt/garmin_mcp",
"garmin-mcp"
]
}
}
}Using Docker with MFA
If using Docker, follow the Handling MFA with Docker section above for a streamlined experience with persistent token storage.
Troubleshooting MFA
Error: "MFA required; run 'garmin-mcp-auth' before starting the server."
Solution:
Open terminal
Run:
garmin-mcp-authEnter credentials and MFA code
Restart Claude Desktop
Token Expired
OAuth tokens expire periodically (approximately every 6 months). Re-authenticate:
garmin-mcp-auth --force-reauthVerify Tokens Work
garmin-mcp-auth --verifygarmin-mcp-auth succeeded, but the server still cannot log in
The server loads tokens only from garmin_tokens.json inside the token
directory ($GARMINTOKENS, default ~/.garminconnect). Its startup error says
what it found there: no directory, no token file, a file it cannot read, or
tokens Garmin rejected. The usual cause is the MCP client starting the server
with a different GARMINTOKENS or HOME than the shell that ran
garmin-mcp-auth (common with Claude Desktop on Windows), so the two resolve
to different directories. Compare them with:
garmin-mcp-auth --diagnoseThe ~/.garminconnect_base64 file that garmin-mcp-auth also writes is a copy
for other garminconnect-based tools; the server does not read it.
Testing
This project includes comprehensive tests for all MCP tools. All tests are currently passing (100%).
Running Tests
# Run unit and integration tests (default - uses mocked Garmin API, e2e tests are deselected)
uv run pytest
# Run only the integration tests
uv run pytest tests/integration/
# Run tests with verbose output
uv run pytest tests/integration/ -v
# Run a specific test module
uv run pytest tests/integration/test_health_wellness_tools.py -v
# Run end-to-end tests (requires real Garmin credentials).
# Some of these create and delete foods, food logs and workouts in your account.
uv run pytest tests/e2e/ -m e2e -vTest Structure
Integration tests (200+ tests): Test all MCP tools using FastMCP integration with mocked Garmin API responses
End-to-end tests (
tests/e2e/): Test with real MCP server and Garmin API (requires valid credentials). Only run with-m e2e.
Reinstalling from local path
If you are working from a local checkout or fork:
uv tool install --python 3.12 --force C:\Users\aresd\Desktop\programacion\garmin_mcpAvailable Tools
148 toolsadd_body_compositionC
Add body composition data
Args: date: Date in YYYY-MM-DD format weight: Weight in kg percent_fat: Body fat percentage percent_hydration: Hydration percentage visceral_fat_mass: Visceral fat mass bone_mass: Bone mass muscle_mass: Muscle mass basal_met: Basal metabolic rate active_met: Active metabolic rate physique_rating: Physique rating metabolic_age: Metabolic age visceral_fat_rating: Visceral fat rating bmi: Body Mass Index
Inputs: date (required), weight (required), bmi, basal_met, +9 more. Returns: result
add_gear_to_activityB
Associate gear with an activity
Links a specific piece of gear (like shoes, bike, etc.) to an activity.
Args: activity_id: ID of the activity gear_uuid: UUID of the gear to add (get from get_gear)
Inputs: gear_uuid (required), activity_id (required). Returns: result
add_hydration_dataB
Add hydration data
Args: value_in_ml: Amount of liquid in milliliters cdate: Date in YYYY-MM-DD format timestamp: Timestamp in YYYY-MM-DDThh:mm:ss.sss format
Inputs: cdate (required), timestamp (required), value_in_ml (required). Returns: result
add_weigh_inC
Add a new weight measurement
Args: weight: Weight value unit_key: Unit of weight ('kg' or 'lb')
Inputs: weight (required), unit_key. Returns: result
add_weigh_in_with_timestampsC
Add a new weight measurement with specific timestamps
Args: weight: Weight value unit_key: Unit of weight ('kg' or 'lb') date_timestamp: Local timestamp in format YYYY-MM-DDThh:mm:ss gmt_timestamp: GMT timestamp in format YYYY-MM-DDThh:mm:ss
Inputs: weight (required), unit_key, gmt_timestamp, date_timestamp. Returns: result
count_activitiesA
Get total count of activities in the user's Garmin account
Returns the total number of activities recorded.
Inputs: no named parameters. Returns: result
create_custom_foodA
Create a custom food in the user's Garmin nutrition library
Creates a new food item with nutritional information per serving. On success the response includes foodId and servingId needed for log_custom_food. If the API returns no data (204), use get_custom_foods(search=food_name) to retrieve those IDs.
All nutrient amounts are ABSOLUTE values per serving, not %DV. Nutrition labels often print %DV for calcium/iron/vitamin D — convert to absolute units before passing.
Args: food_name: Name of the custom food (e.g. "Homemade Chocolate Cookies") calories: Calories per serving serving_unit: Unit for serving size (e.g. "G", "ML", "OZ"). Default "G" number_of_units: Serving size in the specified unit. Default 100 brand_name: Brand or vendor name (e.g. "Three Bridges") carbs: Carbohydrates in grams per serving protein: Protein in grams per serving fat: Total fat in grams per serving fiber: Fiber in grams per serving sugar: Sugar in grams per serving saturated_fat: Saturated fat in grams per serving sodium: Sodium in mg per serving cholesterol: Cholesterol in mg per serving potassium: Potassium in mg per serving trans_fat: Trans fat in grams per serving calcium: Calcium in mg per serving (NOT %DV) iron: Iron in mg per serving (NOT %DV) vitamin_d: Vitamin D in mcg per serving (NOT %DV)
Inputs: calories (required), food_name (required), fat, iron, +14 more. Returns: result
create_manual_activityA
Log a manual activity in Garmin Connect — useful for activities done without a watch.
The type_key must match a Garmin activity type. Use get_activity_types to see the full list. Common values: yoga, strength_training, meditation, indoor_cycling, pilates, bouldering, fitness_equipment.
Args: type_key: Activity type key (e.g. "yoga", "strength_training") date: Date of the activity in YYYY-MM-DD format duration_minutes: Duration of the activity in minutes start_time: Start time as HH:MM (24-hour, default 09:00) activity_name: Optional title; defaults to the type_key if not provided distance_km: Distance in kilometres (default 0.0 for non-distance activities) time_zone: IANA time zone for the activity (default UTC)
Inputs: date (required), type_key (required), duration_minutes (required), time_zone, +3 more. Returns: result
create_run_workoutA
Create a continuous run workout and upload it to Garmin Connect.
Builds a single uninterrupted run interval with warmup and cooldown walks.
Targets a named Garmin heart-rate zone by default. Named zones (Z1-Z5) don't line up with every real training target -- e.g. a 136-148 bpm Zone 2 goal straddles Garmin's Z2 (118-137) and Z3 (138-157). Pass hr_min and hr_max together to target that exact bpm range instead; the watch will then show "in range" only for the range you actually want, not a whole zone that over- or under-shoots it.
Args: name: Workout name (e.g. "Step 8 - 30min continuous") run_seconds: Duration of the run in seconds warmup_min: Warmup walk duration in minutes cooldown_min: Cooldown walk duration in minutes hr_zone: Target heart-rate zone (Z1-Z5, default Z3). Ignored if hr_min/hr_max are given. hr_min: Optional custom target heart rate range, minimum bpm (must be given with hr_max) hr_max: Optional custom target heart rate range, maximum bpm (must be given with hr_min)
Inputs: name (required), warmup_min (required), run_seconds (required), cooldown_min (required), +3 more. Returns: result
create_strength_workoutA
Create a strength workout and upload it to Garmin Connect.
Each exercise becomes a reps-based step; when sets > 1 the exercise is emitted as a repeat group of that many iterations (work + rest per set), so Garmin shows the real set count. The name is kept in the step description; it is also sent as exerciseName, which Garmin only retains when it matches one of its own exercise keys (e.g. "FARMERS_CARRY").
Args: name: Workout name exercises: List of dicts with keys: name, sets, reps, rest_seconds and an optional category. Category is omitted from the payload when not given; Garmin accepts that. When given it must be one of Garmin's exercise categories (e.g. SQUAT, DEADLIFT, PUSH_UP, CARRY, SLED) — anything else, including "UNASSIGNED" and "OTHER", is rejected with 400 Invalid category. Full list: https://connect.garmin.com/web-data/exercises/Exercises.json
Inputs: name (required), exercises (required). Returns: result
create_walk_run_workoutA
Create a walk/run interval workout and upload it to Garmin Connect.
Builds the internal Garmin JSON automatically and returns the new workout ID.
Args: name: Workout name (e.g. "W3 Mié 2:2") run_seconds: Duration of each run interval in seconds walk_seconds: Duration of each walk/recovery interval in seconds repeats: Number of run/walk repetitions warmup_min: Warmup duration in minutes cooldown_min: Cooldown duration in minutes hr_zone: Target heart-rate zone (Z1-Z5, default Z3)
Inputs: name (required), repeats (required), warmup_min (required), run_seconds (required), +3 more. Returns: result
create_z2_walk_workoutA
Create a steady Z2 walking workout and upload it to Garmin Connect.
Args: name: Workout name duration_min: Main walking block duration in minutes hr_min: Minimum heart rate in bpm (used for description; target is Z2) hr_max: Maximum heart rate in bpm (used for description; target is Z2)
Inputs: name (required), hr_max (required), hr_min (required), duration_min (required). Returns: result
delete_courseA
Delete a course from Garmin Connect.
Args: course_id: ID of the course to delete (get IDs from get_courses).
Inputs: course_id (required). Returns: result
delete_custom_foodA
Delete a custom food from the user's Garmin nutrition library
Permanently removes a custom food entry. The food must not be actively referenced in a logged meal to be deleted. Use get_custom_foods to find the foodId.
Args: food_id: ID of the custom food to delete — a 32-char hex string (from get_custom_foods or create_custom_food)
Inputs: food_id (required). Returns: result
delete_food_logA
Delete a food log entry
Permanently removes a logged food item from the nutrition log. Works for both QUICK_ADD and REGULAR_LOG entry types. Use get_nutrition_daily_food_log to find the logId and date.
Args: log_id: Log entry ID to delete — a 32-char hex UUID (from get_nutrition_daily_food_log) meal_date: Date of the log entry in YYYY-MM-DD format
Inputs: log_id (required), meal_date (required). Returns: result
delete_weigh_insB
Delete weight measurements for a specific date
Args: date: Date in YYYY-MM-DD format delete_all: Whether to delete all measurements for the day
Inputs: date (required), delete_all. Returns: result
delete_workoutA
Delete a workout from Garmin Connect
Permanently removes a workout from your Garmin Connect workout library.
Args: workout_id: ID of the workout to delete (get IDs from get_workouts)
Inputs: workout_id (required). Returns: result
delete_workoutsA
Delete multiple workouts from Garmin Connect in a single call
Permanently removes multiple workouts from your Garmin Connect workout library.
Args: workout_ids: List of workout IDs to delete (get IDs from get_workouts)
Inputs: workout_ids (required). Returns: result
download_activity_fileA
Download an activity and save it to disk as a file.
Saves the activity in the requested format. Defaults to the original .fit file; Garmin also supports gpx, tcx, and csv.
Directory resolution (first match wins):
output_dir argument (one-off; not persisted)
GARMIN_FIT_DOWNLOAD_DIR environment variable
persisted config (set via set_fit_download_dir) If none is configured, returns status "needs_setup" with a suggested default (the server's current working directory). In that case, ask the user where to save, call set_fit_download_dir(path), then call this tool again.
Files are named "{activity_id}.{ext}" and overwrite any existing file.
Args: activity_id: Garmin activity ID format: One of fit, gpx, tcx, csv (default fit) output_dir: Optional one-off directory override (not persisted)
Inputs: activity_id (required), format, output_dir. Returns: result
download_course_gpxA
Download the exact official GPX file for a Garmin Connect course.
Args: course_id: ID of the course to download. output_path: Optional local destination file or directory path. Defaults to GARMIN_FIT_DOWNLOAD_DIR or ./courses/{course_id}.gpx.
Inputs: course_id (required), output_path. Returns: result
download_workoutA
Download a workout as a FIT file
Downloads the workout in FIT format. The binary data cannot be returned directly through the MCP interface, but this confirms the workout is available.
Args: workout_id: ID of the workout to download
Inputs: workout_id (required). Returns: result
get_acclimationA
Get heat and altitude acclimation status for a given date.
Garmin tracks how adapted the athlete currently is to training in heat and at altitude. heat_acclimation_percent runs 0-100 and decays without continued exposure; use it to judge readiness for a warm-weather race.
heat_trend reports Garmin's own label (e.g. ACCLIMATIZED). The previous_* fields hold the prior reading so direction of travel is visible without a second call.
VO2 max is not returned here; use get_training_status or get_vo2max_trend.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_activitiesA
Get activities with pagination support.
Retrieves a paginated list of activities ordered newest-first. Use this for browsing through large activity lists when you do not need to filter by date range, or as a complement to get_activities_by_date.
Each activity includes an event_type field. Common values: "race", "training", "uncategorized" (no event type set by the user — common for Peloton imports and untagged runs). Filter for races with event_type == "race" rather than excluding "training", as many non-race activities appear as "uncategorized" rather than "training".
Args: start: Starting index (default 0) limit: Maximum number of activities to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_activities_by_dateA
Get activities between specified dates with pagination support.
For accounts with large activity histories, broad date ranges can return thousands of activities in a single response. Use page and page_size to retrieve activities in manageable chunks and avoid "result too large" errors. Activities are ordered newest-first.
Pagination: when has_more is true the response includes next_page — pass that value as page on the next call to retrieve the following page. Repeat until has_more is false.
Note: total_count for a date range is not available from the Garmin API without fetching all results. Use has_more / next_page to walk pages.
Each activity includes an event_type field with values such as:
"race" — explicitly tagged as a race by the user
"training" — explicitly tagged as a training activity
"uncategorized" — no event type set; common for Peloton imports and untagged outdoor runs. Distinct from "training": filter for races with event_type == "race" rather than excluding "training", since many non-race activities appear as "uncategorized" not "training"
field absent — API returned no eventType for this activity; not observed in practice in any activity back to 2012 (oldest activities sampled on this account)
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format activity_type: Optional activity type filter (e.g., cycling, running, swimming) page: Zero-based page number (default 0) page_size: Number of activities per page, max 200 (default 100)
Inputs: end_date (required), start_date (required), page, page_size, +1 more. Returns: result
get_activities_fordateA
Get activities for a specific date
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_activityA
Get detailed information for a single activity.
Returns a comprehensive summary including timing, distance, heart rate, elevation, training effect, and an event_type field. Common event_type values: "race", "training", "uncategorized" (no event type set by the user). The field is omitted for very old activities that pre-date event type support in the Garmin API.
Args: activity_id: ID of the activity to retrieve
Inputs: activity_id (required). Returns: result
get_activity_exercise_setsB
Get exercise sets for strength training activities
Args: activity_id: ID of the activity to retrieve exercise sets for
Inputs: activity_id (required). Returns: result
get_activity_fit_dataA
Download and parse FIT file for an activity to expose advanced cycling data.
Returns data not available through the standard REST API, including:
DI2 / electronic shifting events with cadence at time of shift, grade at shift, gear combinations, shift quality classification, and terrain-grouped shift analysis
Cycling dynamics per session and lap: platform center offset (PCO), left/right power balance, torque effectiveness, pedal smoothness
Variability Index (NP / avg_power) per session and lap
Climb detection with VAM (vertical ascent rate), avg power/cadence/HR per climb, and W/kg per climb (using auto-fetched body weight from Garmin)
Grade-correlated stats: avg power, cadence, HR broken down by terrain steepness
HR drift / cardiac drift coefficient (aerobic decoupling for rides ≥60 min)
Temperature correlation: avg HR/power in hottest vs. coolest portions of ride
Power Duration Curve: best mean maximal power at 5s, 30s, 1min, 5min, 10min, 20min, 60min
Optional full per-second time series when include_records=True
Shift quality:
proactive: shifted at 70-100 rpm (ideal cadence range)
reactive: shifted below 70 rpm (already grinding before shifting)
coasting: shifted at 0 rpm (mid-stop or freewheeling)
spun_out: shifted above 100 rpm (waited too long in easy gear)
Note: DI2 data requires Shimano Di2 / SRAM eTap. Cycling dynamics require a compatible power meter (e.g., Garmin Rally, Favero Assioma, PowerTap P1 pedals).
Args: activity_id: Garmin activity ID include_records: Include full per-second time series (default False). Warning: adds significant data volume for long rides.
Inputs: activity_id (required), include_records. Returns: result
get_activity_fit_messagesA
Retrieve and generically parse an activity's original FIT file.
This is the source-of-truth endpoint for original device activity data. It returns every message and every field emitted by the FIT decoder without applying sport-specific curation. Messages remain in file order, and each field includes its decoded value plus units and definition number when available. Unknown and duplicate fields are retained.
Garmin Connect edits made after upload may be stored only in Garmin's service and are not necessarily written back into the original FIT file. This tool intentionally does not merge those service-side edits.
High-frequency record messages are counted but omitted by default.
Other message types occurring more than 100 times are also inventoried
but omitted from an unfiltered default response. Request those types
explicitly with message_types. All returned messages are bounded by
message_offset/message_limit (maximum 5000 per call); follow
pagination.next_offset until absent to retrieve the full selected stream.
Use message_types to return only particular FIT message types while message_counts still inventories the complete file. For example, a strength workout can usually be inspected efficiently with ["session", "lap", "set", "exercise_title"]. Message type names are case-insensitive and should use FIT/fitparse snake_case names.
Args: activity_id: Garmin activity ID. message_types: Optional list of FIT message types to return. Omit to return low-frequency non-record message types. include_records: Include the high-frequency record stream (default false). message_offset: Zero-based offset within the selected message stream. message_limit: Maximum messages returned per call, 1-5000 (default 1000).
Inputs: activity_id (required), message_limit, message_types, message_offset, +1 more. Returns: result
get_activity_gearA
Get gear data used for an activity
Args: activity_id: ID of the activity to retrieve gear data for
Inputs: activity_id (required). Returns: result
get_activity_hr_in_timezonesB
Get heart rate data in different time zones for an activity
Args: activity_id: ID of the activity to retrieve heart rate time zone data for
Inputs: activity_id (required). Returns: result
get_activity_power_in_timezonesA
Get power distribution across training zones for an activity.
Returns time spent in each power zone with watt thresholds and duration. Requires a power meter. Zones are based on the athlete's FTP configured in Garmin Connect.
Args: activity_id: ID of the activity to retrieve power zone data for
Inputs: activity_id (required). Returns: result
get_activity_splitsC
Get splits for an activity
Args: activity_id: ID of the activity to retrieve splits for
Inputs: activity_id (required). Returns: result
get_activity_split_summariesC
Get split summaries for an activity
Args: activity_id: ID of the activity to retrieve split summaries for
Inputs: activity_id (required). Returns: result
get_activity_typed_splitsB
Get typed splits for an activity
Args: activity_id: ID of the activity to retrieve typed splits for
Inputs: activity_id (required). Returns: result
get_activity_typesA
Get all available activity types
Returns a list of all activity types supported by Garmin Connect, useful for filtering activities by type.
Inputs: no named parameters. Returns: result
get_activity_weatherA
Get weather data for an activity.
Garmin's weather endpoint returns temperatures in Fahrenheit (from the weather-station source) with no unit indicator, regardless of account settings. This tool converts them to the account's display unit: metric accounts get Celsius, statute_us accounts keep Fahrenheit. The temperature_unit field ("F" or "C") states which unit was returned.
Wind speed, unlike temperature, is already returned in the account's display unit (km/h for metric, mph for statute_us), so it is passed through unconverted and labeled via the wind_speed_unit field.
Args: activity_id: ID of the activity to retrieve weather data for
Inputs: activity_id (required). Returns: result
get_adhoc_challengesA
Get user-created social/group challenges (e.g., step competitions with friends)
Returns challenges created by users to compete with connections. These are different from official Garmin badge challenges.
Args: start: Starting index for pagination (default 0) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_all_day_eventsC
Get daily wellness events data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_all_day_stressB
Get all-day stress data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_available_badge_challengesA
Get official Garmin badge challenges available to join
Returns monthly/seasonal challenges from Garmin that the user can join. These challenges award badges and points upon completion.
Args: start: Starting index for pagination (starts at 1) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_badge_challengesA
Get all badge challenges the user has joined (completed and in-progress)
Returns the user's history of badge challenges including progress, completion status, and earned dates.
Args: start: Starting index for pagination (starts at 1) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_blood_pressureC
Get blood pressure data
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_body_batteryC
Get body battery data with events
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_body_battery_eventsC
Get body battery events data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_body_compositionA
Get body composition data for a single date or date range
Args: start_date: Date in YYYY-MM-DD format or start date if end_date provided end_date: Optional end date in YYYY-MM-DD format for date range
Inputs: start_date (required), end_date. Returns: result
get_calendar_eventsA
Get races and events on the Garmin Connect calendar between two dates
Returns calendar entries the user added or subscribed to in Garmin Connect, such as upcoming races. Use this to answer questions about which events or races are scheduled.
These entries are not returned by get_scheduled_workouts, which covers only workouts, nor by get_goals. This is the only tool that exposes them.
Each event reports its target distance in meters when Garmin stores one, the local start time when the organiser published it, and two flags: is_race marks the entry as a race rather than a general event, and primary_event marks the goal race that an active training plan targets.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_course_detailsA
Get full details of a Garmin Connect course by ID.
Returns course metadata, elevation gain/loss, total distance, and all custom course waypoints (shops, water, food, campgrounds, hazards).
Args: course_id: ID of the course (from get_courses).
Inputs: course_id (required). Returns: result
get_coursesA
List all courses saved on Garmin Connect.
Returns a curated list of courses with id, name, distance, activity type and creation date.
Inputs: no named parameters. Returns: result
get_custom_foodsA
Search or list user's custom foods
Returns custom foods the user has created. Use the search parameter to find existing foods by name before creating duplicates — the response includes foodId and servingId needed for log_custom_food.
For branded catalog foods (FatSecret), use search_foods instead.
Args: search: Search term to filter foods by name (default: list all) start: Starting index for pagination (default 0) limit: Maximum number of results (default 20)
Inputs: limit, start, search. Returns: result
get_custom_food_serving_unitsA
Get available serving units for custom foods
Returns the list of valid serving units (e.g. G, ML, OZ) that can be used when creating custom foods.
Inputs: no named parameters. Returns: result
get_cycling_ftpA
Get the latest cycling Functional Threshold Power (FTP) data.
Returns the most recent cycling FTP estimate available from Garmin.
Inputs: no named parameters. Returns: result
get_daily_stepsB
Get steps data for a date range
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_daily_weigh_insA
Get weight measurements for a specific date
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_device_alarmsA
Get alarms from all Garmin devices
Returns all configured alarms with their schedules, sounds, and enabled status.
Inputs: no named parameters. Returns: result
get_device_last_usedB
Get information about the last used Garmin device
Inputs: no named parameters. Returns: result
get_devicesA
Get all Garmin devices associated with the user account
Inputs: no named parameters. Returns: result
get_device_settingsA
Get settings for a specific Garmin device
Returns device configuration including time/date format, units, activity tracking settings, and alarm information.
Args: device_id: Device ID (optional; defaults to the most recently used device when omitted; can be obtained from get_devices or get_device_last_used)
Inputs: device_id. Returns: result
get_device_solar_dataA
Get solar data for a specific device
Returns solar charging data for devices with solar panels (e.g., Instinct Solar, Fenix Solar). Only applicable to solar-capable devices.
Args: device_id: Device ID (can be obtained from get_devices) date: Date in YYYY-MM-DD format
Inputs: date (required), device_id (required). Returns: result
get_earned_badgesB
Get earned badges for user
Inputs: no named parameters. Returns: result
get_endurance_scoreA
Get endurance score data between dates
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_fitnessage_dataB
Get fitness age data
Args: date: Date in YYYY-MM-DD format details: If True, include component breakdown (BMI, RHR, vigorous activity) with targets and improvement suggestions
Inputs: date (required), details. Returns: result
get_floorsA
Get floors climbed data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_full_nameA
Get user's full name from profile
Inputs: no named parameters. Returns: result
get_garmin_coach_workoutsA
Get Garmin Coach workouts around the given date
Returns workouts from the active Garmin Coach/training plan, including plan metadata, workout identifiers, dates, sport, duration, completion status, rest days, race days, and workout intent when Garmin provides them. Adaptive plans expose only Garmin's currently generated window, typically the current week; future dates may return no workouts even while a plan is active. The count includes rest-day entries.
Garmin's standalone Daily Suggested Workouts are generated on compatible devices. As of July 31, 2026, no supported or known Garmin Connect web/API endpoint, including those exposed by this project's python-garminconnect dependency, returns the device's upcoming DSW schedule. This tool returns Garmin Coach/training-plan workouts and does not synthesize device-generated suggestions.
This is the preferred tool for Garmin Coach requests. The legacy get_training_plan_workouts tool returns the same data; do not call both.
Adaptive Coach plans typically expose workout_uuid; other plan families may expose numeric workout_id. Pass whichever identifier is present to get_workout_by_id. Rest-day UUIDs may return minimal detail without workout segments.
Args: calendar_date: Reference date in YYYY-MM-DD format (returns week's workouts)
Inputs: calendar_date (required). Returns: result
get_gearA
Get all gear registered with the user account
Returns complete gear inventory including free-text notes, usage statistics, and default activity associations. The notes field is null when unavailable. No parameters required - user profile is fetched automatically.
Args: include_stats: Include usage statistics for each gear item (default True). Set to False for faster response with large gear collections.
Inputs: include_stats. Returns: result
get_goalsA
Get Garmin Connect goals (active, future, or past)
Args: goal_type: Type of goals to retrieve. Options: "active", "future", or "past"
Inputs: goal_type. Returns: result
get_heart_ratesA
Get full heart rate time-series data
Note: This returns detailed 2-minute interval data (~25KB). For a compact summary, use get_heart_rates_summary().
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_heart_rates_summaryA
Get heart rate summary with essential metrics (lightweight version)
Returns a compact summary (~500 bytes) instead of full time-series data (~25KB). Ideal for daily health checkups and LLM integrations.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_heart_rate_zonesA
Get the user's saved heart-rate training-zone configuration.
Garmin stores a generic DEFAULT profile plus optional sport-specific overrides such as RUNNING and CYCLING. With no sport, this returns every saved profile; pass a sport key to return just that profile.
Args: sport: Optional Garmin sport key (for example default, running, or cycling). "generic" is accepted as an alias for DEFAULT.
Inputs: sport. Returns: result
get_hill_scoreA
Get hill score data between dates
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_hrv_dataB
Get Heart Rate Variability (HRV) data
Args: date: Date in YYYY-MM-DD format return_timeseries: If True, include detailed 5-minute HRV readings (can be large)
Inputs: date (required), return_timeseries. Returns: result
get_hrv_trendA
Get HRV (Heart Rate Variability) trend over a date range.
Returns daily HRV values and weekly rolling averages. Single-day HRV is too noisy to act on — use this tool to identify baseline shifts that signal accumulated fatigue or recovery. A drop of >10ms from the 7-day baseline warrants reducing training load.
Recommended range: 7-21 days. Maximum: 30 days.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_hydration_dataB
Get hydration data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_inprogress_virtual_challengesA
Get in-progress virtual challenges/expeditions
Returns virtual challenges (like walking expeditions on famous trails) that the user is currently participating in.
Args: start: Starting index for pagination (default 1, must be >= 1; garminconnect 0.3.2 rejects 0 for this endpoint) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_lactate_thresholdA
Get lactate threshold data
Returns lactate threshold information, which is the exercise intensity at which lactate starts to accumulate in the blood. This is a key metric for endurance training.
Args: start_date: Start date in YYYY-MM-DD format (optional, omit for latest) end_date: End date in YYYY-MM-DD format (optional, omit for latest)
Inputs: end_date, start_date. Returns: result
get_lifestyle_logging_dataB
Get lifestyle logging data for a specific date
Returns lifestyle logging data which allows users to track behaviors and their impact on health metrics.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_menstrual_calendar_dataA
Get menstrual calendar data between specified dates
Automatically chunks requests longer than 92 days, Garmin's server-side limit, and stitches the responses together.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_menstrual_data_for_dateB
Get menstrual data for a specific date
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_morning_training_readinessA
Get morning training readiness score
Returns the morning training readiness assessment, which evaluates recovery status and readiness to train based on overnight metrics.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_non_completed_badge_challengesA
Get badge challenges currently in progress (not yet completed)
Returns active challenges the user has joined but hasn't completed yet. Useful for tracking current progress toward badge goals.
Args: start: Starting index for pagination (starts at 1) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_nutrition_daily_food_logA
Get daily food consumption records for a date
Returns food items logged throughout the day including calories, macronutrients, and meal associations.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_nutrition_daily_mealsA
Get daily meal summaries for a date
Returns meal-level summaries (breakfast, lunch, dinner, snacks) with nutritional totals for each meal. Each meal includes a mealId needed for logging food items to that meal.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_nutrition_daily_settingsA
Get nutrition plan/settings for a date
Returns the user's nutrition goals and targets including calorie targets, macronutrient goals, and plan configuration.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_personal_recordD
Get personal records for user
Inputs: no named parameters. Returns: result
get_power_duration_curveA
Get season-best Power Duration Curve across recent activities.
Downloads FIT files for recent cycling activities and computes best mean maximal power at each standard duration. Returns season bests with which activity and date each best came from.
Durations: 5s (sprint), 30s, 1min, 5min (VO2 max proxy), 10min, 20min (FTP proxy), 60min
Use the 20-minute best × 0.95 as a strong FTP estimate without a formal test.
Warning: downloads multiple FIT files — may take 30-60 seconds for 20 activities.
Args: num_activities: Number of recent activities to analyze (default 20, max 50) activity_type: Activity type to filter (default "cycling")
Inputs: activity_type, num_activities. Returns: result
get_pregnancy_summaryA
Get pregnancy summary data
Inputs: no named parameters. Returns: result
get_primary_training_deviceB
Get information about the primary training device
Returns details about the device designated as primary for training metrics, along with other wearable devices on the account.
Inputs: no named parameters. Returns: result
get_progress_summary_between_datesB
Get progress summary for a metric between dates
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format metric: Metric to get progress for (e.g., "elevationGain", "duration", "distance", "movingDuration")
Inputs: metric (required), end_date (required), start_date (required). Returns: result
get_race_predictionsA
Get predicted race times based on current fitness level
Returns Garmin's predictions for 5K, 10K, half marathon, and marathon finish times based on the user's recent training data and VO2 max.
Inputs: no named parameters. Returns: result
get_respiration_dataA
Get full respiration time-series data
Note: This returns detailed interval data (~20KB). For a compact summary, use get_respiration_summary().
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_respiration_summaryA
Get respiration summary with essential metrics (lightweight version)
Returns a compact summary (~300 bytes) instead of full time-series data (~20KB).
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_respiration_trendA
Get overnight respiration rate trend over a date range.
Elevated resting respiration rate (compared to personal baseline) is an early warning sign for overreaching, illness, or poor recovery. Use this alongside HRV trend for a complete recovery picture.
Recommended range: 7-21 days. Maximum: 30 days.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_rhr_dayC
Get resting heart rate data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_running_toleranceA
Get Running Tolerance for a single day.
Returns Garmin's running load capacity model: how much running load the
athlete can currently absorb (tolerance), the intensity-adjusted load
their recent runs have produced (acute load), and the raw distance behind
that load. All three are expressed in km so they're directly comparable —
load_ratio (acute_load_km / distance_km) quantifies how much intensity
is inflating the cost of each kilometer run.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_running_tolerance_trendA
Get Running Tolerance trend over a date range.
Running Tolerance moves slowly — its value is in the trajectory, not any single day. Returns, per period: tolerance_km (current load capacity), acute_load_km (intensity-adjusted load), distance_km (actual distance run), and load_ratio (acute_load_km / distance_km — how much intensity inflates the cost of each kilometer). Weekly aggregation (default) gives a compact multi-month view; daily gives day-to-day resolution for a shorter window.
Recommended range: 4-12 weeks. Maximum: 90 days for daily aggregation, 366 days for weekly (this endpoint returns the whole range in one call, so the limit protects output size, not request volume).
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format aggregation: "daily" or "weekly" (default "weekly")
Inputs: end_date (required), start_date (required), aggregation. Returns: result
get_scheduled_workoutsA
Get scheduled workouts between two dates with curated summary list
Returns workouts that have been scheduled on the Garmin Connect calendar, including their scheduled dates and completion status.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_sleep_dataA
Get full sleep data with all details
Note: This returns detailed sleep data (~50KB). For a compact summary, use get_sleep_summary().
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_sleep_summaryA
Get sleep summary with only essential metrics (lightweight version)
This endpoint returns a compact summary of sleep data (~350 bytes) instead of the full granular data (~50KB). Ideal for daily health checkups and LLM integrations where the full time-series data would overwhelm the context window.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_sleep_summary_rangeA
Get lightweight sleep summaries for every night in a date range.
Returns the same curated metrics as get_sleep_summary (sleep score, duration, sleep stages, HRV, resting HR, etc.) for each night between start_date and end_date, inclusive. Use this instead of calling get_sleep_summary once per night when analyzing sleep trends over weeks or months.
Note: Garmin Connect does not expose a native range endpoint for sleep, so this makes one request per night internally. Recommended range: up to a few weeks for quick checks. Maximum: 90 nights per call.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_spo2_dataB
Get SpO2 (blood oxygen) data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_statsA
Get daily activity stats with curated essential metrics
Returns a summary of daily health and activity data including steps, calories, heart rate, stress, body battery, and sleep metrics.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_stats_and_bodyB
Get stats and body composition data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_steps_dataA
Get detailed steps data with 15-minute intervals
Note: This returns full interval data (~14KB). For a compact summary, use get_stats() which includes total_steps.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_stress_dataA
Get full stress time-series data
Note: This returns detailed interval data (~35KB) including body battery. For a compact summary, use get_stress_summary().
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_stress_summaryA
Get stress summary with essential metrics (lightweight version)
Returns a compact summary (~400 bytes) instead of full time-series data (~35KB). Ideal for daily health checkups and LLM integrations.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_training_effectB
Get training effect data for a specific activity
Args: activity_id: ID of the activity to retrieve training effect for
Inputs: activity_id (required). Returns: result
get_training_load_balanceA
Get Garmin's Load Focus — the distribution of the trailing-month training load across Aerobic Low, Aerobic High, and Anaerobic intensity bands, plus the system's feedback phrase (e.g. AEROBIC_HIGH_SHORTAGE, BALANCED, ANAEROBIC_SHORTAGE).
Use this to assess whether the athlete's training mix is balanced or
deficient in a particular intensity band. Each band reports its load
alongside Garmin's target range; a status of "below", "within", or
"above" is computed from the load relative to that range.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_training_load_trendA
Get the Performance Management Chart (CTL/ATL/TSB) over a date range.
Returns Chronic Training Load (CTL, 42-day fitness), Acute Training Load (ATL, 7-day fatigue), Training Stress Balance (TSB = CTL - ATL, form/freshness), and Acute:Chronic Workload Ratio (ACWR) per day. Use this to assess whether the athlete is building fitness, peaking, or accumulating too much fatigue.
Recommended range: 4-8 weeks. Maximum: 90 days.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_training_plan_workoutsA
Compatibility alias for get_garmin_coach_workouts
Prefer get_garmin_coach_workouts for new requests. This legacy tool returns the same Garmin Coach/training-plan data; do not call both for one request. Adaptive plans expose only Garmin's currently generated window, typically the current week; future dates may return no workouts even while a plan is active.
Adaptive training plans typically expose workout_uuid; other plan families may expose numeric workout_id. Pass whichever identifier is present to get_workout_by_id. The returned count includes rest days.
Args: calendar_date: Reference date in YYYY-MM-DD format (returns week's workouts)
Inputs: calendar_date (required). Returns: result
get_training_readinessB
Get training readiness data with curated metrics
Returns training readiness score and contributing factors.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_training_statusB
Get training status with curated metrics
Returns comprehensive training status including load, VO2 max, recovery, and training readiness indicators.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_unit_systemA
Get user's preferred unit system from profile
Inputs: no named parameters. Returns: result
get_user_profileB
Get user profile information
Inputs: no named parameters. Returns: result
get_userprofile_settingsC
Get user profile settings
Inputs: no named parameters. Returns: result
get_user_summaryB
Get user summary data (compatible with garminconnect-ha)
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_vo2max_trendA
Get VO2 max trend over a date range.
Returns daily VO2 max estimates from Garmin's FirstBeat algorithm. Use this to track whether training is producing fitness gains over weeks or months. Flat or declining VO2 max over 4+ weeks suggests insufficient training stimulus or overreaching.
Note: VO2 max estimates are smoothed and update gradually — daily changes of <0.5 are within normal noise. Focus on the 4-6 week trend direction.
Garmin records a new VO2 max value only on days with a recompute (after an activity). Days in between carry the last known value forward and are marked with "carried_forward": true, matching the trend chart in Garmin Connect.
If historical values are unavailable, the current profile estimate is returned separately and is not represented as a historical trend point.
Recommended range: 4-12 weeks. Maximum: 90 days.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_weekly_intensity_minutesA
Get weekly intensity minutes data aggregates
Returns weekly intensity minutes (moderate and vigorous) for the specified number of weeks ending at end_date.
Args: end_date: End date in YYYY-MM-DD format weeks: Number of weeks to fetch (default 4, max 52)
Inputs: end_date (required), weeks. Returns: result
get_weekly_stepsA
Get weekly step data aggregates
Returns weekly step totals for the specified number of weeks ending at end_date.
Args: end_date: End date in YYYY-MM-DD format weeks: Number of weeks to fetch (default 4, max 52)
Inputs: end_date (required), weeks. Returns: result
get_weekly_stressC
Get weekly stress data aggregates
Returns weekly stress values for the specified number of weeks ending at end_date.
Args: end_date: End date in YYYY-MM-DD format weeks: Number of weeks to fetch (default 4, max 52)
Inputs: end_date (required), weeks. Returns: result
get_weigh_insA
Get weight measurements between specified dates
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_workout_by_idA
Get detailed information for a specific workout
Returns workout details including segments and step structure.
Accepts either:
Numeric workout ID (from get_workouts, get_scheduled_workouts, or training-plan families that expose workout_id)
Workout UUID (from adaptive Garmin Coach/training-plan workouts)
Rest-day UUIDs can resolve to a minimal record without a workout name or segments.
Args: workout_id: Workout ID (numeric) or UUID (for training plan workouts)
Inputs: workout_id (required). Returns: result
get_workoutsA
Get all workouts with curated summary list
Returns a count and list of workout summaries with essential metadata only. For detailed workout information including segments, use get_workout_by_id.
Inputs: no named parameters. Returns: result
log_custom_foodA
Log a food item to a meal on a date
Adds a food entry to the nutrition log. The meal is determined automatically by matching meal_time against each meal's startTime/endTime window; falls back to SNACKS if no window matches.
Food sources:
"GARMIN" (default): user's custom food library. Use get_custom_foods to find food_id and serving_id.
"FATSECRET": branded/catalog food from FatSecret. Use search_foods to find food_id and serving_id. Pass the source value from the search_foods result (e.g. "FATSECRET").
Garmin custom food IDs are 32-char hex UUIDs; FatSecret IDs are numeric strings (e.g. "4132350"). Passing the wrong source for a given food_id returns a 400 from Garmin.
Args: meal_date: Date in YYYY-MM-DD format meal_time: Time in HH:MM:SS format (e.g. "12:30:00", account timezone) food_id: Food ID from get_custom_foods (GARMIN) or search_foods (FATSECRET) serving_id: Serving ID from get_custom_foods or search_foods serving_qty: Number of servings (default 1) source: Food namespace — "GARMIN" (default) or "FATSECRET"
Inputs: food_id (required), meal_date (required), meal_time (required), serving_id (required), +2 more. Returns: result
log_foodA
Quick-add a food entry with macro values to the nutrition log
Logs food directly by name and macros without requiring a food ID. Uses Garmin's Quick Add feature. The meal is determined automatically by matching meal_time against each meal's startTime/endTime window; falls back to SNACKS if no window matches.
Args: meal_date: Date in YYYY-MM-DD format name: Display name for the food entry calories: Calories (kcal) carbs: Carbohydrates in grams protein: Protein in grams fat: Fat in grams meal_time: Time in HH:MM:SS format (account timezone)
Inputs: fat (required), name (required), carbs (required), protein (required), +3 more. Returns: result
remove_gear_from_activityA
Remove gear association from an activity
Unlinks a specific piece of gear from an activity.
Args: activity_id: ID of the activity gear_uuid: UUID of the gear to remove
Inputs: gear_uuid (required), activity_id (required). Returns: result
request_reloadC
Request reload of epoch data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
schedule_weekA
Schedule a list of workouts for the week in a single call.
Idempotent: if a workout is already scheduled for that date, it is reported as already scheduled and the POST is skipped (avoids duplicating calendar entries).
Args: week: List of dicts with keys: date (YYYY-MM-DD), workout_id (int)
Inputs: week (required). Returns: result
schedule_workoutA
Schedule a workout to a specific calendar date
This adds an existing workout from your Garmin workout library to your Garmin Connect calendar on the specified date.
Idempotent: if the workout is already scheduled for that date, this is a no-op that reports success without creating a duplicate entry.
Args: workout_id: ID of the workout to schedule (get IDs from get_workouts) calendar_date: Date to schedule the workout in YYYY-MM-DD format
Inputs: workout_id (required), calendar_date (required). Returns: result
schedule_workoutsA
Schedule multiple workouts to specific calendar dates
This adds workouts to your Garmin Connect calendar in a single call. Each item can either reference an existing workout by ID, or provide inline workout_data to upload-and-schedule in one step.
Args: schedules: List of workout schedules, each with: - calendar_date (str): Date to schedule the workout in YYYY-MM-DD format (required) - workout_id (int): ID of an existing workout to schedule (required unless workout_data is provided) - workout_data (dict): Inline workout JSON to upload first, then schedule (optional). When provided, workout_id is not required. Uses the same structure and target-value rules as upload_workout.
Examples: Schedule existing workouts by ID: [{"workout_id": 123456, "calendar_date": "2024-01-15"}, {"workout_id": 789012, "calendar_date": "2024-01-17"}]
Upload and schedule inline:
[{"calendar_date": "2024-01-15", "workout_data": {"workoutName": "Easy Run", ...}},
{"workout_id": 789012, "calendar_date": "2024-01-17"}]
Inputs: schedules (required). Returns: result
search_foodsA
Search Garmin's general food catalog (FatSecret + Garmin custom foods)
Searches across the entire food catalog including FatSecret-sourced branded and generic foods, not just the user's Garmin custom foods. Use this to find branded packaged foods by name before logging them.
Returns food_id, source, name, brand, and all available servings with macros. The source field ("FATSECRET" or "GARMIN") and food_id together identify the right routing for log_custom_food — pass both to log_custom_food's food_id and source parameters respectively.
For the user's own custom foods only, use get_custom_foods instead.
Args: query: Food name or brand to search for (e.g. "Cheerios", "Greek yogurt") start: Starting index for pagination (default 0) limit: Maximum number of results per page (default 20)
Inputs: query (required), limit, start. Returns: result
set_activity_descriptionA
Set or update the free-text description (notes) of an activity.
This is the notes field shown on the activity page — useful for recording how a session felt, kit used, conditions, niggles, etc. Pass an empty string to clear an existing description.
Args: activity_id: ID of the activity to update description: New description text (empty string clears it)
Inputs: activity_id (required), description (required). Returns: result
set_activity_event_typeA
Set the event type of an activity.
Event type categorises the activity's purpose. Valid keys: race, recreation, specialEvent, training, transportation, touring, geocaching, fitness, uncategorized.
Args: activity_id: ID of the activity to update event_type: Target event type key (e.g. 'race', 'training')
Inputs: event_type (required), activity_id (required). Returns: result
set_activity_feelA
Set how an activity felt ('How did you feel?').
Mirrors Garmin Connect's 5-point feel rating, stored as one of: 0 = very tired / poor 25 = tired 50 = normal 75 = good 100 = strong Higher is better.
Args: activity_id: ID of the activity to update feel: One of 0, 25, 50, 75, 100
Inputs: feel (required), activity_id (required). Returns: result
set_activity_nameA
Set or update the name of an activity.
Args: activity_id: ID of the activity to update activity_name: New activity name
Inputs: activity_id (required), activity_name (required). Returns: result
set_activity_typeA
Change the activity type (sport) of an activity.
Useful for reclassifying a mislabelled activity, e.g. flipping a run logged as 'trail_running' to 'running', or a 'treadmill_running' walk to 'treadmill_walking'. Call get_activity_types to see all valid type keys.
Args: activity_id: ID of the activity to update type_key: Target activity type key (e.g. 'running', 'trail_running', 'treadmill_running', 'cycling', 'lap_swimming')
Inputs: type_key (required), activity_id (required). Returns: result
set_blood_pressureC
Set blood pressure values
Args: systolic: Systolic pressure (top number) diastolic: Diastolic pressure (bottom number) pulse: Pulse rate notes: Optional notes
Inputs: pulse (required), systolic (required), diastolic (required), notes. Returns: result
set_fit_download_dirA
Set and persist the default directory for downloaded activity files.
Stores the absolute path in a small JSON config file (~/.garminconnect_fit_config.json, overridable via GARMIN_FIT_CONFIG) so download_activity_file can save files without asking again.
Args: path: Directory where activity files (.fit/.gpx/.tcx/.csv) are saved. Pass the current working directory to keep files where the server runs.
Inputs: path (required). Returns: result
set_heart_rate_zonesA
Set an account-level heart-rate training-zone configuration for one sport.
WARNING: This mutates the user's Garmin Connect account configuration. Garmin stores zones per sport, so DEFAULT (the generic fallback), RUNNING, CYCLING, and other sport profiles are independent. Only the requested sport is written; omitted values are read from that sport's current configuration and preserved.
This change affects future activity recording and zone-based training. It does NOT retroactively re-slice already-recorded activities: their heart-rate zone boundaries were baked in when those activities were recorded.
The calculation method may be max_hr (% maximum heart rate), hrr or karvonen (% heart-rate reserve), lthr (% lactate-threshold heart rate), or custom_bpm. Manual zone_boundaries are only accepted with the custom_bpm method. Garmin's API does not persist a separate CUSTOM enum: direct-BPM zones are sent and read back with trainingMethod=HR_MAX while the explicit zone floors remain authoritative.
The tool performs a read-modify-write and then re-fetches the requested sport. Its result is the configuration Garmin actually saved, not the request payload.
Args: sport: Garmin sport key, e.g. default/generic, running, or cycling. max_hr: Maximum heart rate in BPM. resting_hr: Resting heart rate in BPM. Supplying it disables Garmin's resting-HR auto-update for this zone profile. lactate_threshold_hr: Lactate-threshold heart rate (LTHR) in BPM. calculation_method: max_hr, hrr/karvonen, lthr, or custom_bpm. zone_boundaries: Five strictly increasing BPM floors [Z1, Z2, Z3, Z4, Z5]. Every floor must be at most max_hr.
Inputs: sport (required), max_hr, resting_hr, zone_boundaries, +2 more. Returns: result
set_nutrition_daily_settingsA
Update daily nutrition goals (calorie target and macronutrient targets).
Reads the current settings for the date, applies the supplied overrides, and writes the merged result back. Only the fields you provide are changed; omitted fields keep their existing values.
Garmin stores macros as grams. The calorie goal should match 4carbs + 4protein + 9*fat to within a small rounding margin — Garmin accepts minor mismatches but will silently correct large discrepancies.
Args: date: Date in YYYY-MM-DD format (settings are typically set once and inherited across days, but Garmin accepts per-day overrides) calorie_goal: Daily calorie target in kcal carbs_grams: Daily carbohydrate target in grams fat_grams: Daily fat target in grams protein_grams: Daily protein target in grams
Inputs: date (required), fat_grams, carbs_grams, calorie_goal, +1 more. Returns: result
set_perceived_effortA
Set the perceived effort (RPE) for an activity.
Mirrors Garmin Connect's 'Perceived Effort' rating on a 0-10 scale, where 0 clears the rating. Internally Garmin stores this multiplied by 10 (so RPE 7 is stored as 70); this tool handles the conversion.
Args: activity_id: ID of the activity to update rpe: Perceived effort from 0 to 10 (0 clears the rating)
Inputs: rpe (required), activity_id (required). Returns: result
unschedule_workoutA
Remove a scheduled workout from the Garmin Connect calendar
Deletes a calendar entry without deleting the underlying workout template — the workout stays in your library and can be re-scheduled.
IMPORTANT: scheduled_workout_id is the calendar-entry id, which is different from the workout's id. Get it from get_scheduled_workouts (the "scheduled_workout_id" field), not from get_workouts.
Note: the scheduled-workouts listing is an eventually-consistent index. If you just scheduled this workout, allow a moment before unscheduling so the id is available.
Args: scheduled_workout_id: Calendar-entry id from get_scheduled_workouts
Inputs: scheduled_workout_id (required). Returns: result
unschedule_workoutsA
Remove multiple scheduled workouts from the Garmin Connect calendar
Deletes multiple calendar entries in a single call. The underlying workout templates are left intact in your library.
IMPORTANT: each id is a calendar-entry id (the "scheduled_workout_id" field from get_scheduled_workouts), not a workout id.
Args: scheduled_workout_ids: List of calendar-entry ids from get_scheduled_workouts
Inputs: scheduled_workout_ids (required). Returns: result
update_custom_foodA
Update an existing custom food in the user's Garmin nutrition library
Fetches the food's current record before writing so that omitted optional fields (brand, carbs, protein, fat, micros, etc.) preserve their existing values rather than being cleared. Only the fields you explicitly pass are changed; everything else is carried forward from the current record.
All nutrient amounts are ABSOLUTE values per serving, not %DV. Nutrition labels often print %DV for calcium/iron/vitamin D — convert to absolute units before passing.
Use get_custom_foods first to find the foodId and servingId.
Args: food_id: ID of the custom food to update (from get_custom_foods) serving_id: Serving ID of the food (from get_custom_foods) food_name: Name of the custom food calories: Calories per serving serving_unit: Unit for serving size (e.g. "G", "ML", "OZ"). Default "G" number_of_units: Serving size in the specified unit. Default 100 brand_name: Brand or vendor name; omit to preserve the existing value carbs: Carbohydrates in grams per serving protein: Protein in grams per serving fat: Total fat in grams per serving fiber: Fiber in grams per serving sugar: Sugar in grams per serving saturated_fat: Saturated fat in grams per serving sodium: Sodium in mg per serving cholesterol: Cholesterol in mg per serving potassium: Potassium in mg per serving trans_fat: Trans fat in grams per serving calcium: Calcium in mg per serving (NOT %DV) iron: Iron in mg per serving (NOT %DV) vitamin_d: Vitamin D in mcg per serving (NOT %DV)
Inputs: food_id (required), calories (required), food_name (required), serving_id (required), +16 more. Returns: result
upload_courseA
Upload a GPX file as a Garmin Connect Course.
The course can then be loaded onto the watch (sync or "Send to Device") and used as a navigation course or to build a PacePro strategy.
Args: gpx_path: Absolute path to the .gpx file on disk. course_name: Override the course name. Defaults to the name parsed from the GPX file. activity_type: One of running, cycling, hiking, walking, trail_running, mountain_biking, road_biking, gravel_cycling. Defaults to running. description: Optional description shown on the course detail page.
Inputs: gpx_path (required), course_name, description, activity_type. Returns: result
upload_workoutA
Upload a workout from JSON data
Creates a new workout in Garmin Connect from structured workout data.
IMPORTANT: Step types must use Garmin's DTO format:
Use "ExecutableStepDTO" for regular steps (warmup, interval, cooldown, recovery)
Use "RepeatGroupDTO" for repeat/interval groups with numberOfIterations. Always include endCondition with conditionTypeId 7 and conditionTypeKey "iterations"; omitting conditionTypeId causes the API to silently corrupt the repeat count.
IMPORTANT: Heart rate targets come in two forms:
Named zone (e.g. Zone 2): set targetType to "heart.rate.zone" and use "zoneNumber" (1-5). Do NOT put the zone number in targetValueOne.
Custom HR range (e.g. 105-143 bpm): set targetType to "heart.rate.zone" and use "targetValueOne" (low bpm) / "targetValueTwo" (high bpm). Do NOT set "zoneNumber". This matches Garmin Connect's "Custom" heart rate target. For non-HR targets (pace, power, cadence), use targetValueOne/targetValueTwo directly. Target values are fields on the workout step, alongside targetType; do not put targetValueOne, targetValueTwo, or zoneNumber inside the targetType object. Use either zoneNumber or targetValueOne/targetValueTwo, not both. Garmin silently discards a custom range when a named zone is also present.
Note: a safety check converts targetValueOne 1-5 to zoneNumber when zoneNumber is missing, to catch the common mistake of putting a zone index in targetValueOne. Typical bpm values (e.g. 105, 143) are not affected.
IMPORTANT: Target type IDs and keys must match Garmin's canonical mapping. Garmin treats workoutTargetTypeId as authoritative, so mismatches are rejected before upload. Known mappings:
workoutTargetTypeId 1 -> "no.target"
workoutTargetTypeId 2 -> "power.zone" (cycling power zone 1-7, use zoneNumber)
workoutTargetTypeId 4 -> "heart.rate.zone"
workoutTargetTypeId 6 -> "pace.zone" (running/swim) OR "power.between" (cycling)
IMPORTANT: For cycling power targets use the correct target type:
Power zone (zone 1-7 based on FTP %): use workoutTargetTypeId 2, key "power.zone", and "zoneNumber" (1-7).
Absolute watt range (e.g. 200-250 W): use workoutTargetTypeId 6, key "power.between", and "targetValueOne" (low watts) / "targetValueTwo" (high watts). Using workoutTargetTypeId 2 with key "power.between" is a silent Garmin bug: the workout uploads but Garmin stores it as "power.zone" and the intent is lost.
Use {"workoutTargetTypeId": 4, "workoutTargetTypeKey": "heart.rate.zone"} with targetValueOne/targetValueTwo for custom heart-rate ranges.
IMPORTANT: Sport type IDs for workouts (different from activity API!):
1 = running, 2 = cycling, 5 = strength_training, 6 = cardio, 11 = walking
IMPORTANT: End condition IDs and keys must match Garmin's canonical mapping. Garmin treats conditionTypeId as authoritative, so mismatches such as {"conditionTypeId": 4, "conditionTypeKey": "heart.rate"} are rejected before upload because Garmin would interpret them as "calories". Use {"conditionTypeId": 6, "conditionTypeKey": "heart.rate"} for heart-rate end conditions.
Available Templates: Instead of building workout JSON from scratch, you can use these MCP resources as starting points:
workout://templates/simple-run - Basic warmup/run/cooldown structure
workout://templates/interval-running - Interval training with repeat groups
workout://templates/tempo-run - Tempo run with heart rate zone targets
workout://templates/strength-circuit - Strength training with exercises, reps, rest
workout://reference/structure - Complete JSON structure reference with all fields
Access these resources using your MCP client's resource reading capability, modify the template as needed, and pass the resulting JSON as the workout_data parameter.
Strength training workouts require these additional fields on each exercise step:
"category": exercise category (e.g. "BENCH_PRESS", "PULL_UP", "CURL", "SHOULDER_PRESS", "ROW", "SQUAT", "DEADLIFT", "TRICEPS_EXTENSION", "PLANK", "LUNGE", "CARDIO")
"exerciseName": specific exercise (e.g. "BARBELL_BENCH_PRESS", "PULL_UP", "DUMBBELL_BICEPS_CURL", "DUMBBELL_SHOULDER_PRESS", "BENT_OVER_ROW_WITH_DUMBELL", "BODY_WEIGHT_DIP", "BARBELL_SQUAT", "BARBELL_DEADLIFT")
"weightValue" (optional): weight as number (e.g. 24.0)
"weightUnit" (optional): {"unitId": 8, "unitKey": "kilogram", "factor": 1000.0} Use endCondition reps (conditionTypeId: 10) for exercises, rest (stepTypeId: 5) between sets.
Example strength exercise step: { "type": "ExecutableStepDTO", "stepOrder": 1, "stepType": {"stepTypeId": 3, "stepTypeKey": "interval"}, "endCondition": {"conditionTypeId": 10, "conditionTypeKey": "reps"}, "endConditionValue": 10.0, "targetType": {"workoutTargetTypeId": 1, "workoutTargetTypeKey": "no.target"}, "category": "BENCH_PRESS", "exerciseName": "BARBELL_BENCH_PRESS", "weightValue": 60.0, "weightUnit": {"unitId": 8, "unitKey": "kilogram", "factor": 1000.0} }
Example running workout with HR zone target: { "workoutName": "My Workout", "sportType": {"sportTypeId": 1, "sportTypeKey": "running"}, "workoutSegments": [{ "segmentOrder": 1, "sportType": {"sportTypeId": 1, "sportTypeKey": "running"}, "workoutSteps": [{ "type": "ExecutableStepDTO", "stepOrder": 1, "stepType": {"stepTypeId": 3, "stepTypeKey": "interval"}, "endCondition": {"conditionTypeId": 2, "conditionTypeKey": "time"}, "endConditionValue": 1200.0, "targetType": {"workoutTargetTypeId": 4, "workoutTargetTypeKey": "heart.rate.zone"}, "zoneNumber": 3 }] }] }
Args: workout_data: Dictionary containing workout structure (name, sport type, segments, etc.)
Inputs: workout_data (required). Returns: result
upload_workoutsA
Upload multiple workouts from JSON data in a single call
Creates multiple new workouts in Garmin Connect. Each item in the list uses the same structure as upload_workout.
IMPORTANT: Step types must use Garmin's DTO format:
Use "ExecutableStepDTO" for regular steps (warmup, interval, cooldown, recovery)
Use "RepeatGroupDTO" for repeat/interval groups with numberOfIterations. Always include endCondition with conditionTypeId 7 and conditionTypeKey "iterations"; omitting conditionTypeId causes the API to silently corrupt the repeat count.
IMPORTANT: For named heart rate zone targets, use "zoneNumber" (1-5), NOT targetValueOne/targetValueTwo. For custom heart-rate ranges, use targetType {"workoutTargetTypeId": 4, "workoutTargetTypeKey": "heart.rate.zone"} with targetValueOne/targetValueTwo. Target values belong on the workout step, alongside targetType, not inside it. For cycling power zone targets (zone-based), use workoutTargetTypeId 2, key "power.zone". For cycling absolute watt range targets, use workoutTargetTypeId 6, key "power.between", with targetValueOne (low watts) and targetValueTwo (high watts). Target type IDs and keys must match Garmin's canonical mapping.
IMPORTANT: End condition IDs and keys must match Garmin's canonical mapping. Garmin treats conditionTypeId as authoritative, so mismatches are rejected before upload.
Args: workouts: List of workout dictionaries, each containing workout structure (name, sport type, segments, etc.) — same format as upload_workout.
Inputs: workouts (required). Returns: result
upsert_and_logA
Find-or-create a custom food then log it in one step
Searches the user's custom food library for food_name. If found, logs it immediately. If not found, creates it with the provided nutrition data and then logs it. This avoids duplicate food entries and removes the need for separate search → create → log round-trips.
Args: meal_date: Date in YYYY-MM-DD format meal_time: Time in HH:MM:SS format (account timezone); used to determine the meal automatically food_name: Name of the food to find or create calories: Calories per serving carbs: Carbohydrates in grams per serving protein: Protein in grams per serving fat: Total fat in grams per serving serving_unit: Unit for serving size (e.g. "G", "ML", "OZ"). Default "G" number_of_units: Serving size in the specified unit. Default 100 serving_qty: Number of servings to log (default 1)
Inputs: calories (required), food_name (required), meal_date (required), meal_time (required), +6 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 148 tools
With 148 tools spanning the same health/activity domains, boundaries blur: get_stats, get_user_summary, and get_stats_and_body all return daily summaries; get_activity_splits, get_activity_typed_splits, and get_activity_split_summaries have vaguely differentiated purposes; get_training_plan_workouts is explicitly a duplicate alias for get_garmin_coach_workouts. Some full-vs-summary pairs are well-noteed, but many near-duplicates force an agent to parse fine print to select correctly.
The overwhhelming majority follow a consistent snake_case verb_noun pattern (get_, set_, add_, delete_, create_, schedule_, upload_, download_). Minor deviations stand out: get_activities_fordate (missing underscore), get_userprofile_settings (missing underscore), get_weigh_ins vs add_weigh_in (pluralization inconsistency), and abbreviation mismatches like get_rhr_day vs get_heart_rates.
148 tools is extreme by any measure and far beyond what a coherent surface needs. Much of the bloat comes from redundant singular/plural pairs, summary/full variants that could be parameterized, four specialty create_*_workout builders alongside generic upload_workout, and an explicit legacy alias tool.
Coverage across the Garmin Connect domain is genuinely extensive: activities, workouts (upload/list/schedule/delete/download), courses, nutrition (food CRUD + logging), gear, devices, health metrics, and training analytics all have solid read and mostly write access. Notable gaps are deleting activities and goals/challenges/badges being strictly read-only, but these are workaroundable and the surface rarely dead-ends.
Maintenance
Related MCP Connectors
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Pace is a remote MCP server that exposes wearable and fitness data to Claude via the Model Context Protocol. It connects to Garmin, Oura, Whoop, Polar, Fitbit and 20+ devices and provides 15 tools for querying sleep, activity, recovery, and training data. Hosted on Google Cloud Run, OAuth 2.1 authentication, Streamable HTTP transport. Instructions: First you need to create an account at: https://pacetraining.co and connect your wearables. After that you can connect the remote Server via Custom Connector in Claude and OAuth 2.1 Flow startet.
Log meals, water, and weight to Garmin Connect from Claude or ChatGPT.
tri-train is a training platform for triathletes that syncs activities from Strava and Garmin and exposes them to Claude through a remote MCP server. Ask Claude how your last block went, why your run pace dropped, or whether you are fresh enough for Sunday's race, and it answers from your real data: activities with laps and grade-adjusted pace, weekly load and the performance management chart (CTL/ATL/TSB), sleep, HRV and resting heart rate, and your race calendar with predictions. Claude can also act: create and adjust planned workouts with structured intervals, push them to your Garmin watch, add races and write notes. Every change is journaled and can be undone. Read and write access are separate OAuth scopes, and the server enforces them on every call. Built and hosted in Switzerland by a triathlete; data stays on our own servers (Swiss revDSG / GDPR).
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceConnects to Garmin Connect and exposes fitness and health data (activities, steps, heart rate, sleep, body composition) to MCP-compatible clients like Claude.MIT
- AlicenseNot gradedqualityDmaintenanceThis Model Context Protocol (MCP) server connects to Garmin Connect and exposes your fitness and health data to Claude and other MCP-compatible clients.1MIT
- AlicenseNot gradedqualityDmaintenanceConnects to Garmin Connect and exposes your fitness and health data to OpenWebUI, Claude, or any MCP-compatible client via Streamable HTTP transport.7MIT
- AlicenseBqualityDmaintenanceExposes personal Garmin Connect data to MCP-capable clients like Claude and Gemini. Enables querying daily stats, heart rate, sleep, activities, and managing workouts.16MIT