googlarz/suunto-mcp
file decoding, GPX export, plus 24/7 activity, sleep, and recovery/HRV. Resources expose today's recovery and the week's training summary as ambient context.
Ask AI about googlarz/suunto-mcp
Powered by Claude Β· Grounded in docs
I know everything about googlarz/suunto-mcp. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
Suunto MCP
Bring your Suunto watch into the conversation.
β³ Status: awaiting API access. I've applied for a Suunto apizone API key and am waiting on approval. Until then, the code is implemented end-to-end against the documented Suunto API surface but has not been verified against a live account. Expect to file at least one issue once real credentials land. PRs from anyone with API access already are very welcome.
This is a small bridge that lets AI assistants like Claude read your Suunto training data β runs, hikes, sleep, recovery β so you can just talk to your watch.
Built by a Suunto user (hi π) who wanted to ask his coach-shaped chatbot "how was my last long run?" instead of clicking through dashboards β and to plug live training data into a personal health-skill for context-aware health Q&A.
What can you do with it?
Once it's set up, you can ask things like:
- "How many kilometers did I run this month?"
- "Compare my last three long runs β was my heart rate drift better?"
- "Pull the GPX of yesterday's hike and write a short journal entry."
- "What's my average resting HR trend over the last 2 weeks?"
- "Find every workout above 160bpm average and show me the route."
- "Summarize my training week in the style of a coaching report."
The AI does the work. You just ask.
Is this for me?
Yes, if you own any modern Suunto watch (Race, Vertical, 9 Peak, 5 Peak, Ocean, etc.) and you sync it to the Suunto app on your phone.
No coding required to use it β just follow the setup below. The hard part (parsing Suunto's API, refreshing tokens, decoding the binary FIT file format) is already done for you.
How it works (in one picture)
Your Suunto watch ββΊ Suunto app ββΊ Suunto cloud ββΊ Suunto MCP ββΊ Claude (or any MCP client)
This project is the second-to-last box. It speaks Suunto on one side and the Model Context Protocol on the other.
Prerequisites
- A Suunto watch synced to the Suunto app (your data must already be in Suunto's cloud β this tool reads from there, not the watch directly)
- Node.js β₯ 20 (nodejs.org β install if needed)
- An MCP-capable client: Claude Desktop, Claude Code, Cursor, or any MCP-compatible app
- ~10 minutes if you already have an apizone account, ~25 minutes from scratch
Setup
1. Get a Suunto API key
Suunto opened their platform to all developers in March 2026 β anyone can sign up. Publishing to the Suunto store needs a partner agreement; personal use doesn't.
-
Go to apizone.suunto.com β Sign up.
-
Once signed in, click Apps β Create app. Give it any name (e.g. "Personal MCP"). For redirect URI use exactly:
http://localhost:8421/callback -
From the app overview page, copy the Client ID and Client Secret (you may need to click Regenerate to reveal the secret once).
-
Click Subscribe to APIs and subscribe your app to all of these products β each is a separate subscription:
- Workouts API (required)
- Activity API β for steps, calories, daily HR
- Sleep API β for sleep stages and score
- Recovery API β for HRV / recovery score
- Subscriptions API β for webhook management
Without a subscription, calls to that product return 403/404.
-
Go to your user profile β Subscriptions tab and copy the primary subscription key. This is the
Ocp-Apim-Subscription-Keythat Suunto's Azure API Management gateway requires on every call.
π If you can't find a value, run
npm run doctorafter step 3 below β it will tell you exactly which one is missing or wrong.
2. Install
git clone https://github.com/googlarz/suunto-mcp
cd suunto-mcp
npm install
npm run build
A
Dockerfileis also included for containerized deployments and for glama.ai's automated introspection checks.
3. Configure
cp .env.example .env
$EDITOR .env # paste the 3 values from step 1
4. Pair your Suunto account
npm run auth
Your browser opens automatically to Suunto's authorization page. Click
Authorize. The page redirects back to a local success screen and tokens
are saved to ~/.suunto-mcp/tokens.json (mode 0600). You only do this
once β the server refreshes tokens automatically.
If the browser doesn't open, copy the URL from the terminal manually. Set
SUUNTO_NO_BROWSER=1to disable the auto-open behavior.
5. Verify it works
npm run doctor
This runs an end-to-end health check: Node version, env vars, network
reachability, token freshness, a live list_workouts probe, and which
24/7 products you're subscribed to. Fix any β lines before moving on.
6. Plug into Claude
Claude Desktop
Edit (creating if missing):
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Claude Code
Edit ~/.claude/mcp_config.json (or run claude mcp add if your version
supports the CLI flow).
Config (works for both)
Once published to npm (no clone needed):
{
"mcpServers": {
"suunto": {
"command": "npx",
"args": ["-y", "suunto-mcp"],
"env": {
"SUUNTO_CLIENT_ID": "...",
"SUUNTO_CLIENT_SECRET": "...",
"SUUNTO_SUBSCRIPTION_KEY": "..."
}
}
}
}
From a local clone:
{
"mcpServers": {
"suunto": {
"command": "node",
"args": ["/absolute/path/to/suunto-mcp/dist/index.js"],
"env": {
"SUUNTO_CLIENT_ID": "...",
"SUUNTO_CLIENT_SECRET": "...",
"SUUNTO_SUBSCRIPTION_KEY": "..."
}
}
}
}
Restart Claude. Quick sanity check β ask:
"What was my most recent workout?"
If you get a real answer with sport, distance, and date, you're connected.
Example conversation
You: Compare my last three long runs. Has my heart-rate drift improved?
Claude: Pulling your recent workouts⦠(calls list_workouts)
Found 3 runs over 90 minutes in the last 6 weeks:
β’ Apr 12 β 22.4 km, 2h09, avg HR 148, last-30min drift +6 bpm
β’ Apr 19 β 24.0 km, 2h21, avg HR 144, last-30min drift +4 bpm
β’ Apr 26 β 25.1 km, 2h28, avg HR 142, last-30min drift +2 bpm
Drift is trending down despite slightly longer runs β your aerobic
base is improving. Pace at the same HR is also ~3 s/km faster.
Tools exposed to the AI
Workouts
| Tool | What it does |
|---|---|
list_workouts | Recent workouts, filter by date range, auto-paginates |
get_workout | Full summary for one workout |
get_workout_samples | Time-series HR / pace / altitude / power / GPS |
get_workout_fit | Downloads the FIT file and returns parsed, structured JSON |
export_workout_gpx | GPX track export β for maps, Strava, route planning |
24/7 health data (requires the Activity / Sleep / Recovery API products to be enabled on your apizone subscription)
| Tool | What it does |
|---|---|
get_daily_activity | Steps, calories, daily HR for a single day |
list_daily_activity | Steps, calories, daily HR for a date range |
get_sleep | Sleep stages, duration, score for a night |
list_sleep | Sleep data over a date range |
get_recovery | Recovery / HRV / stress for a single day |
list_recovery | Recovery data over a date range |
Webhooks
| Tool | What it does |
|---|---|
list_subscriptions | Active webhook subscriptions on the account |
The AI picks the right one for your question. You don't need to know which.
Optional: CLI
Query your Suunto data directly from the terminal β no AI client needed.
Useful for scripting, quick checks, or piping into jq.
# After npm run build (or npm install -g suunto-mcp once published):
suunto-mcp list-workouts --since 2026-04-01 --limit 10
suunto-mcp get-workout <workoutKey>
suunto-mcp get-sleep 2026-04-20
suunto-mcp list-sleep --from 2026-04-01 --to 2026-04-30
suunto-mcp list-recovery --from 2026-04-01 --to 2026-04-30
suunto-mcp export-workout-gpx <workoutKey> > route.gpx
All commands output JSON to stdout:
# Most recent workout's sport and distance
suunto-mcp list-workouts --limit 1 | jq '.payload[0] | {sport, km: (.totalDistance/1000)}'
# Average sleep score over the last week
suunto-mcp list-sleep --from 2026-04-14 --to 2026-04-20 \
| jq '[.payload[].sleepScore] | add/length'
Run suunto-mcp --help for the full command list.
Optional: webhooks
Suunto can push notifications to you the moment a new workout finishes syncing β no polling.
npm run webhook
This starts a tiny HTTP receiver on port 8422 that logs every event to
~/.suunto-mcp/webhooks.ndjson. Expose it to the public internet (cloudflared,
ngrok, your own VPS) and register the URL in your Suunto app settings.
For most personal setups, skip this. Polling on demand is simpler.
Pairs well with health-skill
If you already use googlarz/health-skill β a Claude skill for symptom triage, lab interpretation, and lifestyle guidance β Suunto MCP gives it a live feed of your training, sleep, and recovery data. Together they answer questions like "is my resting HR drift this week consistent with the cold I had?" or "given my recovery scores, should I keep this week's intervals?" with real numbers instead of guesses.
Privacy
As of v0.1:
- All data flows directly between your machine and Suunto's API. No third-party servers, no analytics.
- Tokens are stored locally at
~/.suunto-mcp/tokens.json(mode0600), or in your OS keychain if you opted in. - Suunto sees that an app called "Suunto MCP" is authorized on your account (visible in apizone β user profile β Authorized applications).
- The AI only sees data it explicitly requests via tools or resources.
If a future version offers a hosted-proxy option for non-tech users, this section will be updated explicitly. The default will always remain local-first.
Troubleshooting
Run npm run doctor first β it pinpoints most issues automatically.
| Symptom | Likely cause | Fix |
|---|---|---|
Missing required env vars | .env not loaded or not filled in | cp .env.example .env, fill it, retry |
Not authenticated / SuuntoNotAuthenticatedError | Tokens missing | Run npm run auth |
Token request failed: 400 | Wrong client secret, or redirect URI doesn't match apizone exactly | Re-copy from apizone, ensure http://localhost:8421/callback is registered |
SuuntoAuthError (401) on every call | Subscription key wrong or expired | Re-copy primary key from apizone β user profile β Subscriptions |
SuuntoForbiddenError (403) on list_workouts | App not subscribed to the Workouts product | apizone β your app β Subscribe to APIs β Workouts |
404 on get_sleep / get_recovery / get_daily_activity | App not subscribed to that 24/7 product | Subscribe in apizone (each product is separate) |
| Empty workout list | Watch hasn't synced to the Suunto cloud | Open Suunto app on your phone, wait for sync |
npm run auth hangs / browser never opens | Port 8421 already in use, or running headless | lsof -i :8421 to check; set SUUNTO_NO_BROWSER=1 and copy URL manually |
| OAuth callback page says "State mismatch" | Started a second auth flow before the first finished | Close all auth tabs and run npm run auth once |
| Tokens "disappear" after switching to keychain | File-based tokens don't migrate automatically | Re-run npm run auth after enabling keychain |
Disconnecting / cleanup
To remove access:
- Revoke the OAuth grant on Suunto's side β log in to apizone β user profile β Authorized applications β remove your app. Suunto stops honoring the tokens immediately.
- Delete local tokens:
If you were using the keychain backend, delete the entry namedrm -f ~/.suunto-mcp/tokens.jsonsuunto-mcp / tokensin your OS keychain (Keychain Access on macOS, etc.). - Remove the MCP entry from your Claude config and restart Claude.
- Optional β delete your apizone app if you no longer want it listed as a registered application.
MCP Resources (ambient context)
In addition to tools, the server exposes resources β passive data the client can pull without the model having to call a tool:
| URI | What |
|---|---|
suunto://recent/workout | Most recent workout summary |
suunto://today/sleep | Last night's sleep |
suunto://today/recovery | Today's recovery / HRV |
suunto://today/activity | Today's steps / calories / HR |
suunto://this-week/summary | Aggregated training totals for the current ISO week |
Clients that surface MCP resources (Claude Desktop, Cursor) will let you attach these directly into a conversation β useful for "given my recovery today, should Iβ¦" style questions.
Reliability
- Automatic retries with exponential backoff + jitter on
429,500,502,503,504(up to 4 attempts). Retry-Afterheader is honored when Suunto returns one.- Auto-pagination in
list_workoutsβ keeps fetching pages until yourlimitis met or there's nothing left. - Token refresh is automatic β the access token is silently re-issued before each request if it's within 60 seconds of expiry.
- Concurrent-refresh deduplication β multiple parallel calls share a
single in-flight refresh, so Suunto never sees a double
refresh_tokengrant (which would invalidate the older token and log you out). - Structured error types β
SuuntoAuthError,SuuntoForbiddenError,SuuntoNotFoundError,SuuntoRateLimitError,SuuntoApiError,SuuntoNotAuthenticatedError,SuuntoTokenError. Lets clients distinguish "re-authenticate" from "wait and retry" from "this resource doesn't exist."
Token storage
By default, tokens are written to ~/.suunto-mcp/tokens.json with file
mode 0600. For stronger protection, opt into the OS keychain
(macOS Keychain, Linux libsecret, Windows Credential Manager):
SUUNTO_TOKEN_STORAGE=keychain npm install @napi-rs/keyring
SUUNTO_TOKEN_STORAGE=keychain npm run auth
The keychain backend is an optional dependency β npm install will not
fail if it can't be built on your platform; it just falls back to the
file-based default.
Health check
npm run doctor
Output:
Suunto MCP β health check
β Node version 20.18.0 (require β₯ 20)
β Credentials client_id, client_secret, subscription_key set
β Network β cloudapi-oauth.suunto.com reachable (HTTP 302)
β Pairing paired (user: dawid), token expires in 47 min
β API probe (list_workouts) received 1 workout (latest: 1714137600000)
β Daily activity product subscribed
! Sleep product not subscribed on apizone
β Recovery product subscribed
Run this whenever something feels off. It pinpoints the exact failing layer (Node, env, network, auth, API quota, missing product subscription).
Tests
npm test
40 unit + integration tests cover:
- OAuth URL building, code exchange, refresh, token-expiry refresh logic
- Concurrent-refresh deduplication (4 parallel calls β 1 token request)
- Token storage (round-trip, file permissions, missing-file fallback, parent-dir creation)
- API client: bearer + subscription-key headers, retry on 429/500 with
Retry-After, no retry on 4xx, byte-stream downloads - Structured error types (
SuuntoAuthError,SuuntoForbiddenError,SuuntoNotFoundError,SuuntoRateLimitError) list_workoutsauto-pagination across multiple pages- FIT integration: parser accepts a minimum-valid byte stream, rejects garbage and empty input
- FIT summary extraction, empty-FIT handling, record sampling
- MCP resources: enumeration, dispatch, today's-date wiring, week aggregation
- Config loading, env overrides, missing-credential errors
CI runs on Node 20 and 22 on every push and PR.
PRs welcome.
Credits
- Suunto APIzone β the people who opened the door
- Model Context Protocol β the standard this server speaks
fit-file-parserβ for decoding FIT binaries
License
MIT. Use it, fork it, improve it.
