Likenttt/garmin-connect-plugin-for-dsh ↗★ 21
dsh-plugin-garmin-connect
A TypeScript-based Garmin Connect plugin and MCP server with secure browser-based MFA for DeepSeek Harness and other AI agents 适合需要通过AI Agent安全接入并管理佳明(Garmin Connect)运动健康数据的用户。
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:Likenttt/garmin-connect-plugin-for-dshREADME
Read the full README ↗dsh-plugin-garmin-connect
A TypeScript-based Garmin Connect plugin and MCP server with secure browser-based MFA, built for DeepSeek Harness and designed to work with other AI agents.
English | 简体中文 | Test Report | Changelog

Works with mainstream coding agents and AI workspaces
DeepSeek Harness · WorkBuddy · Qwen Work · ZCode · Claude Code · Codex · Cursor · Windsurf
Connect through MCP or a SKILL.md workflow, depending on the client.
[!NOTE] MFA support in 0.1.6: local dsh Web, the
garmin-connect-auth servesystem-browser flow, and MCP URL elicitation can bootstrap the same owner-only session for China and International accounts. Real-account MFA completed in both regions on 2026-08-29, including a full China-region browser-to-session-and-read chain and an International-region system-browser DI exchange with owner-only session persistence. Browser policy may still block an individual flow. The olderlogin --browsercommand is retained only as a diagnostic.
More Apps
| Icon | App | What it does |
|---|---|---|
| [ |
![]()
](https://wristtale.com) | WristTale | Read TXT and Markdown on your Garmin watch | | [
![]()
](https://jiake.app) | JiaKe.app | Turn Garmin screenshots into polished assets | | [
![]()
](https://gamerasnap.com) | GameraSnap | Control your phone camera from your Garmin watch | | [
](https://wristalbum.wristtale.com) | WristAlbum | Keep a private photo album on your Garmin | | [
![]()
](https://wristpass.li2niu.com) | WristPass | Keep cards and tickets ready on your wrist | | [
![]()
](https://2fa4g.li2niu.com) | 2FA4G | Keep offline 2FA codes on your Garmin |
What It Does
This plugin connects DeepSeek Harness or any MCP-compatible AI agent to Garmin Connect, exposing your wearable data as AI-callable tools. Once installed, the agent can automatically query your activities, sleep, steps, and heart rate to provide personalized fitness insights — all through natural language.
Registered Tools
The plugin registers 10 tools. Eight return Garmin data without writing;
download_garmin_activity_fit writes one local file on the MCP/dsh host, and
create_garmin_workout changes the user's Garmin workout library.
| Tool | Description | Example Args |
|---|---|---|
get_garmin_activities | Fetch recent activities (runs, rides, swims…) with compact or full detail | {"limit": 5, "detail": "compact"} |
get_garmin_sleep | Sleep score, duration, and stage breakdown for a date or range | {"startDate": "2023-10-01", "endDate": "2023-10-02"} |
get_garmin_steps | Step totals for a date or range; goal/distance appear only when Garmin supplies them | {"startDate": "2023-10-01"} |
get_garmin_heart_rate | Resting, max, and min heart rate for a date or range | {"startDate": "2023-10-01", "endDate": "2023-10-02"} |
get_garmin_weight | Body composition (weight, BMI, body fat %, muscle mass, etc.) for a date or range | {"startDate": "2023-10-01"} |
get_garmin_workouts | Workout templates from your Garmin workout library (not calendar scheduling) | {"limit": 10, "offset": 0} |
get_garmin_profile | User profile summary | {} or omit |
get_running_skill_advice | Explain 8 workout types and 4 training philosophies, or collect the mandatory intake for personalized coaching | {"mode": "explain", "query": "Daniels", "language": "en"} |
download_garmin_activity_fit | Download an activity's original archive and safely extract its single FIT file to the account directory under the configured host parent | {"activityId": 123456789} |
create_garmin_workout | Preview a structured workout; create it only after explicit confirmation | {"name": "Threshold 3×8min", "steps": [...]} |
Workout creation is a two-call flow. The preview response includes a one-time
confirmationId; after the user approves the unchanged preview, call the tool
again with the same definition, confirmed: true, and that confirmationId.
An ID expires after 10 minutes and cannot be reused.
Personalized running coaching
get_running_skill_advice deliberately separates explanation from planning:
mode: "explain"explains a workout type or training philosophy. It does not invent an athlete-specific schedule.mode: "personalized"is required for any recommendation or plan. Before it returns planning material, the tool requires answers for all six intake areas below. Missing answers are returned as focused questions; Garmin activity data is not fetched and a schedule must not be generated yet.
| Intake field | What the assistant must ask |
|---|---|
goal | Target distance/event, future ISO YYYY-MM-DD date, and completion or ideal/minimum time goal |
currentPerformance + performanceBasis | A representative race/time trial from the past two years, result, non-future date, effort/conditions, or an explicit no_recent_benchmark |
trainingBackground | Running history and recent 4–8 week volume, frequency, long run, quality work, and interruptions |
availability | Available days/time, fixed rest and long-run days, terrain/facility limits, strength-training time, and whether double days are possible |
healthConstraints + hasWarningSymptoms | Current/past-year injury, pain, relevant disease/medication, sleep and recovery, plus an explicit warning-symptom boolean |
trainingPreference + preference details | steady, hard_easy, or mixed, plus maxQualitySessionsPerWeek (0–7) and intensityGuidancePreference (pace, heart_rate, rpe, or mixed) |
If hasWarningSymptoms is true—for example current chest discomfort,
unusual breathlessness with mild activity, fainting/dizziness, or abnormal
palpitations—the tool returns a safety stop without workout material or Garmin
activity access. It advises medical clearance and does not diagnose. If
performanceBasis is no_recent_benchmark, planning material instructs the
assistant to begin with easy base work or a low-risk benchmark instead of
inventing precise threshold or interval paces.
The compact philosophy layer contains:
- Hansons — frequent, more evenly distributed mileage, pace discipline, and cumulative fatigue; its 16-mile long run is not a standalone prescription.
- Jack Daniels — derive VDOT and E/M/T/I/R intensity from current, recent performance, never from the goal time.
- Norwegian threshold — borrow controlled, non-exhaustive threshold work and hard/easy separation; double-threshold days are not prescribed by default.
- Polarized training — keep most work genuinely easy and a small amount clearly hard; 80/20 is a direction rather than an exact quota.
Recent Garmin runs may supplement this intake but never replace the athlete's
answers. The method notes and evidence boundaries are summarized in
the training-method research note.
Each philosophy and workout-card output labels its statements as
system_principle, research_evidence, or application_inference so a method
definition or coaching inference is not misrepresented as comparative proof.
Quick Start
1. Install this plugin — from the npm registry (recommended)
npx --legacy-peer-deps=false @deepseek-ai/dsh plugin --profile web add dsh-plugin-garmin-connect
This single command installs the dependency and activates the plugin layer — the first run automatically initializes the web profile. You only need pnpm on your PATH:
npm install -g pnpm
--legacy-peer-deps=falsemakes npm resolve peer dependencies normally. If your npm config haslegacy-peer-deps=true(it skips peer packages), dsh would fail to boot withERR_MODULE_NOT_FOUND: Cannot find package '@deepseek-ai/cordis-plugin-group'. On machines without that setting the flag is a harmless no-op.
Verify the plugin layer is composed without booting:
npx --legacy-peer-deps=false @deepseek-ai/dsh --profile web --dump-config | grep -A 2 garmin-connect
Other install sources:
# Local checkout (development)
cd garmin-connect-plugin-for-dsh && npm install
npx --legacy-peer-deps=false @deepseek-ai/dsh plugin --profile web add .
# GitHub source install
npx --legacy-peer-deps=false @deepseek-ai/dsh plugin --profile web add github:/
2. Install the Harness CLI (if you haven't already)
npx --legacy-peer-deps=false @deepseek-ai/dsh web
The web UI starts at http://127.0.0.1:3080 by default. If you launch Harness via npx, keep using the same prefix for the commands below (npx --legacy-peer-deps=false @deepseek-ai/dsh …); if you have dsh installed globally, you can drop the npx @deepseek-ai/ prefix.
3. Configure Credentials
Normal runtime credentials come from environment variables (or a secret store
provided by your launcher); keep .env out of version control. The local Web
MFA flow is the narrow exception: after explicit profile confirmation, the
Host atomically saves an owner-only DI session file. It never saves the password,
MFA code, or CAPTCHA response.
# Source checkout only: copy the bundled template
cp .env.example .env
# Edit .env and fill in your Garmin credentials
For a registry installation, create .env directly in the directory where you
run dsh (your workspace root), then add the variables from the table below;
the package's template is inside the installed dependency rather than your
current directory. The plugin loads the workspace .env automatically.
| Variable | Required | Description |
|---|---|---|
GARMIN_USERNAME | ✅ | Your Garmin account email |
GARMIN_ACCOUNT | ❌ | Lowercase local alias used for the implicit Web/CLI/MCP session path (default when omitted) |
GARMIN_PASSWORD | ✅* | Legacy direct-login password; do not use this for the interactive MFA setup below |
GARMIN_SESSION_TOKEN | ✅* | Inline pre-authenticated token (supported, but the session file is safer) |
GARMIN_SESSION_TOKEN_FILE | ✅* | Path to the owner-only DI v2 (or compatible legacy OAuth) session file generated by the local auth command |
GARMIN_REGION | ❌ | global (default) or cn for Garmin China |
GARMIN_FIT_DOWNLOAD_DIR | FIT only | User-selected host parent directory for FIT exports; no default. The generated account directory includes GARMIN_REGION |
GARMIN_CACHE_TTL | ❌ | Cache duration in seconds (default: 300) |
GARMIN_REQUEST_TIMEOUT_MS | ❌ | Garmin request timeout in milliseconds (default: 15000) |
GARMIN_LOG_LEVEL | ❌ | debug | info | warn | error |
GARMIN_ACTIVITY_DETAIL | ❌ | compact (default) or full (expanded fitness plus precise route/location fields; credentials and account/social identifiers are filtered) |
* Normal data access needs one of
GARMIN_PASSWORD,GARMIN_SESSION_TOKEN, orGARMIN_SESSION_TOKEN_FILE. The local Web,auth:serve, and standalone MCP flows may start without one and create the implicit account session file. A protected file is safer than an inline token, especially when isolating multiple processes. If more than one is configured, the inline token takes precedence over the file until Garmin explicitly rejects it; a newly written, account-matching session file can then take over on retry. A valid session takes precedence over password login.⚠️ If your password contains
#or other special characters, wrap it in double quotes — otherwise#and everything after it will be treated as a comment:GARMIN_PASSWORD="my#secret!pass"
GARMIN_SESSION_TOKENand the contents ofGARMIN_SESSION_TOKEN_FILEare as sensitive as a password. Token export is intentionally not AI-callable; never paste a token into an AI conversation.
Two-step verification — browser-based MFA
When dsh and its Web UI are running together on the same local machine, use the
China account or International account Garmin button in the top bar. The
selected button must match the process's configured GARMIN_REGION; a mismatch
fails before any Garmin page is opened. A matching selection opens a custom bridge on an ephemeral
127.0.0.1 port; that bridge, rather than the dsh page itself, embeds Garmin's
official GAuth page. Email, password, MFA code, and any CAPTCHA are entered only
inside the Garmin iframe.
Making this safe and reliable required more than adding a verification-code field. We first kept Garmin's official GAuth page inside a local iframe so the email, password, and MFA code stayed on Garmin's origin, then worked through cross-origin messaging, missing vendor styles, browser CSP/Trusted Types, third-party-frame policy, MFA redirects, and exact ticket/service binding. Inspired by Zhitao's DailySync approach of completing Garmin SSO in a fresh browser tab, we added the system-browser loopback broker used by CLI and MCP clients. The tab returns only a short-lived, one-time service ticket; the Host immediately exchanges it for the longer-lived DI session and refresh credentials, verifies the Garmin profile, and writes an owner-only session. In other words, the ticket itself is not a one-year credential—the potentially long-lived artifact is the exchanged session/refresh credential, whose actual lifetime remains controlled by Garmin.
Garmin sends its short-lived service ticket only to the isolated loopback bridge. The bridge validates the ex