Server data from the Official MCP Registry
Read your Garmin sleep, HRV, steps, stress, Body Battery, and activities (unofficial)
About
Read your Garmin sleep, HRV, steps, stress, Body Battery, and activities (unofficial)
Security Report
Valid MCP server (3 strong, 3 medium validity signals). No known CVEs in dependencies. ⚠️ Package registry links to a different repository than scanned source. Imported from the Official MCP Registry. 1 finding(s) downgraded by scanner intelligence.
9 files analyzed · 1 issue found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Permissions Required
This plugin requests these system permissions. Most are normal for its category.
What You'll Need
Set these up before or after installing:
Environment variable: GARMIN_PERSONAL_API_KEY
How to Install
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-dynamicsninja-garmin-connect-personal-api": {
"env": {
"GARMIN_PERSONAL_API_KEY": "your-garmin-personal-api-key-here"
},
"args": [
"-y",
"garmin-connect-personal-api-mcp"
],
"command": "npx"
}
}
}Documentation
View on GitHubFrom the project's GitHub README.
Garmin Connect Personal API Client for JavaScript
Unofficial. This is a community port. It isn't affiliated with or endorsed by Garmin.
A JavaScript port of garmin/connect-personal-api-python, a small client for reading data from Garmin's Personal API. It has no runtime dependencies, and its parts can be used independently:
PersonalApiAuthenticatorcaches bearer tokens and exchanges a Personal API key for a new token when needed.GarminPersonalApiClientmakes authenticated GET requests with any bearer token and provides convenience wrappers for Personal API data sets.gc-personalis a command-line interface built on those same wrappers, with direct commands for scripts and an interactive menu for exploring the data.garmin-connect-personal-api-mcpis an MCP server that lets Claude and other AI assistants answer questions about your sleep, HRV, training, and more.
Requirements and installation
You need Node.js 20.4 or newer and a Garmin Connect Personal API key.
npm install garmin-connect-personal-api-js # library
npm install -g garmin-connect-personal-api-js # adds the gc-personal command
To use a clone of this repository instead, run npm install and then
npm link to put gc-personal on your PATH, or run node bin/gc-personal.js.
The package is ESM-only and ships TypeScript declarations generated from its JSDoc.
Authentication
The package does not create Personal API keys. Create a key on Garmin Connect.
Command-line key sources
gc-personal reads the key from one of these sources:
- Key file:
~/.config/garmin/connect-personal-api-key, or another path passed with--api-key-file. On Windows,~is your user profile folder. The file should contain only the API key; surrounding whitespace is ignored. - Environment variable:
GARMIN_PERSONAL_API_KEY. - Command-line argument:
--api-key.
If more than one source is provided, precedence is:
- Explicit key:
--api-key. - Explicit key file:
--api-key-file, even if it names the default path. - Environment variable:
GARMIN_PERSONAL_API_KEY. - Default key file, when neither explicit option nor the environment variable supplies a key.
A missing, unreadable, or empty explicitly selected file is an error, not a
reason to fall back to the environment or default file. An empty explicit key
or environment variable is also an error. A key passed with --api-key may
be exposed through shell history or the process list.
gc-personal profile-settings # default key file
gc-personal --api-key-file ~/my-garmin-key profile-settings # another key file
GARMIN_PERSONAL_API_KEY='your-key' gc-personal profile-settings
gc-personal --api-key 'your-key' profile-settings # visible to other processes
JavaScript key sources
obtainToken() requires an explicit key and doesn't discover one itself. To
opt into discovery, pass it the result of resolveApiKey(), which applies the
same precedence as the CLI: apiKey, then keyFile, then
GARMIN_PERSONAL_API_KEY, then the default key file.
Bearer-token cache
The authenticator exchanges the API key for a bearer token and caches it in
~/.config/garmin/connect-personal-api-bearer-token.json. Use
--bearer-token-cache-file in the CLI, or the bearerTokenCacheFile option,
to choose another location. The cache format is the same as the Python
client's, so the two can share a cache file.
A cached token is reused only when it was issued for the current API key and
base URL, and has more than one hour remaining before expiration, based on the
JWT exp claim. Otherwise, the authenticator obtains and caches a new token.
Rarely, a token stops working before it expires, for example because it was
revoked. Requests then fail with 401, and the client doesn't retry them. To
recover, obtain a new token:
- Direct commands: run
gc-personal refresh-token, or delete the cache file, then rerun the command. - Interactive mode: choose Reload Bearer Token from the menu.
- JavaScript: call
obtainToken(apiKey, { forceRefresh: true }), then pass the new token'saccessTokentoclient.updateBearerToken().
Protecting credential files
The bearer-token cache, and the key file if you use one, hold plain-text
credentials. Keep them readable only by your user and out of version control.
On POSIX systems, the package logs a warning, but continues, when either file
is readable by group or other users. This check isn't performed on Windows.
The authenticator creates the cache file with 0600 permissions; an existing
cache file keeps its current permissions. Restrict your key file yourself, for
example with chmod 600.
Command-line interface
gc-personal --help
gc-personal steps --help
gc-personal profile-settings
gc-personal sleep --date 2026-08-24
gc-personal steps --start-date 2026-08-20 --end-date 2026-08-24
gc-personal activities --start 0 --limit 20 --start-date 2026-08-01
gc-personal activity 123456789
gc-personal activity-workouts 123456789
gc-personal activity-download 123456789
gc-personal activity-download 123456789 -o ./run.zip
gc-personal refresh-token
gc-personal --api-key-file /path/to/key --timeout 20 --quiet profile-settings
Most date options default to today. Shared options such as --api-key-file
and --timeout go before the command. Use --quiet to suppress request logs
for non-interactive commands.
Data commands print JSON with sorted keys to stdout. Request logs, status
messages, and errors go to stderr, so output can be piped to tools such as
jq. The command exits with a non-zero status on failure (2 for invalid
arguments).
activity-download saves a ZIP file as activity-ID.zip in the current
directory unless -o PATH is given, and doesn't replace an existing file
unless you pass --force. Use -o - to write the ZIP bytes to stdout; the
command refuses to write them to a terminal.
Interactive mode
Run gc-personal without a command to open a menu for exploring the data. It
authenticates through the same cache-aware path as direct commands and can
force a new token from the menu. On a terminal, date prompts accept the up and
down arrow keys to move the date by one day. Select an activity in the
activity browser to view its details or workouts, or to download its ZIP file.
Use --no-color or set NO_COLOR to disable colors.
Library usage
Explicit API key
import { GarminPersonalApiClient, PersonalApiAuthenticator } from 'garmin-connect-personal-api-js';
const apiKey = await mySecretManager.get('garmin-personal-api-key');
const token = await new PersonalApiAuthenticator().obtainToken(apiKey);
const client = new GarminPersonalApiClient(token.accessToken);
const sleep = await client.getDailySleep('2026-08-24');
const steps = await client.getDailySteps('2026-08-20', '2026-08-24');
obtainToken() resolves to a BearerToken with the token string in
accessToken and its expiration, in seconds since the epoch, in expiresAt.
The token string is left out of console.log() and JSON.stringify() output.
Key discovery
import { PersonalApiAuthenticator, resolveApiKey } from 'garmin-connect-personal-api-js';
const token = await new PersonalApiAuthenticator().obtainToken(resolveApiKey());
To select a key file explicitly, use resolveApiKey({ keyFile: '~/my-garmin-key' }).
This overrides the environment variable. If the selected file is missing,
unreadable, or empty, discovery throws instead of trying another source.
Bearer tokens from another source
const client = new GarminPersonalApiClient(await myTokenProvider.getToken());
const profile = await client.getUserProfileSettings();
Configuration
Both classes accept these options:
| Option | Default | Description |
|---|---|---|
timeoutSeconds | 30 | Per-request timeout. Must be greater than zero. |
fetch | globalThis.fetch | A fetch-compatible function, for proxies, retries, or tests. |
logger | silent info, warnings to stderr | An object with info(message) and warn(message). Request activity is logged at info; tokens and keys are never logged. |
PersonalApiAuthenticator also accepts bearerTokenCacheFile. Set
GARMIN_PERSONAL_API_BASE_URL to use another API base URL.
close() aborts in-flight requests. Both classes also implement
Symbol.dispose, so they work with using declarations.
Downloading activity files
downloadActivity() resolves to ZIP bytes in a Buffer, without writing or
extracting files:
import { writeFile } from 'node:fs/promises';
const content = await client.downloadActivity(123456789);
await writeFile('activity-123456789.zip', content);
The whole ZIP is held in memory. Unlike the CLI, writeFile() overwrites an
existing file.
Examples
The examples folder has ready-to-run scripts. Each one reads the
key the same way the CLI does.
| Script | What it does |
|---|---|
sleep-report.js | Prints a table of the last few nights' sleep stages, scores, and HRV |
export-daily-csv.js | Exports daily steps, heart rate, stress, Body Battery, and sleep to CSV for spreadsheets |
backup-activities.js | Downloads original activity files to a folder, skipping ones already saved; safe to schedule |
fetch-sleep.js, fetch-activities.js, get-token.js | Minimal starting points |
node examples/sleep-report.js --days 14
node examples/export-daily-csv.js --start-date 2026-01-01 -o daily.csv
node examples/backup-activities.js --dir ~/garmin-activities
AI assistants (MCP)
The MCP server exposes every endpoint as a read-only tool, so you can ask Claude Desktop, Claude Code, or another MCP client about your data. To add it to Claude Code:
claude mcp add garmin --env GARMIN_PERSONAL_API_KEY=your-key -- npx -y garmin-connect-personal-api-mcp
See the MCP server README for Claude Desktop and other clients.
Endpoint wrappers
Every wrapper returns a promise of the decoded JSON response. Wrappers may lag
behind the endpoints available through the Personal API; see the
Personal API Swagger documentation,
and use get() for endpoints without a wrapper.
| JavaScript wrapper | CLI command | HTTP request |
|---|---|---|
getDailyStress(startDate, endDate) | stress | GET /usersummary-service/stats/stress/daily/{startDate}/{endDate} |
getDailySteps(startDate, endDate) | steps | GET /usersummary-service/stats/steps/daily/{startDate}/{endDate} |
getDailyHydration(startDate, endDate) | hydration | GET /usersummary-service/stats/hydration/daily/{startDate}/{endDate} |
getEffectiveWeightGoals(startDate, endDate) | effective-weight-goals | GET /goal-service/goal/user/effective/weightgoal/{startDate}/{endDate} |
getWeightMeasurements(startDate, endDate) | weight-measurements | GET /weight-service/weight/range/{startDate}/{endDate}?includeAll=true |
getDailyFoodLogs(calendarDate) | food-logs | GET /nutrition-service/food/logs/{calendarDate} |
getUserProfileSettings() | profile-settings | GET /userprofile-service/userprofile/settings |
getDailySleep(calendarDate) | sleep | GET /sleep-service/sleep/dailySleep?date={calendarDate} |
getDailySleepSummaries(startDate, endDate) | sleep-summaries | GET /sleep-service/sleep/dailySleepSummariesByDate?startDate=…&endDate=… |
getDailyBodyBattery(startDate, endDate, { chunks }) | body-battery | GET /wellness-service/wellness/bodyBattery/reports/daily?startDate=…&endDate=…[&chunks=…] |
getDailySummaries(startDate, endDate) | daily-summaries | GET /usersummary-service/v2/dailysummaries?startDate=…&endDate=… |
getDailyHeartRate(calendarDate) | heart-rate | GET /wellness-service/wellness/dailyHeartRate?date={calendarDate} |
getDailyHrv(calendarDate) | hrv | GET /hrv-service/hrv/{calendarDate} |
getDailyHrvSummaries(startDate, endDate) | hrv-summaries | GET /hrv-service/hrv/daily/{startDate}/{endDate} |
getHeartRateZones() | heart-rate-zones | GET /biometric-service/heartRateZones/v2 |
getMenstrualCycleSummary(calendarDate) | menstrual-cycle-summary | GET /periodichealth-service/menstrualcycle/summary/{calendarDate} |
getMenstrualCycleDayView(calendarDate, todayDate) | menstrual-cycle-day-view | GET /periodichealth-service/menstrualcycle/dayview/{calendarDate}?todayDate=… |
getFitnessAge(calendarDate) | fitness-age | GET /fitnessage-service/fitnessage/v2/{calendarDate} |
getDailySpo2(calendarDate) | spo2 | GET /wellness-service/wellness/daily/spo2acclimation/{calendarDate} |
getWellnessActivitySummaries(startDate, endDate) | wellness-activity-summaries | GET /wellnessactivity-service/activity/summary/range/{startDate}/{endDate} |
searchActivities({ start, limit, startDate, endDate, activityType }) | activities | GET /activitylist-service/activities/search/activities?start=0&limit=20[&startDate=…&endDate=…&activityType=…] |
getActivity(activityId) | activity | GET /activity-service/activity/{activityId} |
getActivityWorkouts(activityId) | activity-workouts | GET /activity-service/activity/{activityId}/workouts |
downloadActivity(activityId) | activity-download | GET /download-service/files/activity/{activityId} (ZIP bytes) |
Dates accept YYYY-MM-DD strings or Date objects, which are formatted in
local time. Validation of dates, ranges, and other arguments is left to the
API, except download activity IDs, which must be positive integers.
Call an endpoint without a wrapper
const customData = await client.get('/some-service/some-resource', {
params: { calendarDate: '2026-08-24' },
});
The client adds bearer-token authorization and resolves to decoded JSON. A
successful response with no content resolves to null.
Errors
GarminPersonalApiErrorfor missing, unreadable, or empty credentials, invalid local settings such as a non-positive timeout or an invalid download activity ID, an empty download response, a token response that is not a JWT with anexpclaim, or a download file that can't be saved by the CLI.HttpError, a subclass ofGarminPersonalApiError, for unsuccessful token or data responses. It exposesstatusCode,method,url, andresponseText.- Node.js file-system errors when the bearer-token cache can't be read or written, for example because of file permissions.
Network failures surface as the TypeError that fetch throws, timeouts as a
TimeoutError DOMException, and invalid JSON responses as a SyntaxError.
Differences from the Python client
- Methods and options use camelCase, and network methods return promises.
timeoutis namedtimeoutSeconds; it is still in seconds.- A custom
fetchfunction replaces therequests.Sessionoption, and aloggeroption replaces theloggingmodule. - Interactive mode is built in rather than an optional extra, and its date arrow-key navigation works on every platform, including Windows.
Development
npm install
npm test # node:test, no credentials or network access needed
npm run typecheck # tsc over the JSDoc types
License
Copyright 2026 Garmin Ltd. or its subsidiaries. This port is distributed under the Apache 2.0 License. See LICENSE for the complete license text.
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Rea
Freeby Morluto · Developer Tools
Reverse engineer anything from your terminal or agent with one CLI and MCP server.
Paperclip
Freeby Paperclipai · Developer Tools
Trending hip-hop artist momentum scores across four cultural dimensions.
Git
Freeby Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
mcp-creator-python
Freeby mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.
