Suunto MCP
Connects Claude to your Suunto account so you can ask about training, sleep, recovery and routes — and push workouts, routes and guided sessions back.
Read workouts —
list_workouts(filtered by date, newest first),get_workout(summary),get_workout_laps(lap-by-lap table with guide labels and reliability checks),get_workout_fit(parsed FIT file, compact or full).Whole-day snapshot —
get_daily_snapshotcombines last night's sleep, recovery balance, steps/energy and workouts, for one day or up to 14 days.24/7 health —
get_sleep/list_sleep(stages, score, HRV),get_recovery/list_recovery(balance, stress state),get_daily_activity/list_daily_activity(HR, steps, energy samples),get_daily_activity_statistics(aggregated totals).Routes —
list_routes,get_route,export_route(GPX),upload_route(import a GPX as a private route).Upload workouts —
upload_workout(FIT file) plusget_upload_statusto poll processing.Push guided sessions to the watch —
push_workout_guide(lap-per-exercise),push_interval_guide(timed/distance auto-advance with HR targets),push_strength_guide(per-set strength with rest modes); manage them withlist_guides/delete_guide.Daily health digest —
generate_daily_digestwrites a color-coded markdown summary including computed Fitness/Fatigue/Form (CTL/ATL/TSB) from workout TSS.Unavailable —
get_workout_samples,export_workout_gpxandlist_subscriptionsare hidden/legacy; Suunto's gateway rejects those endpoints.
Allows exporting workout GPX tracks that can be imported into Strava for route planning, mapping, and activity sharing.
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., "@Suunto MCPHow many kilometers did I run this month?"
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.
Suunto MCP
Ask Claude anything about your training. Suunto MCP connects your Suunto watch data to Claude so you can just talk to your data instead of clicking through dashboards.
Built by a Suunto user who wanted to ask "how was my last long run?" and get a real answer with numbers — and to feed live training data into a personal AI coach.
🏃 For regular Suunto users: Suunto's API docs say access is for commercial partners only — that's not the full picture. Private users get access too. It just takes 3–4 weeks for approval after you apply. Submit, wait, enjoy. Don't let that disclaimer stop you. ✅
What you can do
Once it's set up, just ask:
"How many kilometers did I run this month?"
"Compare my last three long runs — has my heart-rate drift improved?"
"How did my heart rate hold up set by set in last night's gym session?"
"What's my average resting HR trend over the last two weeks?"
"Summarize my training week in the style of a coaching report."
"I've been feeling off — how do my recovery scores compare to last month?"
"Find every workout where I averaged above 160 bpm."
"Which of my runs this year had the most elevation?"
Claude figures out what data to pull. You just ask.
It's not just read-only either — Claude can push things to your watch too:
"Plan tonight's gym session and send it to my watch."
"Upload yesterday's Garmin export as a Suunto workout."
"Export my last route as GPX so I can share it."
See What you can push to your watch below.
Related MCP server: Garmin MCP Server
🤖 Don't want to do this yourself? Let Claude Code install it
If you already have Claude Code, you don't need to run a single terminal command. Just open it and say:
"Please install and set up suunto-mcp from https://github.com/googlarz/suunto-mcp"
Claude Code will clone the repo, run all the install commands, and add everything to your Claude Desktop config — on its own. That's exactly how this project's author set it up: no manual terminal work.
Three things stay yours no matter what, by design, not because of missing tooling:
Creating the apizone.suunto.com account — Claude can't create accounts on your behalf.
The apizone web form (naming your app, revealing your subscription key) — it's your account session; Claude tells you exactly where to click, but can't click there for you.
Clicking "Authorize" during login — that's OAuth working as intended. An app that could approve its own access wouldn't be secure.
Claude will tell you exactly what to do and when for each of these.
If you want to do it manually, keep reading.
What you need
Before starting, make sure you have:
A Suunto watch synced to the Suunto app (any modern model — Race, Vertical, 9 Peak, 5 Peak, Ocean, etc.)
Claude Desktop (or another MCP-compatible AI app)
Node.js — free, download here, choose the "LTS" version
Git — free, download here
~5 min to submit + 3–4 week wait for Suunto approval + ~15 min to install
Once it's done, you never redo it.
Setup
Prefer one guided walkthrough with a pacing choice up front (fast path vs. explained-step-by-step) and a proper explanation of how syncing works? See GETTING_STARTED.md. What follows here is the same steps in reference form.
The setup has three parts:
Register with Suunto's developer portal — tells Suunto your app is allowed to read your data
Install and configure — gets the software running on your computer
Connect to Claude — lets the AI find and use it
Part 1: Register with Suunto's developer portal (~5 min to submit, then wait 3–4 weeks)
Suunto has a free developer portal called apizone where you register apps that can access your data. You'll create an account, subscribe to the data plan, and register a small "app" — don't worry, there's nothing to build, it's just a name and a password you make up.
Step 1: Create your apizone account
Go to apizone.suunto.com and sign up or sign in.
Use the same email you use for the Suunto app. If you have a Sports Tracker account, that works too — it's the same login system.
Step 2: Subscribe to the Developer API
After signing in, follow the How to start guide — it walks you through subscribing to the Developer API. This is free and gives you access to your workout history.
Heads up: Suunto's website states that API access is only for commercial partners — ignore that. Private users do get access, it just takes 3–4 weeks for the subscription to be approved. Submit it and wait. It will come through.
You may see other products like "Sleep API", "Recovery API", "Daily Activity API". The Developer API is enough to get started (workouts). Subscribe to the other three as well if you want sleep, recovery, steps, the daily snapshot or the daily digest — they are listed next to the Developer API, and you can also add them later.
⏳ Stop here and wait. Once you've subscribed, Suunto needs to approve your request. This takes 3–4 weeks. You'll get an email when it's done. Come back to Steps 3–4 only after your subscription shows as Active in your apizone profile.
Step 3: Register your app (do this after approval)
You're going to tell Suunto: "I have a small program, here's its name and a secret password — please let it read my data."
Go to your apizone profile page
Scroll down to OAuth application settings
Fill in the form:
Field
What to enter
App name
suunto-mcp(or anything you like)Client secret
Make up a unique password — something only you'd know, e.g.
alice-suunto-2026with your own name. Write it down. Don't use this exact example.Redirect URI
http://localhost:8421/callback— copy this exactlyClick Save
After saving, the form shows a Client ID — a long code that Suunto generated for you. Copy it.
What are these three things? — Client ID: your app's username, generated by Suunto — Client Secret: your app's password, chosen by you — Redirect URI: where Suunto sends you back after you approve access — must match exactly, typos break it
The Client Secret is never shown again after you save. If you forget it, just set a new one in the same form.
Step 4: Get your subscription key (do this after approval)
The subscription key is a second passcode that goes on every data request. Here's how to find it:
Still on the apizone profile page
Scroll to the Subscriptions section
Your Developer API subscription is listed there. Next to it you'll see a Primary Key — click the button next to it to reveal it, then copy the key.
Save all three values before continuing — you'll need them in Part 2:
Client ID (from the OAuth app form above)
Client Secret (the password you made up)
Subscription Key (from the Subscriptions section)
Part 2: Install and configure (~10 min, after Suunto approves you)
Used the "Let Claude Code install it" option above? Claude already ran every command below — skip to Part 3. These steps are for anyone doing it by hand.
Step 5: Download the code
Open Terminal on Mac (press ⌘Space and type "Terminal") or Command Prompt on Windows. Then run these commands one at a time:
git clone https://github.com/googlarz/suunto-mcp
cd suunto-mcp
npm install
npm run buildThis downloads the code, installs what it needs, and builds it. Takes 1–2 minutes. If you see any errors, check the Troubleshooting section.
Step 6: Add your credentials
You'll create a file called .env in the suunto-mcp folder and put your three values in it. Even if Claude is doing the rest of this for you, type these three values in yourself rather than pasting them into the chat — keeps them out of your conversation history.
On Mac:
cp .env.example .env
open -e .envThis copies the template and opens it in TextEdit. Replace each placeholder with your actual values, then save and close.
On Windows:
copy .env.example .env
notepad .envThe file looks like this — replace the parts after the = signs:
SUUNTO_CLIENT_ID=your-client-id-here
SUUNTO_CLIENT_SECRET=your-client-secret-here
SUUNTO_SUBSCRIPTION_KEY=your-subscription-key-hereSave and close the file.
Only if you want to push guided workouts to your watch (see What you can push), add one more line:
SUUNTO_APP_NAME=your-app-name-here— it must exactly match the app name you registered on apizone.suunto.com in Step 3, or the watch will reject the upload. Not needed for anything else.
Step 7: Pair your Suunto account
npm run authHere's exactly what happens:
① Terminal — you see a long URL printed and the message "Opening Suunto authorization in your browser…"
② Browser opens — Suunto's login page appears. It looks identical to the Suunto app login: email + password fields at the top, then "Sign in with Apple" and "Sign in with Facebook" below. Sign in with whichever you use.
③ Permissions screen — after logging in, a screen appears asking you to approve access for "suunto-mcp". It lists what the app will be able to read (your workouts). Click Authorize.
④ Browser confirmation — the page shows: "Suunto MCP connected. You can close this tab."
⑤ Terminal confirmation — prints: "Paired successfully. Tokens saved."
Done — you won't need to do this again. The connection stays active and renews itself automatically.
Browser didn't open automatically? Copy the long URL from the terminal and paste it into your browser manually.
Step 8: Check everything is working
npm run doctorThis runs a health check. You should see output like:
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 200)
✓ Pairing paired (user: your-username), token expires in 47 min
✓ API probe (list_workouts) received 1 workoutThe exact lines vary a little, and more appear for the Sleep, Recovery and Daily Activity products. If any line shows ✗, the message tells you exactly what to fix — resolve it before moving on. A ! is only a warning (for example, an optional product you haven't subscribed to).
Part 3: Connect to Claude Desktop (~5 min)
Used the "Let Claude Code install it" option? This part is done too — Claude edited your config directly. Restart Claude Desktop and jump to Step 11.
Now you'll tell Claude Desktop where to find Suunto MCP.
Step 9: Open the Claude config file
Open this file in a text editor (create it if it doesn't exist yet):
Mac:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
Quick way on Mac — run this in Terminal:
mkdir -p ~/Library/Application\ Support/Claude && open -e ~/Library/Application\ Support/Claude/claude_desktop_config.jsonQuick way on Windows — run this in Command Prompt:
notepad "%APPDATA%\Claude\claude_desktop_config.json"(If it asks "file not found — create it?", click Yes.)
Step 10: Add Suunto MCP
First, find the actual path to the suunto-mcp folder. In Terminal, while inside the folder, run:
pwdIt'll print something like /Users/yourname/suunto-mcp. Copy that.
Now paste the following into the config file. Replace /Users/yourname/suunto-mcp with the path you got from pwd, and replace the credential placeholders with your actual values.
If the file already has other servers configured, don't replace the whole file — just add the
"suunto"section alongside them. The structure must be valid JSON, so keep all the curly braces balanced. When in doubt, compare your file to the example below.If the file is empty, paste the whole block as-is.
{
"mcpServers": {
"suunto": {
"command": "node",
"args": ["/Users/yourname/suunto-mcp/dist/index.js"],
"env": {
"SUUNTO_CLIENT_ID": "your-client-id",
"SUUNTO_CLIENT_SECRET": "your-client-secret",
"SUUNTO_SUBSCRIPTION_KEY": "your-subscription-key"
}
}
}
}To also push guided workouts to your watch, add one more line inside "env": put a comma at the end of the SUUNTO_SUBSCRIPTION_KEY line, then add "SUUNTO_APP_NAME": "your-app-name" (exactly the app name registered on apizone) on a new line after it. If you only ask Claude about your data, you don't need it.
Anything the server needs must be in this "env" block — a .env file is only read by the terminal commands (npm run auth, npm run doctor, the CLI) when you run them from this folder.
Save the file.
Step 11: Test it
Three things, in order — this is the only part of setup nothing can do for you:
Quit Claude Desktop completely. Not just closing the window — ⌘Q on Mac, or right-click the icon in the taskbar → Quit on Windows. Config changes only load on a fresh start.
Reopen Claude Desktop.
Ask it:
"What was my most recent workout?"
If Claude answers with your actual sport, date, and distance — you're done. If it doesn't, don't guess — go to Troubleshooting.
Example conversation
You: Compare my last three long runs. Has my heart-rate drift improved?
Claude: Looking up your 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.What data is available
Category | What you can ask about | Requires |
Workouts | Any recorded activity — runs, hikes, rides, swims, ski tours. Distance, time, heart rate, pace, elevation, power, and lap-by-lap data for guided sessions. | Developer API (already subscribed) |
Sleep | Sleep duration, stages (light/deep/REM), sleep score. | Sleep API subscription on apizone |
Recovery | Recovery balance and stress state through the day (HRV comes with the sleep data). Balance is the watch's Resources percentage (0.96 = 96%); stress state 1 = Recovering, 2 = Active. The app's single Recovery state percentage is not in the API. | Recovery API subscription on apizone |
Daily activity | Steps, calories, 24/7 heart rate. | Daily Activity API subscription on apizone |
To add sleep, recovery, or daily activity: go back to apizone.suunto.com, find each product, and subscribe. Then run npm run doctor to confirm they're active.
What you can push to your watch
Suunto MCP isn't read-only. Claude can also send things back to your account:
You want to | Ask Claude | Requires |
Get a gym plan on your wrist | "Plan today's workout and send it to my watch" |
|
Upload a workout from another device | "Upload this FIT file as a Suunto workout" | — |
Grab a saved route as GPX | "Export my Sunday route as GPX" | — |
Guided workouts show up as a SuuntoPlus Guide: exercise name and weight/reps on screen, lap button advances to the next one, a stopwatch (not a countdown) between exercises with a preview of what's next, a vibrate when a new exercise starts, and a "session complete" screen at the end. There's no live push to the watch itself — it appears after your phone's next normal Suunto app sync, same as any other watch data.
This pairs naturally with a coaching workflow: describe your goals, equipment, and current lifts to Claude, and it can write a real progressive program and push each session directly — see Pairs well with health-skill below for recovery-aware programming.
Prompts
MCP clients that support prompts (for example Claude Desktop) list these as one-click workflows. Each fills in the exact dates and tells Claude which tools to call.
Prompt | What it does | Optional argument |
| Morning briefing: last night's sleep, recovery on waking, yesterday's load and workouts, how ready today is |
|
| Weekly review: sleep, HRV, recovery and training load against the week before, each gym session against its plan |
|
| Readiness gate: cut planned intensity 20-30% if HRV is clearly below your recent level or sleep score is low, otherwise train as planned |
|
Daily health digest
Ask Claude "generate my daily digest for yesterday" and it writes a color-coded markdown summary — steps, sleep, recovery balance, HRV, and a training-load model (Fitness/Fatigue/Form) — appended to SUUNTO_HISTORY.md in the folder the server runs from (set SUUNTO_DIGEST_HISTORY_PATH to choose another file).
Fitness (CTL), Fatigue (ATL), and Form (TSB) aren't Suunto API fields — there's no endpoint for them. They're computed here from each workout's real tss.trainingStressScore using standard 42-day/7-day exponential decay, the same math training-load tools like TrainingPeaks use. The running values persist in ~/.suunto-mcp/averages.json (override with SUUNTO_DIGEST_AVERAGES_PATH) since there's nowhere else to keep them.
How close is it to the watch? Checked on one day against the Suunto app: Fatigue matched (11), Form matched (2), Fitness came out about 1 lower (13.1 vs 14). Treat Fitness as ±1 of your watch; seed it from the app if you need it exact.
A few things worth knowing before you rely on it:
CTL/ATL start at 0 on first use and take 4–6 weeks to converge to a realistic number — there's no API to read your watch's own displayed Fitness/Fatigue. To skip the cold-start, tell Claude the numbers off your watch on your very first digest ("my watch shows Fitness 42, Fatigue 38, seed the digest with those") — or pass
--seed-ctl 42 --seed-atl 38on the CLI. Only works on the first-ever digest; ignored after that.TSB colors match your watch's own legend (🔵 Optimal >+10, 🟢 Balanced 0 to +10, 🟡 Compromised −10 to 0, 🔴 Strained <−10) — not an invented scale.
Ramp rate (this week's CTL vs. 7 days ago) has its own scale: 🔴 above +8/week means you're loading too fast — real injury risk, not just "good progress." 🟢 +3 to +8 is building well, 🟡 −2 to +2 is holding steady, 🟠 below −2 means fitness is slipping.
Recovery Balance is reported as morning (the lowest point overnight) vs. peak (the highest point that day) — they use different color scales, since peak is naturally higher than the overnight low.
HRV below your normal range for 2+ days in a row, or morning recovery below 65% for 2+ days in a row, adds a note to check your blood pressure — sustained low HRV/recovery is a real physiological signal worth a second data point on.
Rolling baselines track each metric separately, with a separate bucket for "party nights" (>20,000 steps) so an outlier day doesn't skew your normal-day average.
Run dates in chronological order. The digest refuses a date on or before the last one it processed, so a missed day can't be filled in after a later one has run — the running totals move forward one day at a time.
Requires Sleep and Recovery API subscriptions on apizone for those sections to populate — without them, the digest still generates, those sections just say "no data" instead of erroring.
CLI: suunto-mcp daily-digest 2026-04-20 [--seed-ctl 42 --seed-atl 38]. MCP tool: generate_daily_digest.
Troubleshooting
Always run npm run doctor first — it pinpoints most problems automatically.
What you see | What it means | How to fix it |
Claude returns an error or nothing | Something isn't connected yet | Run |
Empty workout list | Watch hasn't synced recently | Open the Suunto app on your phone and wait for the sync to complete |
"Not authenticated" | The pairing step didn't finish | Run |
You logged in but nothing happened | The browser tab closed or timed out before Suunto confirmed | Close all Suunto tabs and run |
"Token request failed" or "400 error" | Client Secret or Redirect URI don't match apizone | Go to apizone → profile → OAuth application settings and confirm both values match exactly |
"401" error on every request | Usually the subscription key is wrong or incomplete | Go to apizone → profile → Subscriptions, reveal and re-copy the Primary Key |
"401" with "RateLimitExceeded" | Suunto's gateway reports a rate limit as 401 | Wait about 2 minutes and ask again — the key is fine |
"401" with "OperationNotFound", or an error from a hidden legacy tool | Suunto no longer serves that endpoint ( | Nothing to fix on your side; re-pairing will not help |
"403 Forbidden" on workouts | Developer API subscription isn't active | Sign in to apizone and confirm it's listed as Active |
Sleep / recovery / activity returns "not found" | Those need separate subscriptions | Go to apizone and subscribe to the Sleep, Recovery, or Daily Activity API |
Laps missing, doubled or out of order in a guided gym session | The watch's buttons were locked, the exercise was restarted, or a lap was pressed outside the guide | Ask for |
A workout has no heart rate | Heart-rate tracking was off, e.g. battery mode Tour | Choose a different battery mode before the workout; the data can't be recovered afterwards |
Daily calories look far too low | The daily-statistics energy looks like active energy, not total (resting + active) — this is not verified against the watch | Treat it as active kcal; don't read it as total daily burn |
Numbers differ from the Suunto app | Balance equals the app's Resources % (0.96 = 96%). The app's single Recovery state % is not available through the API. Fitness (CTL) computed here can be about 1 point off the watch | Use the app for Recovery state %; seed the digest from the app if you need CTL exact |
Got an SSL error after Apple sign-in | Known Suunto quirk with Apple login | Close the error tab, go back to the auth URL the terminal printed, and continue |
"State mismatch" error | A second auth flow started before the first finished | Close all auth-related tabs and run |
| Node.js version too old or not installed | Run |
Terminal says "EADDRINUSE" or port in use | Something else is using port 8421 | Restart your computer, or run |
Guide upload fails with an "owner" error |
| Check apizone → your app → confirm the exact name, fix the env var, restart Claude |
Pushed a guide but it's not on the watch | Watch hasn't synced with your phone yet | Open the Suunto app and let it sync; it should appear without any extra steps |
FAQ
Is this safe? Will Suunto lock my account? Suunto built this API specifically for people to connect their own tools — it's explicitly allowed. You're using it exactly as intended.
Is my data leaving my computer? Your data travels directly between your computer and Suunto's servers. Suunto MCP is just the bridge. When Claude asks about your workouts, it goes: Claude → Suunto MCP (on your machine) → Suunto's servers → back. No third-party services see your data.
Which Suunto watches work? Any watch that syncs to the Suunto app: Race, Vertical, 9 Peak Pro, 9 Peak, 5 Peak, Wing, Ocean, and older models. If it appears in your Suunto app, it works here.
Do I need to do anything when I record a new workout? No. Just ask Claude — it always pulls live data from Suunto.
What if I want to disconnect and stop using this? See Disconnecting below. You can fully revoke access in under a minute.
Can I use this with AI apps other than Claude? Yes — anything that supports MCP: Claude Code, Cursor, Windsurf, and others.
My Suunto app username is different from my email — which do I use? Use your email address to sign in to apizone. Your username will appear once you're authenticated.
Privacy
All data flows directly between your computer and Suunto's servers. No third-party servers, no analytics.
Your login credentials are stored locally at
~/.suunto-mcp/tokens.json— not uploaded anywhere.Suunto shows your connected app as "suunto-mcp" in apizone → profile → Authorized applications. You can revoke it there at any time.
The AI only sees data it explicitly requests for your question — not your entire history at once.
Upgrading
npx:
npx -y suunto-mcpfetches the newest version on a fresh start; if you pinned a version inargs, change it. Restart Claude afterwards.Cloned folder:
git pull,npm install,npm run build, then restart Claude.
See CHANGELOG.md for what changed.
Environment variables
Set these in the env block of your Claude config (or in .env for terminal commands).
Variable | Needed | What it does |
| yes | Your apizone credentials |
| to push guides | Exact app name registered on apizone |
| no | OAuth callback; default |
| no | Token file; default |
| no |
|
| no | Don't open a browser during pairing; copy the URL yourself |
| no | Where the daily digest writes |
| webhook only | See the Webhooks section |
Disconnecting
To fully remove access:
Log in to apizone.suunto.com → profile → Authorized applications → remove suunto-mcp. Suunto immediately stops honoring the connection.
Delete local credentials:
rm -f ~/.suunto-mcp/tokens.jsonIf you used
SUUNTO_TOKEN_STORAGE=keychain, delete the "suunto-mcp" entry in your system keychain instead. The daily digest also keeps~/.suunto-mcp/averages.jsonandSUUNTO_HISTORY.md— delete them too if you want the health data gone.Remove the
"suunto"block from your Claude config and restart Claude.
Pairs well with health-skill
If you use googlarz/health-skill — a Claude skill for symptom triage and health Q&A — you can connect both to Claude: health-skill handles the health side while Claude reads your training, sleep, and recovery data from Suunto MCP in the same conversation. Together they can answer questions like "given my recovery scores this week, should I keep tomorrow's interval session?" with real numbers.
The same combination works for planning, not just Q&A: Claude can check your actual HRV and sleep before writing a session, scale it back on a bad recovery day instead of a generic one, and push the result straight to your watch with push_strength_guide (gym) or push_interval_guide (cardio). Ask for it directly — "check my recovery and plan today's gym session" — no extra setup beyond having both connected.
To also store your daily step counts in health-skill's own records, run suunto-mcp sync-to-health-skill --health-root <your health folder> (add --person-id for a household member). It copies steps only, and only for days that are finished — today's total is picked up tomorrow.
For the full version — real progressive-overload programming that persists week to week instead of a one-off ask — install googlarz/gym-skill: /gym setup once, then /gym plan//gym today//gym log//gym review going forward.
Advanced
The package is published on npm as suunto-mcp, so you can skip Step 5 (no git clone, no build). You still need Node.js 20 or newer and the credentials from Part 1.
Pair your account (replaces Step 7). Run these in a terminal, with your own values:
export SUUNTO_CLIENT_ID=your-client-id SUUNTO_CLIENT_SECRET=your-client-secret SUUNTO_SUBSCRIPTION_KEY=your-subscription-key
npx -p suunto-mcp suunto-mcp-auth
npx -p suunto-mcp suunto-mcp-doctorThe first command opens the browser pairing page and saves your tokens to ~/.suunto-mcp/tokens.json; the second checks that everything is connected.
Connect to Claude Desktop (replaces Step 10). Use npx instead of a path to the code:
{
"mcpServers": {
"suunto": {
"command": "npx",
"args": ["-y", "suunto-mcp"],
"env": {
"SUUNTO_CLIENT_ID": "your-client-id",
"SUUNTO_CLIENT_SECRET": "your-client-secret",
"SUUNTO_SUBSCRIPTION_KEY": "your-subscription-key"
}
}
}
}The first start downloads the package, so it can take a few seconds. To stay on one version, write "suunto-mcp@<version>" in args (for example the current one from npm). In Claude Code: claude mcp add suunto -e SUUNTO_CLIENT_ID=... -e SUUNTO_CLIENT_SECRET=... -e SUUNTO_SUBSCRIPTION_KEY=... -- npx -y suunto-mcp.
Run this in a terminal (use your own values and the real path to this folder), then claude mcp list to verify it's loaded:
claude mcp add suunto -e SUUNTO_CLIENT_ID=your-client-id -e SUUNTO_CLIENT_SECRET=your-client-secret -e SUUNTO_SUBSCRIPTION_KEY=your-subscription-key -- node /full/path/to/suunto-mcp/dist/index.jsAdd -e SUUNTO_APP_NAME=your-app-name if you want to push guided workouts.
After building, you can query Suunto data directly without Claude. From the cloned folder use node dist/index.js <command>; with the npm package use npx suunto-mcp <command> (or just suunto-mcp if installed globally):
node dist/index.js list-workouts --limit 10
node dist/index.js get-workout <workoutKey>
node dist/index.js get-workout-laps <workoutKey>
node dist/index.js get-daily-snapshot 2026-04-20
node dist/index.js get-daily-snapshot 2026-04-14 --to 2026-04-20
node dist/index.js get-sleep 2026-04-20
node dist/index.js list-recovery --from 2026-04-01 --to 2026-04-30Other commands: get-workout-fit, get-daily-activity, list-daily-activity, list-sleep, get-recovery, daily-digest, sync-to-health-skill. Run it with --help for the full list and every option. All output is JSON — pipe into jq for filtering.
npm run webhookStarts an HTTP receiver on port 8422 that logs workout events as they arrive. It listens on 127.0.0.1 only, so expose it through a tunnel (cloudflared, ngrok) or a reverse proxy and register the URL in apizone → webhooks.
Set SUUNTO_WEBHOOK_SECRET to the notification secret you configure in apizone's OAuth application settings. Every request is verified against Suunto's X-HMAC-SHA256-Signature header and rejected with 401 if it doesn't match. Without the secret the receiver rejects everything (set SUUNTO_WEBHOOK_ALLOW_UNSIGNED=1 only for local testing). Other options: PORT, SUUNTO_WEBHOOK_HOST (default 127.0.0.1), SUUNTO_WEBHOOK_LOG (default ~/.suunto-mcp/webhooks.ndjson, written private and capped at 100 MB).
Most users can skip this whole section — asking Claude on demand is simpler.
To store your Suunto login tokens in your OS keychain (macOS Keychain, Windows Credential Manager) instead of a file:
SUUNTO_TOKEN_STORAGE=keychain npm install @napi-rs/keyring
SUUNTO_TOKEN_STORAGE=keychain npm run authClaude picks the right tool automatically — you don't need to know these. For the curious:
Workouts
Tool | What it does |
| Recent workouts, filtered by date |
| Summary numbers for one workout (no laps — use |
| One call for a whole day: last night's sleep (main sleep, naps), the day's lowest and highest recovery balance (and the value on waking and at bedtime), steps, energy and workouts. Pass |
| Fitness (CTL), Fatigue (ATL) and Form (TSB) day by day, plus weekly totals and the 7-day ramp rate, computed from workout TSS (Suunto's API has no such numbers). Up to 120 days ending at |
| Lap-by-lap table for one workout: time, heart rate, calories and the guide step of each lap — the way to read back a guided gym session set by set; it also reports when the table looks unreliable (a missing or doubled lap, no heart rate) |
| Reads a guided gym session back against its plan: maps the laps to exercises, sets and rests and lists deviations (missing or extra sets and exercises, short or long sets, rests off target, laps outside the guide), each marked with what it rests on (the labels, your plan, or a heuristic). Pass the exercises array you pushed as |
| Raw FIT file decoded to structured data (large — |
| Hidden by default — Suunto's servers reject this call (OperationNotFound, checked on one account, September 2026); set |
| Hidden by default — Suunto's servers reject this call (OperationNotFound, checked on one account, September 2026); set |
24/7 health (requires individual product subscriptions on apizone)
Tool | What it does |
| Steps, calories, daily heart rate |
| Sleep stages, duration, score |
| Recovery balance and stress state through the day |
| Aggregated daily stats over a date range |
Routes
Tool | What it does |
| Your newest saved routes (Suunto returns at most 5) |
| One route by its ID — works for routes |
| Export a route as GPX |
Uploads & guided workouts (write — send data back to your account)
Tool | What it does |
| Upload a FIT file as a new workout — Suunto's own docs say only FIT is currently supported for this endpoint, despite what you might read elsewhere |
| Import a GPX file as a route (one route per |
| Check whether an upload finished processing |
| Push a structured workout (exercises, weights, rest, notifications) as a SuuntoPlus Guide — requires |
| Push an auto-advancing interval/cardio guide (timed or distance-based segments, target HR ranges) — requires |
| Push a per-set strength session (prep stopwatch, set/rest lap tracking, configurable rest mode) — requires |
| List all SuuntoPlus Guides on your account |
| Permanently delete a guide by id |
Other
Tool | What it does |
| Hidden by default — Suunto's servers reject this call (OperationNotFound, checked on one account, September 2026); set |
| Writes the daily health digest to |
Credits
Suunto APIzone — for opening their API to everyone
Model Context Protocol — the standard this speaks
fit-file-parser— FIT binary decoding
License
MIT — use it, fork it, improve it.
Available Tools
26 toolscompare_workout_to_planCompare workout to planARead-only
Compares one guided gym session with its plan: maps the manual laps (the same data get_workout_laps returns) to exercises, sets and rests, and reports planned vs actual with honest deviations. Works for sessions pushed with push_strength_guide (lapGranularity perSet or perExercise, restMode countdown or stopwatch) and push_workout_guide. The plan — exercise order, set counts, rest targets — is read from the lap labels, so plan is optional; pass the exercises array you pushed to also check the session against it (missing, extra or reordered exercises, set counts, rest targets). Output: { workoutKey, guide, status ('ok' | 'no-laps' | 'no-guide-labels' | 'unknown-label-format'), format ('strength-perSet' | 'strength-perExercise' | 'workout'), restMode ('countdown' | 'stopwatch' | null), complete (the guide's 'Session complete' lap was reached), reliable, note, checks (get_workout_laps' checks, passed through), lapCount, baselineHr (heart rate of lap 1, the first prep — null for workout guides), cols, exercises: [{ n, name, detail, plan (index into plan or null), planned: { sets, restS }, prep, sets, rests }], afterComplete, unattributed, deviations: [{ code, basis ('labels' | 'plan' | 'heuristic'), ex, plan, laps, detail }] }. prep, sets, rests, afterComplete and unattributed rows are arrays in the column order given by cols. A rest row's peak is the rest lap's max HR and end its min HR (optical HR lags 20–30 s, so the peak lands in the rest, not in the set); endVsBaseline is end minus baselineHr. Deviation codes: unexpected-sequence, rest-split, rest-short, rest-long, short-set, long-set, missing-set, extra-set, rest-target-mismatch, extra-exercise, ended-early, unlabelled-lap, after-complete, order-differs, missing-exercise. reliable is false when checks has an entry other than no-heart-rate (which only nulls the heart-rate fields) or the laps break the guide's step order; the mapping is then best effort and note says why. With restMode countdown a rest lap always lasts exactly its target (the watch advances by itself), so extra rest ends up inside the next set's lap and is not measurable. About 1–4 KB. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| plan | No | Optional: what the session was meant to be, in order. Pass the exercises array given to push_strength_guide or push_workout_guide as-is — only name, sets and restSec are read. Without it the plan is read from the lap labels alone. | |
| workoutKey | Yes | The 24-character workoutKey returned by list_workouts. Anything else fails with a not-found error without calling Suunto. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite annotations already indicating a read-only operation, the description adds substantial behavioral detail: output statuses, deviation codes, reliability rules, and the countdown-rest caveat. It also explains that unreliable mappings are best effort and why, which is far beyond what the annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and usage are front-loaded, and most sentences carry necessary detail because there is no output schema. The main weakness is density: the long single paragraph mixes output structure, edge cases, and caveats without visual separation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex tool with no output schema, the description is highly complete: it describes the return shape, statuses, columns, deviation codes, reliability behavior, and rest-mode limitations. An agent has enough information to interpret results and handle edge cases.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so parameter basics are already documented, but the description adds meaningful semantics: `plan` is optional, only `name`, `sets`, and `restSec` are read, and without it the plan is inferred from lap labels. This helps an agent decide whether and how to supply `plan` beyond the schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: it compares one guided gym session to its plan, maps manual laps to exercises/sets/rests, and reports planned versus actual. It also names related tools and guide types (`get_workout_laps`, `push_strength_guide`, `push_workout_guide`), making it distinguishable from siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clearly states the supported session types and that `plan` is optional, including when to pass the original exercises array for deeper checks. It does not explicitly say when to use `get_workout_laps` instead, but the intended context is clear from the description.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_guideDelete SuuntoPlus guideADestructiveIdempotent
Permanently deletes one SuuntoPlus Guide from the user's account by id. Use list_guides to find the id. This removes it from the Suunto app / apizone catalogue; it does not reach into the watch to un-pin a copy already synced there. Write operation (irreversible).
| Name | Required | Description | Default |
|---|---|---|---|
| guideId | Yes | Guide id, from list_guides or from a previous push_*_guide response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true, idempotentHint=true, and readOnlyHint=false, so safety is partly covered. The description adds valuable behavioral context beyond that: permanence, irreversibility, and the important limitation that it does not un-pin a copy on the watch.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four tight sentences with zero filler; the core action and its id lookup guidance are front-loaded, and the watch-copy caveat is a single clarifying clause.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter destructive tool with no output schema, the description covers the action, the id source, the irreversibility, and the key side-effect boundary, which is everything an agent needs to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single guideId parameter is already documented with its provenance. The description only reiterates 'by id' and points to list_guides, adding little beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (deletes), resource (one SuuntoPlus Guide), and scope (by id, from the user's account) in the first sentence, immediately distinguishing it from sibling push_*_guide and list_guides.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent to use list_guides to find the id, and clearly scopes what the operation does and does not affect (account/catalogue removal vs. a synced watch copy). Nothing about when to invoke it is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
export_routeExport route as GPXARead-only
Exports a saved Suunto route as a GPX 1.1 XML string. Suitable for import into navigation apps (Komoot, Strava, Garmin Connect, etc.). Use list_routes to discover valid route IDs. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| routeId | Yes | Route ID returned by list_routes. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so 'Read-only' is largely a restatement. The description earns credit beyond that by disclosing the concrete return type (GPX 1.1 XML string) and the interoperability intent, which the annotations do not cover. No auth or rate-limit notes, but none are needed for this read-only export.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with the operation and output format, followed by relevance and prerequisite. Every sentence carries distinct information with no padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description correctly fills the gap by stating the return value is a GPX 1.1 XML string. Combined with the prerequisite pointer and read-only status, an agent has everything needed to call this one-parameter tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single routeId parameter is already documented in the schema as 'Route ID returned by list_routes.' The description's 'Use list_routes to discover valid route IDs' essentially repeats that provenance rather than adding format or constraint detail, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource ('Exports a saved Suunto route') and even names the output format (GPX 1.1 XML string), which is unusually specific. It does not, however, explicitly differentiate itself from the sibling export_workout_gpx, leaving the agent to infer the route-vs-workout distinction from the resource name alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides clear context for when the output is useful ('import into navigation apps such as Komoot, Strava, Garmin Connect') and names the prerequisite discovery tool (list_routes). It stops short of an explicit when-not or an alternative export tool comparison, but the usage context is well conveyed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
generate_daily_digestGenerate daily health digestADestructiveIdempotent
Builds a color-coded daily health digest (steps, sleep, recovery balance, HRV, and a training-load model) for one date and appends it as markdown to a history file. Suunto's API has no fitness/fatigue endpoints, so this computes CTL (42-day fitness), ATL (7-day fatigue), and TSB (form) from each workout's tss.trainingStressScore using standard exponential time constants, persisting the running values in a local sidecar file (SUUNTO_DIGEST_AVERAGES_PATH env var, default ~/.suunto-mcp/averages.json) since there's nowhere else to store them. Running-average baselines (all days so far) per metric are also tracked there, with a separate baseline bucket for 'party nights' (>20,000 steps) so those don't skew the normal-day average. Requires Sleep and Recovery API subscriptions on apizone for the sleep/recovery sections to populate — falls back to 'no data' text for sections without a subscription rather than erroring. Write operation (updates the sidecar file and appends to the history file).
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Calendar date YYYY-MM-DD to summarize. Use yesterday or earlier — today's data is usually partial until the watch has synced. | |
| seedAtl | No | Same as seedCtl but for Fatigue (ATL). Only used on the very first digest ever run. | |
| seedCtl | No | Only used on the very first digest ever run (no prior sidecar file). Anchors the starting Fitness (CTL) value to the number shown on the user's watch instead of cold-starting at 0. Ask the user for their watch's displayed Fitness value if this is their first digest. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Far exceeds the annotations (which only say non-read-only, destructive, idempotent, open-world). It discloses the CTL/ATL/TSB computation from tss.trainingStressScore, the sidecar file and env var used for persistence, the baseline bucketing for 'party nights', the subscription-dependent fallback, and explicitly labels itself a write operation that appends to a history file.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but dense, and every sentence carries required information given the tool's complexity. It is front-loaded with the purpose, then proceeds to computation, persistence, prerequisites, and side effects in a logical order with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers behavior, side effects, prerequisites, and fallbacks for a complex tool with no output schema. Remaining gaps are minor: the history file location/format and what the call returns to the caller are not described, though the sidecar path is.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description adds domain context for the date (via the workload) but says nothing about seedCtl/seedAtl beyond what the schema already documents, so it does not meaningfully extend parameter meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource: builds a color-coded daily health digest covering steps, sleep, recovery, HRV and a training-load model for one date. No sibling tool does anything comparable, so differentiation is inherent, and the side effects (sidecar update, markdown append) are stated up front.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear operating context: one date per call, requires Sleep and Recovery API subscriptions for those sections, and falls back to 'no data' instead of erroring. It does not name an alternative tool or an explicit when-not-to-use condition, but there is no overlapping sibling to route against.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_daily_activityGet daily activityARead-only
Returns the 24/7 activity samples for one local calendar day (00:00–23:59 in the local time the watch stamped on each sample) from the /247samples API, as a plain array of { timestamp (ISO 8601 with UTC offset), entryData: { HR (bpm), StepCount, EnergyConsumption (joules, as in get_daily_activity_statistics) } } — 144 rows for a full day, one per 10 minutes (138 or 150 on the days the clocks change). A day without synced data returns []. Use list_daily_activity for a date range. Requires 24/7 Activity API subscription on apizone. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Calendar date YYYY-MM-DD (a local day, in the local time the watch stamped on each sample). Data arrives when the watch syncs, so today's is usually partial — the API answers 200 with an empty or partial payload for today and future dates rather than an error (confirmed live). Use yesterday or earlier for complete results. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint and openWorldHint; the description adds substantial behavior beyond that — 144 rows at one per 10 minutes, 138/150 rows on DST change days, empty array for unsynced days, and the local-time stamping of each sample. This is exactly the extra context annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but well-structured: day scope, source API, return shape, row count, DST exception, empty case, alternative, prerequisite, and read-only marker all in one sentence. The return-shape detail is front-loaded enough to be usable, though the em-dash clause is heavy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully specifies the return value: array of objects with timestamp (ISO 8601 + offset) and entryData fields with units, plus row count and the empty-day case. An agent has everything needed to call and interpret this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the date parameter already documents format, pattern, examples, and the partial-today behavior, so the schema carries the load. The description's restatement of local-day semantics adds little beyond what the parameter description already states, matching the baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (returns 24/7 activity samples for one local calendar day) with a precisely scoped resource, underlying API endpoint, and day definition. It explicitly names the sibling list_daily_activity as the range alternative, letting an agent distinguish it without reading any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear routing rule ('Use list_daily_activity for a date range') and a precondition (requires 24/7 Activity API subscription on apizone). It does not restate the today/future partial-data caveat here, though the schema parameter description covers it, so usage context is clear but not fully self-contained.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_daily_activity_statisticsGet daily activity statisticsARead-only
Returns aggregated daily step count and energy consumption (joules) from the /247 API for the given datetime range. Response is an array of AggregatedActivityData objects, each with a Name ('stepcount' or 'energyconsumption'), Aggregation ('sum'), and Sources array containing per-device Samples with TimeISO8601 and Value. The window must be less than 28 days (exactly 28 is rejected). Samples with null Value indicate no data synced for that day. Each daily Sample is stamped local noon (TimeISO8601 like 2026-09-27T12:00:00+02:00); a one-day window (startdate = enddate = D) was observed returning the samples for D and the day after, so select samples by the date in TimeISO8601 rather than summing the response. Prefer this tool over list_daily_activity when you need totals rather than intraday time-series. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| enddate | Yes | End datetime in ISO-8601 format, same forms as startdate (e.g. 2026-04-27T23:59:59). Must be less than 28 days after startdate. | |
| startdate | Yes | Start datetime in ISO-8601 format, with or without a UTC offset (e.g. 2026-04-01T00:00:00 or 2026-04-01T00:00:00+02:00). An offset written +0200, as `date +%z` prints it, is rewritten to +02:00 because Suunto rejects the former. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover readOnlyHint/openWorldHint; the description goes far beyond them with the hard 28-day window limit, null-Value semantics (no data synced that day), local-noon timestamp stamping, and the observed one-day-window off-by-one quirk with an explicit workaround (select samples by TimeISO8601 date rather than summing). It also restates read-only, which is consistent with, not contradicting, the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and data source, then return shape, constraints, edge cases, and sibling routing in that order. Sentences are dense but each carries distinct operational information; nothing is restated filler despite the length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the full burden of describing the return value and does so precisely: an array of AggregatedActivityData with Name, Aggregation, and Sources/Samples shape. Combined with the range constraint and timestamp caveat, an agent has everything needed to call and interpret this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so baseline is 3. The description adds real parameter-relevant meaning: the strict 'less than 28 days, exactly 28 rejected' boundary nuance beyond the schema's looser 'must be less than 28 days after startdate', plus the guidance to select samples by the TimeISO8601 date because of how start/end boundaries behave.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb and resource (aggregated daily step count and energy consumption) and states the backing endpoint (/247 API) plus the required datetime range. It also distinguishes itself from the sibling list_daily_activity by contrasting totals vs. intraday time-series, so an agent can choose without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: 'Prefer this tool over list_daily_activity when you need totals rather than intraday time-series.' The constraint 'window must be less than 28 days (exactly 28 is rejected)' tells the agent when a call will fail, which is actionable selection guidance rather than inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_daily_snapshotGet daily snapshotARead-only
One call for "how was this day, and the night before it": the aggregation the other tools leave to the caller. Output: { date, sleepNightOf, sleep, recovery, activity, workouts, errors }. sleep describes the NIGHT THAT LED INTO the date (sleepNightOf = the previous date, i.e. sleeps that began between noon on the previous day and noon on the date): { main (the longest non-nap sleep: sleepId, bedtimeStart, bedtimeEnd, durationS, deepS, lightS, remS, score, avgHrv, hrAvg, hrMin, spo2Max, latencyS, wasoS, wakeBeforeOffBedS — all durations in seconds: time to fall asleep, awake after falling asleep, awake in bed before getting up), otherNights (further non-nap sleeps, when the watch split a night), nightSleepS (total of main + otherNights, null when there is none), naps }. Suunto marks any sleep shorter than about 3 hours as a nap, so a short night appears under naps with main null. recovery covers the local calendar day: { samples, low: { balance, at }, high, first, last, morning: { balance, at } (the sample nearest the main sleep's bedtimeEnd — the waking value), atBedtime: { balance, at } (nearest its bedtimeStart, which falls on the previous local day), both null when there is no main sleep or no sample within an hour, stressStateSamples (samples per StressState) } or null without data. low is the day's lowest balance — not necessarily overnight (after an evening workout it can fall in the evening). activity: { steps, energyKcal } for the local day — energyKcal is the daily-statistics energy converted from joules; real days come out around 700-1,500 kcal, well below a resting rate, so it looks like ACTIVE energy rather than a total (not verified against the watch). A value is null, never 0, when Suunto has no sample for the date. workouts: the day's workouts (by their own local date) with { workoutKey, activityId, startLocal, totalTimeS, kcal, hrAvg, hrMax, tss (HR method), guide, hasLaps } — pass a workoutKey with hasLaps to get_workout_laps. Each section is fetched independently: one that fails is null and explained in errors, the others are still valid. With to, returns { from, to, days: [...], errors } instead (errors is shared by the whole range; a failed section is null in every day). Use this instead of combining get_sleep, get_recovery, get_daily_activity_statistics and list_workouts by hand. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | Optional last day of a range (inclusive, at most 14 days from `date`). The result is then { from, to, days: [one entry per day, in the shape above without errors], errors } — one request per section for the whole range, so prefer it to calling this once per day. | |
| date | Yes | The local calendar day YYYY-MM-DD (the first day when `to` is given). Use yesterday or earlier for a complete day; today's data is partial until the watch has synced, and the night that led into today may still be in progress. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnly/openWorld, so the description carries the real burden and delivers: per-section independent fetch with partial failure semantics ('one that fails is null and explained in errors, the others are still valid'), the null-never-0 convention, the ~3-hour nap threshold, and the unverified energyKcal caveat. This is well beyond what the annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is front-loaded with a clear purpose sentence, and given the absent output schema most details earn their place. But it is delivered as one dense block of nested parentheticals that is hard to scan, and the shape could have been split into labeled sections.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description must describe return values and does so exhaustively: the top-level shape, per-section field lists, range-mode shape, and error behavior. Nothing an agent needs to call or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3 and the schema already documents both params. The description adds value by documenting the return-shape switch when `to` is present and the local-calendar-day semantics of `date` including the midnight-to-noon sleep attribution window, going slightly past what the schema states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening states a specific verb+resource and positions it explicitly as an aggregation: 'One call for "how was this day, and the night before it": the aggregation the other tools leave to the caller.' It then names the exact siblings it replaces (get_sleep, get_recovery, get_daily_activity_statistics, list_workouts), so an agent can distinguish it without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use with alternatives named: 'Use this instead of combining get_sleep, get_recovery, get_daily_activity_statistics and list_workouts by hand.' It also gives a when-not condition for the date param ('today's data is partial until the watch has synced') and the range alternative ('prefer it to calling this once per day').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_recoveryGet recoveryARead-only
Returns recovery-balance samples from the /247samples API for one local calendar day (00:00–23:59 in the local time the watch stamped on each sample), as a plain array of { timestamp (ISO 8601 with UTC offset), entryData: { Balance (0.0–1.0 recovery balance), StressState (0=Invalid, 1=Relaxing, 2=Active, 3=Passive, 4=Stressful) } } — 48 half-hourly rows for a full day (46 or 50 on the days the clocks change). A day without recovery data returns []. Use list_recovery for a date range. Requires Recovery API subscription on apizone; returns 404 without it. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Calendar date YYYY-MM-DD (a local day, in the local time the watch stamped on each sample). Data arrives when the watch syncs, so today's is usually partial — the API answers 200 with an empty or partial payload for today and future dates rather than an error (confirmed live). Use yesterday or earlier for complete results. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint/openWorldHint annotations, it discloses concrete behaviors: 404 without a Recovery API subscription, [] for a day with no data, 48 half-hourly rows (46/50 on clock-change days), and the local-time stamping. That is rich operational context an agent cannot get from the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded, leading with the return shape and following with the routing hint and edge cases; every clause adds operational value. It is a single long sentence rather than cleanly separated, which slightly hurts readability but not content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Although there is no output schema, the description fully specifies the return shape, field types, and value ranges (Balance 0.0–1.0, StressState enum mapping) plus the empty-array and subscription-failure cases. Nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents the date format and the today-is-partial caveat, so baseline would be 3. The description adds edge-case meaning: the exact 00:00–23:59 local-day boundary and the clock-change row count that defines a 'full day'.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns recovery-balance samples from the /247samples API') and pins the scope to one local calendar day. It explicitly names the sibling it is not (list_recovery), so an agent can distinguish it without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes range queries to list_recovery, and warns that today/future dates return empty or partial payloads, advising yesterday or earlier for complete results. This is clear when-to-use, when-not, and named alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_routeGet routeARead-only
Returns one saved Suunto route by id: { id, description, visibility, activityIds, startPoint, centerPoint, endPoint (latitude, longitude, altitude), totalDistance (m), averageSpeed, created, modified (epoch ms), watchEnabled (true = offered to the watch), turnWaypointsEnabled }. Works for any route, including ones list_routes does not show (it returns at most 5). Whether the route is actually on the watch cannot be seen here — that is decided by the Suunto app's sync. Use export_route for the track. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| routeId | Yes | Route ID, e.g. from upload_route or list_routes. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, and the trailing 'Read-only' merely restates them. However the description adds real behavioral context beyond the annotations: the watchEnabled/watch-sync caveat ('Whether the route is actually on the watch cannot be seen here') and the list_routes 5-item limitation are non-obvious constraints an agent would otherwise get wrong.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then the field list, then caveats — a sensible ordering. The long inline field enumeration (with units and flag meanings) is dense but justified given there is no output schema; only the 'Read-only' sentence is redundant with annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description carries the return-value burden and does so thoroughly: field names, units (m, epoch ms), coordinate structure, and flag semantics. Combined with the watch-sync caveat, nothing an agent needs to interpret the response is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 100% schema coverage on a single required routeId parameter, the schema fully documents the input and the description does not add format or edge-case detail beyond it. Baseline 3 applies when the schema does the work.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns one saved Suunto route by id') and immediately enumerates the returned fields, so it is unmistakably distinct from list_routes (enumeration) and export_route (track download). An agent can pick it correctly without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent to alternatives: 'Use export_route for the track,' and notes it works for routes list_routes omits because that tool returns at most 5. Both the when and the when-to-use-something-else are stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_sleepGet sleepARead-only
Returns the sleeps of one night from the /247samples API. A date means the NIGHT of that date: every sleep that began between 12:00 (noon) on it and 12:00 the next day, in the local time the watch stamped on the sleep — so 23:00, 00:30 and 03:00 bedtimes all belong to the same date, and an afternoon nap is filed with the night after it. Last night is therefore filed under yesterday's date. Plain array with one row per sleep — Suunto re-sends a sleep every time it revises it, and only the longest revision is kept — of { timestamp (= BedtimeStart, ISO 8601 with UTC offset), entryData: { SleepId, IsNap, BedtimeStart, BedtimeEnd, Duration (s), DeepSleepDuration, LightSleepDuration, REMSleepDuration (s), SleepQualityScore, AvgHRV (ms), HRAvg, HRMin (bpm), … } }. IsNap is true for any sleep shorter than about 3 hours, at any time of day, and can flip while a sleep is still being recorded — so it also marks a short fragment of a split night; do not drop rows by IsNap alone. A night can hold several rows (a split night, or a nap beside it): rows are not merged, so decide from BedtimeStart and Duration which belong together. Returns [] when no sleep began in that window, e.g. today's date before tonight. Use list_sleep for a range. Requires Sleep API subscription on apizone; returns 404 without it. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Date YYYY-MM-DD of the night, NOT the wake-up date: sleeps that began between noon on this date and noon the next day (in the local time the watch stamped on each sample), so a bedtime shortly after midnight — even 03:00 — still belongs to the previous date. Last night's sleep is under yesterday's date, not today's; today's date is empty until tonight's sleep begins. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only and open-world access, but the description adds critical behavior: Sleep API subscription requirement, 404 response, revision-deduplication behavior, unmerged split rows, and IsNap flipping. These go well beyond annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but dense with necessary domain nuance; it front-loads the return type and date semantics. However, it repeats the schema's date explanation and packs multiple caveats into a single paragraph, which slightly hurts scannability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully specifies the return array shape, nested fields, and edge cases (empty array, revisions, split nights, IsNap caveats). It also covers auth requirements and sibling routing, leaving no critical gap for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents the date-window semantics, so the baseline is 3. The description adds some distinct value by explicitly mentioning that afternoon naps are filed with the following night and giving multiple bedtime examples, but much of its date explanation repeats the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the sleeps of one night from the /247samples API') and names the sibling alternative ('Use list_sleep for a range'), so an agent can distinguish it immediately from range-based sleep queries.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes range queries to list_sleep, explains the noon-to-noon date window, notes that last night is filed under yesterday, and states that a subscription is required with 404 otherwise. When and when-not are both covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_training_loadGet training load (CTL/ATL/TSB)ARead-only
Fitness (CTL, 42-day), Fatigue (ATL, 7-day) and Form (TSB = CTL - ATL) day by day up to date. Suunto's API has no endpoint for these, so they are computed from each workout's TSS (tss.trainingStressScore, 0 when Suunto has none) with the standard exponential model — the same maths as generate_daily_digest. The result can be about 1 point off the watch for CTL (compared once with the Suunto app: ATL and TSB matched, CTL was about 0.9 lower). Without seeds, workouts from 120 days before the window are read as warm-up and CTL/ATL start from 0 there, so the window is not cold-started; with little workout history CTL still reads low (it takes about 4-6 weeks to converge), which notes then says. Output (compact JSON, numbers to 1 decimal): { from, to, current: { ctl, atl, tsb, rampRate7d (CTL change over the last 7 days, null when not enough days) }, weeks: [{ weekStart (Monday), workouts, durationS, tss }] (only the days from..to count, so the first and last week can be partial), series: [{ date, tss, ctl, atl, tsb }] (values at the end of each day; tss 0 is a rest day, not missing data), notes: [caveats, plus: no workouts at all in the range, workouts without TSS, and when the workout limit was reached so numbers may be too low] }. Workouts are placed on their own local date. Read-only: it only reads workouts and never touches the digest's history or averages files.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | The last local calendar day of the series, YYYY-MM-DD. Today works but its workouts may not have synced yet. | |
| days | No | How many days the series covers, ending at `date` (default 28). | |
| seedAtl | No | Same as seedCtl but for Fatigue (ATL). | |
| seedCtl | No | Optional: Fitness (CTL) at the end of the day before the series starts, read off the watch or app, instead of computing it from a 120-day warm-up. Giving seedCtl or seedAtl skips the warm-up; a seed left out starts at 0. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
It adds substantial behavior beyond the annotations: computed rather than API-native values, expected ~1-point CTL deviation, 120-day warm-up logic, seed effects, workout-limit caveats, and note conditions. It also confirms the read-only behavior and clarifies that it does not touch digest history or averages files.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but front-loaded with the core purpose, and every sentence carries operational value: computation source, accuracy caveats, seed behavior, output shape, and read-only scope. No sentence is mere restatement.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity and the absence of an output schema, the description is complete: it defines the metrics, the computation model, the output structure, caveats, and note contents. An agent has enough information to invoke it correctly and interpret results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents all four parameters. The description still adds useful meaning by explaining that a missing seedCtl/seedAtl starts from 0 and that providing either seed skips the warm-up, which is not fully apparent from the schema alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states exactly what the tool returns: Fitness/CTL, Fatigue/ATL, and Form/TSB day by day up to a date. It distinguishes the tool from siblings by explaining that Suunto has no endpoint for these metrics and they are computed from each workout's TSS.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context for when the tool is meaningful, including seed behavior, warm-up limitations, and convergence time. It does not explicitly say when to choose this over generate_daily_digest or other sibling tools, so it falls short of full when/when-not/alternatives guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_upload_statusGet workout upload statusARead-only
Polls the processing status of a workout upload initiated by upload_workout. Returns status (e.g. 'Queued', 'Processing', 'Processed', 'Error') and the workoutKey once processing completes. Use the returned workoutKey with get_workout for full detail.
| Name | Required | Description | Default |
|---|---|---|---|
| uploadId | Yes | Upload ID returned by upload_workout. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark it readOnly and openWorld. The description adds concrete return behavior: the set of status values and the fact that workoutKey appears once processing completes, which helps the agent interpret results. It does not cover rate limits or auth, but with annotations present it adds useful context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with the core purpose, then return values, then next action. Every sentence earns its place with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter status tool with no output schema, the description adequately explains what is returned (status values and workoutKey) and how to proceed. Annotations cover safety, and the schema covers the input, so nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, and the single uploadId parameter is fully documented as the ID returned by upload_workout. The description reinforces the dependency but adds no syntax or format detail beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Polls') and resource ('processing status of a workout upload'), identifies the initiating sibling (upload_workout), and distinguishes its output from get_workout. An agent can tell this is a status-checking tool rather than a data-retrieval or upload tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly ties invocation to a prior upload_workout call and directs the agent to use the returned workoutKey with get_workout for full detail. It does not explicitly state when not to call it (e.g., avoid polling repeatedly), but the context and alternative are clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_workoutGet workoutARead-only
Returns the base summary for one workout (about 1.6 KB): the same scalar fields as a list_workouts item (times, distance, energy, hrdata, tss/tssList, recoveryTime) plus extensionTypes, the list of data streams Suunto holds for it. It does NOT include laps, HR zones or other extension data — use get_workout_laps for laps and zone times, get_workout_fit for record-level data. Throws SuuntoNotFoundError if the workoutKey is malformed (not 24 hex characters) or does not exist. Use list_workouts to discover valid workoutKey values. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| workoutKey | Yes | Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first. Passing an invalid key throws SuuntoNotFoundError. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover read-only/open-world, and the description adds substantial behavior beyond them: approximate response size (~1.6 KB), the exact field set and extensionTypes, explicit exclusions, and the SuuntoNotFoundError failure mode for malformed or missing keys.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the return shape, then exclusions/alternatives, then error behavior, then key discovery. Dense but every clause carries distinct information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully compensates by describing the returned fields, the non-returned data, response size, and failure modes. An agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents the opaque key and its discovery path. The description adds the malformed-key format detail ('not 24 hex characters') and reinforces the discover-via-list_workouts rule, marginally exceeding the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the base summary for one workout') and enumerates the exact scalar fields returned. It explicitly distinguishes itself from siblings get_workout_laps and get_workout_fit by naming what it does NOT include.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit routing: use get_workout_laps for laps/zone times, get_workout_fit for record-level data, and list_workouts to discover valid keys. The condition selecting each alternative is stated, not implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_workout_fitGet workout FIT dataARead-only
Downloads the workout's binary FIT file from Suunto and returns it parsed to JSON. Default (full=false): compact summary { sport, total_distance_km, avg_heart_rate, training_effect, laps (a COUNT only, not the laps), records_sample: { first, middle, last (one record each), count } }. Set full=true to receive every parsed FIT record and lap — pretty-printed, about 550 KB for a 35-lap strength session, so the result usually spills to a file. For per-lap data use get_workout_laps instead (about 2.5 KB); use full=true only when record-level data is required. An unknown workoutKey fails with a 403 Forbidden error here (not-found on the other workout tools). Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| full | No | false (default): return compact summary. true: return all parsed FIT records. | |
| workoutKey | Yes | Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, it discloses non-obvious behavior: an unknown workoutKey returns 403 Forbidden HERE but not-found on other workout tools, and large results spill to a file. These are exactly the operational traits annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then the default behavior, then the escape hatch, then the error quirk. Dense but every clause carries actionable information (size estimates, error code divergence, sibling pointer) with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the return-value burden and does so thoroughly, describing both the compact summary fields and the full payload nature. Combined with the error behavior and sibling routing, nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description goes further by spelling out the exact shape the full=false summary returns (sport, total_distance_km, records_sample structure) and the size consequence of full=true. This adds meaning beyond the terse schema text, though much of it is return-shape rather than parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Downloads the workout's binary FIT file from Suunto and returns it parsed to JSON'), and immediately distinguishes itself from siblings by naming get_workout_laps as the per-lap alternative. An agent can tell what this does and how it differs from nearby tools without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes usage: default compact summary vs full=true for record-level data, with a concrete size signal ('about 550 KB for a 35-lap strength session, so the result usually spills to a file') and a named alternative ('For per-lap data use get_workout_laps instead (about 2.5 KB)'). It even states the exclusivity condition ('use full=true only when record-level data is required').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_workout_lapsGet workout lapsARead-only
Returns the manual laps of one workout as a compact table, plus its training-load fields — the way to read back a guided gym session set by set (push_strength_guide records one lap per set and per rest; push_workout_guide one lap per exercise and one per rest between exercises). A session from push_interval_guide auto-advances and is expected to record no manual laps (unverified), so it should return an empty table. About 2.5 KB for a 35-lap strength session, versus ~550 KB for get_workout_fit full=true. Output: { workoutKey, activityId, startTime (epoch ms), totalTimeS, guide: { id, name } | null (the guide that ran, as recorded by Suunto — not looked up in list_guides, because guides are often deleted afterwards), tss: [{ method (seen so far: 'HR', 'MET'), value }], pte, peakEpoc, recoveryTime (from the workout's summary extension; units not verified, and it can differ from the recoveryTime that list_workouts and get_workout carry), hrZoneTimeS: [zone1..zone5 seconds], feeling (the answer to the watch's 'How was it?' question, passed through as Suunto sends it; null when skipped), lapCount, checks: [{ code, detail }], laps: { cols, rows } }. checks lists reasons not to trust positional reading of the table (empty when clean): 'duplicate-rest' (the same rest label twice in a row — a set lap is missing or a rest was split), 'no-session-complete' (a guided table without its final lap — session ended early, buttons locked or watch restarted), 'unlabelled-laps' (some laps have no guide label), 'no-heart-rate' (no lap has heart rate, e.g. battery mode Tour). laps.cols = [i (1-based), startOffsetS (from workout start), durationS, hrAvg, hrMax, hrMin (bpm), kcal, kind, label]; each row is an array in that order. label is the text of the guide step that was active during the lap (lines joined with ' | '), or null when no guide ran. kind is 'rest' when the label contains 'Next:' at its start or after a '·' (a per-set rest lap reads 'Next: set k/S', or 's target · Next: set k/S' with restMode 'stopwatch'), 'done' for the final 'Session complete' lap, 'step' for any other labelled lap, null when there is no label. A per-set strength guide yields, per exercise, a prep lap, then set 1, rest, set 2, rest, … — 2 × sets laps — and one trailing 'Session complete' lap for the whole session; a prep lap's label reads ' | ' and a set lap's ' | ' (the halves are swapped, but the text is not always decisive, e.g. a name equal to the detail or a label cut at 40 characters), so tell them apart by position. Real sessions can deviate (skipped or repeated rest laps), so check the labels rather than only counting. A workout without manual laps (unguided gym, cycling) returns lapCount 0 and laps.rows [] — not an error. Call list_workouts first for the workoutKey.
| Name | Required | Description | Default |
|---|---|---|---|
| workoutKey | Yes | The 24-character workoutKey returned by list_workouts. Anything else fails with a not-found error without calling Suunto. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes far beyond the readOnly/openWorld annotations by documenting expected payload size, full output shape, lap table columns, checks for suspicious tables, empty-result behavior, and several unverified or edge-case fields. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Very long, but most sentences carry useful operational detail for a complex tool with no output schema. It is front-loaded with purpose and size comparison, though readability would improve with headings or bullets.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Complete for a complex read tool with no output schema: it documents the return structure, lap interpretation rules, warning checks, guide behavior, and empty cases. An agent has enough information to call and interpret the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is only one parameter and schema coverage is 100%, so the schema already explains the 24-character workoutKey. The description reinforces the prerequisite, but adds little semantic detail beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: returns manual laps of one workout plus training-load fields. It distinguishes the tool from get_workout_fit full=true by purpose and payload size, and explains the guided gym use case clearly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly directs the agent to call list_workouts first for workoutKey, compares this tool with get_workout_fit full=true, explains which guided sessions produce laps, and notes that interval-guide sessions should return an empty table rather than an error.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_daily_activityList daily activityARead-only
Returns 24/7 activity samples from the /247samples API for the local calendar days [from, to] inclusive (in the local time the watch stamped on each sample), ordered chronologically, as a plain array of { timestamp (ISO 8601 with UTC offset), entryData: { HR (bpm), StepCount, EnergyConsumption (joules, as in get_daily_activity_statistics) } }. Days without synced data are simply absent. Use get_daily_activity for a single day or get_daily_activity_statistics for aggregated daily step/energy totals. Requires 24/7 Activity API subscription on apizone. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | End date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer about 3–7 days: a day is ~15 KB, so 30 days is ~450 KB (use get_daily_activity_statistics for longer totals). | |
| from | Yes | Start date YYYY-MM-DD, inclusive. Must be ≤ to. Days without synced data are silently omitted, not 404. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint/openWorldHint annotations, it discloses the output shape (array of timestamp + entryData with HR, StepCount, EnergyConsumption), the absence-instead-of-error behavior for unsynced days, chronology, and the subscription requirement. This is unusually rich behavioral context for a read tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One long sentence but front-loaded and dense: resource and request first, output shape second, alternatives and constraints last. Every clause carries information, though the parenthetical nested object definition makes it heavier to parse than it needs to be.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, yet the description fully specifies the return structure, ordering, missing-day behavior, and the API subscription gate. Combined with the 100%-covered input schema, an agent has everything needed to call and interpret this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents format, inclusivity, and size guidance, so the baseline is 3. The description adds genuine param-level semantics: intervals are interpreted in the local time the watch stamped on each sample, and results are ordered chronologically, which the schema does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource (returns 24/7 activity samples from /247samples) with explicit scope (local calendar days [from, to] inclusive). It directly distinguishes itself from the sibling tools get_daily_activity (single day) and get_daily_activity_statistics (aggregated totals), so an agent can route without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states exactly when to use this tool versus the two alternatives, names those alternatives, and adds a hard prerequisite (24/7 Activity API subscription on apizone) plus a range-size preference. Nothing about selection is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_guidesList SuuntoPlus guidesARead-only
Returns all SuuntoPlus Guides (from push_workout_guide/push_interval_guide/push_strength_guide) on the user's account, newest first. Each item includes id, name, description, owner, localDate, and usage. Use the id with delete_guide, or with push_*_guide's guideId param to update an existing guide instead of creating a new one. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, and the description redundantly confirms 'Read-only'. It adds genuine context beyond annotations: the ordering (newest first) and the fact that it returns ALL guides with no pagination/limit caveat mentioned. No auth or rate-limit detail, but nothing contradicts annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the verb+resource+ordering, followed by the returned fields and the actionable id usage. No filler; every sentence carries operational information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fills the gap by enumerating returned fields and ordering, and it explains the follow-up call pattern with delete_guide and push_*_guide. Combined with annotations covering the safety profile, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so the baseline of 4 applies. The description correctly implies no filtering (returns all guides on the account) and details the fields present on each returned item (id, name, description, owner, localDate, usage), which compensates for the absent output schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Returns all SuuntoPlus Guides') and scopes the sources (from push_workout_guide/push_interval_guide/push_strength_guide) on the user's account, plus ordering (newest first). An agent can distinguish this from sibling list_* tools without opening another schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent downstream: use the id with delete_guide, or with push_*_guide's guideId to update an existing guide rather than create a new one. There is no competing 'list guides' sibling, so no when-not-to-use exclusion exists, but the context for use is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_recoveryList recoveryARead-only
Returns recovery-balance samples from the /247samples API for the local calendar days [from, to] inclusive (in the local time the watch stamped on each sample), ordered chronologically, as a plain array of { timestamp (ISO 8601 with UTC offset), entryData: { Balance (0.0–1.0 recovery balance), StressState (0=Invalid, 1=Relaxing, 2=Active, 3=Passive, 4=Stressful) } }. Days without recovery data are simply absent. Use get_recovery for a single day. Requires Recovery API subscription on apizone; returns 404 without it. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | End date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer about 14 days or less: a day is ~4 KB of output. | |
| from | Yes | Start date YYYY-MM-DD, inclusive. Must be ≤ to. Days without recovery data are silently omitted, not 404. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, and the description redundantly restates read-only but adds real value beyond them: the Recovery API subscription requirement and the 404 behavior without it, the chronological ordering guarantee, and the silent omission of empty days. It stops short of pagination or rate-limit detail, so 4 rather than 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core behavior and return shape in one dense sentence, then adds short supporting sentences for the alternative and the subscription constraint. The parenthetical balance/StressState enumeration is long but earns its place because there is no output schema to carry it.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully specifies the return structure, ordering, missing-day behavior, and the auth/error profile, so nothing an agent needs to call or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds semantics the schema does not: that days are interpreted in the local time the watch stamped on each sample, and that the output is a plain array. This meaningfully clarifies the temporal boundary beyond the raw date pattern.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Returns) and resource (recovery-balance samples) with precise scope: local calendar days [from, to] inclusive, chronological ordering, and the exact return shape. It explicitly contrasts with get_recovery for a single day, so an agent can distinguish it from siblings without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the alternative explicitly ('Use get_recovery for a single day') and gives the selecting condition (single day vs. range). It also warns days without data are simply absent, so the agent will not misread an empty stretch as an error.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_routesList routesARead-only
Returns the user's saved Suunto routes — at most 5 of them (Suunto's list endpoint ignores limit/offset and appears to return the newest, unverified), so an account with more routes is NOT fully listed; look a route up by id with get_route (ids come from upload_route results, the Suunto app or the route webhook). Each route: id, description, visibility, activityIds, startPoint/centerPoint/endPoint, totalDistance (m), created/modified (epoch ms), watchEnabled. Use export_route for the GPX track. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnlyHint/openWorldHint annotations by disclosing that Suunto's endpoint ignores limit/offset, appears to return the newest routes, and that this is unverified — a behavioral limitation an agent could not otherwise anticipate. It also enumerates the returned fields and their units.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The cap ('at most 5') and the incompleteness warning are front-loaded before the alternatives and field list, which is the right ordering. The field enumeration is a long run of clauses but each item carries return-value information not available elsewhere.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of describing return values and does so field-by-field with units (meters, epoch ms). Combined with the truncation caveat and sibling routing, an agent has everything needed to call and interpret it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline of 4 applies; the description correctly offers no parameter guidance. It instead spends its words on the return shape, which is the only semantic surface available.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Returns the user's saved Suunto routes') and immediately scopes it with a hard cap of 5. It also distinguishes itself from siblings by naming get_route for id lookups and export_route for GPX tracks.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says the tool is NOT a full listing and directs the agent to get_route by id (with three concrete id sources) and to export_route for the GPX track. The conditions selecting each alternative are spelled out, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_sleepList sleepARead-only
Returns the sleeps of the nights [from, to] inclusive from the /247samples API, ordered chronologically by bedtime. A date means the NIGHT of that date: every sleep that began between 12:00 (noon) on it and 12:00 the next day, in the local time the watch stamped on the sleep — so 23:00, 00:30 and 03:00 bedtimes all belong to the same date, and an afternoon nap is filed with the night after it. Last night is therefore filed under yesterday's date. Same rows as get_sleep, one per sleep (revisions collapsed): { timestamp (= BedtimeStart, ISO 8601 with UTC offset), entryData: { SleepId, IsNap, BedtimeStart, BedtimeEnd, Duration (s), DeepSleepDuration, LightSleepDuration, REMSleepDuration (s), SleepQualityScore, AvgHRV (ms), HRAvg, HRMin (bpm), … } }. Nights without recorded sleep are simply absent. Use get_sleep for a single night. Requires Sleep API subscription on apizone; returns 404 without it. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| to | Yes | Last bedtime date YYYY-MM-DD, inclusive (the date the person went to bed, not woke up — see get_sleep). Future dates are accepted but produce no entries. Prefer ranges ≤ 30 days for responsiveness. | |
| from | Yes | First bedtime date YYYY-MM-DD, inclusive (the date the person went to bed, not woke up — see get_sleep). Must be ≤ to. Nights without recorded sleep are silently omitted, not 404. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover readOnly/openWorld; the description adds substantial behavioral context beyond them: the noon-to-noon 'night' filing rule, chronological ordering, revision collapsing ('one per sleep'), 404 auth behavior, and that absent nights are simply not present.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and routing, then schema/return detail. The inline entryData field enumeration is dense but justified since no output schema exists. Length is high but most sentences carry distinct information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description supplies the full return shape (timestamp, entryData fields) plus the semantic caveats needed to interpret dates correctly. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds interpretive depth (a date means the NIGHT beginning at noon, 23:00/00:30/03:00 bedtimes all map to one date, naps file with the following night). That said, it largely restates the schema's own 'date went to bed, not woke up' note.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the sleeps of the nights') with explicit scope (from/to, /247samples API, ordered chronologically by bedtime). It distinguishes itself from the sibling get_sleep by naming it and its different use case.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: 'Use get_sleep for a single night.' It also states a prerequisite ('Requires Sleep API subscription on apizone; returns 404 without it') and notes that missing nights are silently omitted rather than errors.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_workoutsList workoutsARead-only
Returns the user's recent Suunto workouts ordered newest-first (Workout API v3). Each item: workoutKey (string id), activityId (numeric activity code — there is no separate plain-language 'sport' field; use get_workout_fit for the parsed FIT file's session.sport if a sport name is needed), startTime (epoch ms), totalTime (s), totalDistance (m), totalAscent (m), totalDescent (m), energyConsumption (kilocalories, not 'totalCalories'), hrdata: { avg, max } (workout heart rate — hrdata.max is the account's overall max HR, use hrdata.workoutMaxHR for this specific workout's peak). Auto-paginates with offset-based pagination until limit is reached or no more workouts exist. Each item also embeds SummaryExtension (including apps[]: the SuuntoPlus guide that ran, if any) and IntensityExtension (HR-zone times). Use get_workout_laps for the lap table of a single workout. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of workouts to return (1–1000). Defaults to 25. Pagination is automatic across API pages; set to 1 for the single most-recent workout. | |
| since | No | ISO 8601 lower bound on startTime (inclusive). Filters on workout start time; omit for all time. Pagination is automatic so since does not affect page size. | |
| until | No | ISO 8601 upper bound on startTime (inclusive). Omit to include workouts up to the present. Combine with since to target a specific window. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover read-only/open-world, and the description adds genuine behavioral detail beyond them: auto-pagination semantics ('until limit is reached or no more workouts exist'), plus field-level traps (hrdata.max is the account's overall max HR, not the workout peak; energyConsumption is not totalCalories; no plain-language sport field exists). These gotchas materially change how an agent interprets results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Single dense paragraph, front-loaded with the core action before field details. It is long, but with no output schema the field-level exposition earns its place; the sole weakness is that field descriptions and usage hints are interleaved rather than separated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema in structured form, the description compensates by enumerating the returned shape (workoutKey, activityId, startTime, totalTime, distance, ascent/descent, energy, hrdata, SummaryExtension, IntensityExtension). An agent has enough to call it and interpret the response.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents limit, since, and until thoroughly. The description adds only the pagination caveat ('auto-paginates... until limit is reached'), which the schema also states, so it does not meaningfully exceed the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Returns/list), resource (user's recent Suunto workouts), and ordering (newest-first), with versioning (Workout API v3). An agent can immediately distinguish it from get_workout (single) and get_workout_laps (lap table).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent to alternatives for adjacent needs ('use get_workout_fit for the parsed FIT file's session.sport', 'Use get_workout_laps for the lap table of a single workout'). It gives clear context for related lookups but never states the inverse boundary (e.g. list vs. fetch a specific workout) or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
push_interval_guidePush interval guide to watchADestructive
Pushes an interval/cardio guide (warmup, timed or distance-based work intervals, recoveries, optional repeats) to the user's Suunto account via the SuuntoPlus Guide Cloud API. Unlike push_workout_guide (manual lap-per-exercise), interval segments auto-advance by elapsed time or distance — hands-off during a run or ride. Each segment can show a target heart-rate range alongside live HR. Requires SUUNTO_APP_NAME env var to exactly match the app name registered on apizone.suunto.com. Same delivery caveat as push_workout_guide: appears after the phone's next normal Suunto app sync, no live push. Returns Suunto's response (including the guide id) plus nextStep and check: { verified, note? }: after the push the guide is looked up in the guide list (by id), and verified:false with a note means the push went through but the guide was not found there — say so to the user instead of reporting success. Pushes are queued one at a time with a short pause, so several calls at once are safe. Write operation.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Session date YYYY-MM-DD. | |
| title | Yes | Short session name shown in the Suunto app, e.g. '4x4 VO2max'. | |
| blocks | Yes | Ordered list of blocks. A block with times>1 repeats its segments as a unit (e.g. 4x[interval,recovery]) — put only the segments that repeat inside it; warmup/cooldown go in their own times=1 blocks before/after. | |
| guideId | No | If provided, updates this existing guide instead of creating a new one. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds substantial behavioral context beyond the annotations: it describes the delivery caveat (appears after the next normal Suunto app sync, no live push), the verification step after pushing, the warning to report verified:false rather than success, and the queued one-at-a-time behavior making concurrent calls safe. It also states the SUUNTO_APP_NAME prerequisite and that this is a write operation, consistent with the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but front-loaded: it opens with the operation, immediately contrasts the sibling tool, then covers prerequisites, delivery behavior, return verification, concurrency, and write status. Each sentence carries useful context, though the length is substantial and could be tightened slightly without losing meaning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex write tool with no output schema, the description supplies the critical missing context: prerequisite environment variable, delivery timing, post-push verification semantics, how to interpret verified:false, and concurrency safety. Combined with the fully described input schema, an agent has what it needs to call the tool correctly and interpret the response.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all four parameters, including block repeats, segment timing/distance, and heart-rate targets. The description adds conceptual color (warmup, intervals, recoveries, optional repeats, live HR targets) but does not add syntax or constraints beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: pushes an interval/cardio guide to the user's Suunto account via the SuuntoPlus Guide Cloud API. It also distinguishes the tool from push_workout_guide by explaining that interval segments auto-advance by time or distance rather than requiring manual laps.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear usage context by contrasting it with push_workout_guide: choose this for hands-off interval/cardio sessions with time- or distance-based auto-advance. It names the alternative tool and explains the key differentiator, though it does not give explicit when-not-to-use guidance beyond that comparison.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
push_strength_guidePush strength guide to watchADestructive
Pushes a resistance-training guide to the user's Suunto account via the SuuntoPlus Guide Cloud API — the tool to use for gym sessions. Per exercise: a prep step (self-paced stopwatch showing the plate breakdown if given, otherwise the weight/sets detail, plus the exercise name and live HR; a lap press starts the exercise), then with lapGranularity 'perSet' (default) each set is its own step ended by a lap press, and each rest between sets is its own step showing 'Next: set k/S'. restMode 'countdown' (default) counts down restSec and auto-advances into the next set with a vibration; 'stopwatch' counts up and waits for a lap press. lapGranularity 'perExercise' gives one step per exercise after its prep, with no between-set rests and no per-set laps. Every prep, set and rest is its own lap and the guide ends with one extra 'Session complete' step, so a perSet session records 2 × (total sets) + 1 laps. Read them back after the workout with get_workout_laps — its labels are the step texts. Requires SUUNTO_APP_NAME to exactly match the app name registered on apizone.suunto.com. Without guideId a new guide is created on every call (see list_guides / delete_guide to tidy up); with guideId that guide is overwritten. There is no live push to the watch: it appears after the phone's next normal Suunto app sync. Returns Suunto's response (including the guide id) plus nextStep and check: { verified, note? }: after the push the guide is looked up in the guide list (by id), and verified:false with a note means the push went through but the guide was not found there — say so to the user instead of reporting success. Pushes are queued one at a time with a short pause, so several calls at once are safe. Write operation.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Session date YYYY-MM-DD. | |
| title | Yes | Short session name shown in the Suunto app, e.g. 'Push A'. | |
| guideId | No | If provided, updates this existing guide instead of creating a new one. | |
| restMode | No | Rest between sets. 'countdown' (default): counts down from restSec and auto-advances into the next set. 'stopwatch': counts up and waits for a lap press — the user paces it. Has no effect with lapGranularity 'perExercise' (no between-set rests). Before/between exercises is always a self-paced stopwatch. | countdown |
| exercises | Yes | ||
| lapGranularity | No | 'perSet' (default, recommended): one step per set plus one per rest, so laps bound every set/rest individually — needed to read per-set HR and duration from the synced workout. 'perExercise': one step per whole exercise instead, like push_workout_guide — shorter Guide list, coarser data. | perSet |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare a destructive, non-idempotent, open-world write; the description earns its keep by adding the non-obvious consequences: a new guide is created on every call without guideId, existing guides are overwritten with guideId, there is no live push (appears after next phone sync), and pushes are queued serially so concurrent calls are safe. The verified/note return contract for a failed lookup is exactly the kind of post-condition annotations can't express, and nothing contradicts the destructiveHint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and warnings are front-loaded, but the body is one dense paragraph of long run-on sentences covering lap math, rest modes, and sync timing with no structure. Nearly all content is useful, yet the lap-counting narrative is restated in several clauses and would be far cheaper to read as a short list.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 6-parameter destructive write with no output schema, the description covers prerequisites (SUUNTO_APP_NAME must match the registered app), persistence semantics, sync delay, concurrency, and the shape and interpretation of the response including the verified/note failure case. An agent has everything needed to call it and report results honestly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 83%, so the baseline is 3, but the description goes beyond the schema by explaining the operational consequence of each mode choice (perSet laps bound every set/rest so per-set HR is readable; perExercise has no between-set rests) and gives the lap-count formula 2 × total sets + 1. It does not spell out plates formatting beyond the schema, so it is not a full 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource ('Pushes a resistance-training guide to the user's Suunto account') and immediately differentiates from siblings by scoping it to 'gym sessions' and contrasting perExercise mode with push_workout_guide. An agent can pick this over push_interval_guide or push_workout_guide without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use it (gym sessions), when a variant is preferable (perExercise 'shorter Guide list, coarser data'), the alternative for reading results back ('get_workout_laps'), and the cleanup path ('list_guides / delete_guide'). It also gives the guideId create-vs-overwrite decision rule, leaving no inference required.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
push_workout_guidePush workout guide to watchADestructive
Pushes a text-step workout guide to the user's Suunto account via the SuuntoPlus Guide Cloud API. Each exercise becomes one step, advanced by a lap-button press on the watch. Requires SUUNTO_APP_NAME env var to exactly match the app name registered on apizone.suunto.com. There is no live push to the watch itself — delivery depends on the phone's normal Suunto app sync. In testing it showed up on the watch after the next ordinary sync with no manual pinning needed; if it doesn't appear, check the Suunto app under SuuntoPlus Guides and pin it there. For gym sessions prefer push_strength_guide: it records one lap per set and per rest, which get_workout_laps can read back. Returns Suunto's response (including the guide id) plus nextStep and check: { verified, note? }: after the push the guide is looked up in the guide list (by id), and verified:false with a note means the push went through but the guide was not found there — say so to the user instead of reporting success. Pushes are queued one at a time with a short pause, so several calls at once are safe. Write operation.
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Session date YYYY-MM-DD. | |
| title | Yes | Short session name shown in the Suunto app, e.g. 'Push A'. | |
| guideId | No | If provided, updates this existing guide instead of creating a new one. | |
| exercises | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: requires SUUNTO_APP_NAME to exactly match the name registered on apizone.suunto.com, clarifies that delivery is sync-dependent with pinning as a fallback, and discloses that pushes are queued one at a time so concurrent calls are safe. It also explains the returned check/verified semantics and instructs the agent not to report success when verified is false — a mutation-relevant failure mode the annotations cannot convey. 'Write operation' is consistent with readOnlyHint=false and destructiveHint=true.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and the sibling routing are front-loaded, followed by prerequisites, delivery mechanics, and result interpretation in descending order of importance. The parenthetical about testing/pinning is the only slightly expendable clause, and even it sets user expectations usefully.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a non-idempotent, open-world write tool with no output schema, the description covers prerequisites, concurrency safety, delivery latency, and how to interpret the returned verification data. An agent has everything needed to call it and to report results honestly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 75%, so the schema already documents most fields; the description adds the important mapping that each exercise entry becomes one discrete step advanced by a lap press, which is not derivable from the schema. It does not clarify how guideId interacts with title/date on update, leaving that overlap to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Pushes a text-step workout guide to the user's Suunto account via the SuuntoPlus Guide Cloud API') and explains the runtime effect: each exercise becomes one step advanced by a lap-button press. It explicitly distinguishes itself from push_strength_guide, so an agent can separate the two without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear routing rule for the closest sibling ('For gym sessions prefer push_strength_guide') and explains the delivery path (no live push; arrives on the phone's next normal Suunto app sync) plus a remedy if it doesn't appear. It stops short of covering push_interval_guide or when to prefer upload_workout, so the sibling landscape is only partially mapped.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
upload_routeImport GPX routeA
Imports a GPX file as a route in the user's Suunto account (it appears in the Suunto app's route library as PRIVATE and can then be synced to the watch for navigation). Provide the absolute path to a .gpx file. Every or element in the file becomes its own route; the route name comes from its (or ), and waypoints are created from elements at the same coordinates as route points and from route points that have a name or type. The route's activities come from the activities argument, NOT from the GPX content (default [1] = running). Returns { count, routes: [{ id, name, activities, segments }], check: { verified, watchEnabled, distanceM, pointsInFile, pointsInRoute, note? } }. Imports are queued one at a time with a short pause and each route is read back from Suunto (point count against the file), so many calls at once are safe; expect roughly 3 s per route. verified:false with a note means the import went through but the read-back did not match — say so to the user instead of reporting success. A route cannot be deleted through this server — remove it in the Suunto app. Requires the Route API on the Suunto subscription (list_routes shows existing routes). Write operation.
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | Absolute path to the .gpx file on disk. | |
| activities | No | Suunto activity IDs the route is suitable for (1 = running; the other IDs are in Suunto's Activities.pdf on apizone). Shown in the app's route details. Optional. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint=false, destructiveHint=false, idempotentHint=false), it discloses serial queuing with a ~3s per-route cost, read-back verification behavior, the meaning of verified:false with a note, and that routes cannot be deleted via this server. ".gpx" write-operation framing is consistent with readOnlyHint=false; creating without destroying is consistent with destructiveHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded: the import action and scope come first, then file/format mapping, then return shape and operational caveats. Nearly every sentence carries non-obvious information, though the parenthetical return-shape enumeration is heavy in a block of prose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description inlines the return shape ({count, routes, check} with field meanings) and the failure semantics of check.verified. An agent has everything needed to call it, interpret the result, and warn the user about unverified imports.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, so the baseline is 3, but the description adds real meaning: activities override rather than derive from GPX content, default [1] = running, and how each <rte>/<trk>, <name>/<desc>, and <wpt> maps to output. It also clarifies filePath must be absolute.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource: "Imports a GPX file as a route in the user's Suunto account." It clearly separates this from siblings like list_routes (read), export_route (reverse direction), and upload_workout (workouts not routes), and even states the resulting visibility (PRIVATE route library).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives concrete operating context: provide an absolute .gpx path, activities come from the argument not the file, list_routes shows existing routes, and the Route API subscription is required. It stops short of explicitly naming when to prefer this over export_route or other import paths, but the surrounding conditions are clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
upload_workoutUpload workout fileA
Uploads a workout file to the user's Suunto account. Provide the absolute path to the file on disk. The file is pushed to Suunto and appears in the app after processing (usually a few seconds). Waits for Suunto to process the file (polling the upload status for up to about 20 s) and returns { uploadId, message, status, check: { verified, note? } }: verified is true when the status reaches 'Processed'; otherwise note says why (another final status such as 'Error', or processing not finished in time — then poll get_upload_status with the uploadId later). Uploads are queued one at a time with a short pause, so several calls at once are safe. Suunto's own upload API docs state only .fit (binary) is currently supported for this endpoint — a .gpx path is still accepted here (sent as application/gpx+xml) in case that changes, but treat it as unverified; use .fit for a workout that must reliably show up. Write operation.
| Name | Required | Description | Default |
|---|---|---|---|
| comment | No | Longer notes for the workout. Optional. | |
| privacy | No | Visibility. DEFAULT uses the account's default setting. | DEFAULT |
| filePath | Yes | Absolute path to the .fit or .gpx file on disk. | |
| description | No | Short workout title shown in the Suunto app. Optional. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial context beyond annotations: it waits/polls up to ~20s, returns a structured result with a verified flag and a reason note, uploads are serialized with a pause so concurrent calls are safe, and it discloses format-support caveats. This is exactly the behavioral detail annotations alone can't convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and required input are front-loaded, and the return-shape explanation is worth its space. Slight waste in the trailing 'Write operation.' which merely restates readOnlyHint=false, and the format caveat sentence is dense.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully carries the return contract ({ uploadId, message, status, check }) and the polling/async follow-up path. Nothing an agent needs to call or interpret this write tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so all four parameters are already documented. The description adds only a minor format hint (.fit vs .gpx) tied to filePath; comment, privacy, and description semantics come entirely from the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Uploads a workout file to the user's Suunto account'), which cleanly separates it from siblings like upload_route and push_workout_guide. An agent can tell exactly what domain object is being pushed.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explains the required input (absolute path) and gives concrete routing guidance: prefer .fit for reliable ingestion, treat .gpx as unverified, and call get_upload_status with the uploadId if processing doesn't finish. It doesn't explicitly contrast against the sibling push_* tools, but the when-to-use context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.21.0- Added
compare_workout_to_plan - Removed
export_workout_gpx - Added
get_training_load - Removed
get_workout_samples - Removed
list_subscriptions
1 tool update
v0.20.0- Added
get_route
1 tool update
v0.19.1- Added
upload_route
1 tool update
v0.18.0- Added
get_daily_snapshot
9 tool updates
v0.15.1- Changed
generate_daily_digest1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date YYYY-MM-DD to summarize. Use yesterday or earlier — Suunto syncs once daily, so today's data is usually incomplete."New value: +"Calendar date YYYY-MM-DD to summarize. Use yesterday or earlier — today's data is usually partial until the watch has synced."
- Changed
get_daily_activity1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date YYYY-MM-DD. Suunto syncs once daily, so today's data is usually incomplete — but the API responds 200 with an empty or partial payload for today/future dates, it does not throw SuuntoNotFoundError (confirmed live). Use yesterday or earlier for complete results."New value: +"Calendar date YYYY-MM-DD (a local day, in the local time the watch stamped on each sample). Data arrives when the watch syncs, so today's is usually partial — the API answers 200 with an empty or partial payload for today and future dates rather than an error (confirmed live). Use yesterday or earlier for complete results."
- Changed
get_daily_activity_statistics3 fields changed- changed
Input schema / properties / enddate / descriptionPrevious value: -"End datetime in ISO-8601 format (e.g. 2026-04-30T23:59:59). Must be within 28 days of startdate."New value: +"End datetime in ISO-8601 format, same forms as startdate (e.g. 2026-04-27T23:59:59). Must be less than 28 days after startdate." - changed
Input schema / properties / enddate / examplesPrevious value: -[ - "2026-04-30T23:59:59" -]New value: +[ + "2026-04-27T23:59:59" +] - changed
Input schema / properties / startdate / descriptionPrevious value: -"Start datetime in ISO-8601 format (e.g. 2026-04-01T00:00:00). Data is stored in UTC."New value: +"Start datetime in ISO-8601 format, with or without a UTC offset (e.g. 2026-04-01T00:00:00 or 2026-04-01T00:00:00+02:00). An offset written +0200, as `date +%z` prints it, is rewritten to +02:00 because Suunto rejects the former."
- Changed
get_recovery1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date YYYY-MM-DD. Suunto syncs once daily, so today's data is usually incomplete — but the API responds 200 with an empty or partial payload for today/future dates, it does not throw SuuntoNotFoundError (confirmed live). Use yesterday or earlier for complete results."New value: +"Calendar date YYYY-MM-DD (a local day, in the local time the watch stamped on each sample). Data arrives when the watch syncs, so today's is usually partial — the API answers 200 with an empty or partial payload for today and future dates rather than an error (confirmed live). Use yesterday or earlier for complete results."
- Changed
get_sleep1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Date YYYY-MM-DD the person went to bed (bedtime), NOT the wake-up date — a bedtime shortly after midnight still counts as the previous date. To get 'last night's sleep' as of right now, use yesterday's date, not today's. Suunto syncs once daily — use yesterday or earlier for reliable results."New value: +"Date YYYY-MM-DD of the night, NOT the wake-up date: sleeps that began between noon on this date and noon the next day (in the local time the watch stamped on each sample), so a bedtime shortly after midnight — even 03:00 — still belongs to the previous date. Last night's sleep is under yesterday's date, not today's; today's date is empty until tonight's sleep begins."
- Added
get_workout_laps - Changed
list_daily_activity1 field changed- changed
Input schema / properties / to / descriptionPrevious value: -"End date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer ranges ≤ 30 days for responsiveness."New value: +"End date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer about 3–7 days: a day is ~15 KB, so 30 days is ~450 KB (use get_daily_activity_statistics for longer totals)."
- Changed
list_recovery1 field changed- changed
Input schema / properties / to / descriptionPrevious value: -"End date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer ranges ≤ 30 days for responsiveness."New value: +"End date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer about 14 days or less: a day is ~4 KB of output."
- Changed
push_strength_guide2 fields changed- changed
Input schema / properties / exercises / items / properties / detail / descriptionPrevious value: -"Display string shown on the exercise's set steps and on the prep screen before it — include weight and sets, e.g. '60kg 3x10'."New value: +"Display string shown on the exercise's set steps, and on the prep screen before it unless 'plates' is given — include weight and sets, e.g. '60kg 3x10'." - added
Input schema / properties / exercises / items / properties / platesAdded value: +{ + "description": "Per-side plate breakdown for barbell exercises, e.g. '2x20+1x5/side' — shown on the prep screen instead of detail, since that's when the bar actually gets loaded. Omit for non-barbell exercises (dumbbell, machine, bodyweight, cable); compute the math yourself before calling this tool, it isn't done here.", + "type": "string" +}
3 tool updates
v0.15.0- Added
delete_guide - Added
list_guides - Added
push_strength_guide
2 tool updates
v0.14.4- Changed
get_daily_activity1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date YYYY-MM-DD. Suunto syncs once daily — today or future dates typically return SuuntoNotFoundError; use yesterday or earlier for reliable results."New value: +"Calendar date YYYY-MM-DD. Suunto syncs once daily, so today's data is usually incomplete — but the API responds 200 with an empty or partial payload for today/future dates, it does not throw SuuntoNotFoundError (confirmed live). Use yesterday or earlier for complete results."
- Changed
get_recovery1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date YYYY-MM-DD. Suunto syncs once daily — today or future dates typically return SuuntoNotFoundError; use yesterday or earlier for reliable results."New value: +"Calendar date YYYY-MM-DD. Suunto syncs once daily, so today's data is usually incomplete — but the API responds 200 with an empty or partial payload for today/future dates, it does not throw SuuntoNotFoundError (confirmed live). Use yesterday or earlier for complete results."
2 tool updates
v0.14.1- Changed
get_sleep1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Wake-up date YYYY-MM-DD. Keyed to the morning the session ended, not when it started. Suunto syncs once daily — use yesterday or earlier for reliable results."New value: +"Date YYYY-MM-DD the person went to bed (bedtime), NOT the wake-up date — a bedtime shortly after midnight still counts as the previous date. To get 'last night's sleep' as of right now, use yesterday's date, not today's. Suunto syncs once daily — use yesterday or earlier for reliable results."
- Changed
list_sleep2 fields changed- changed
Input schema / properties / from / descriptionPrevious value: -"First wake-up date YYYY-MM-DD, inclusive. Must be ≤ to. Nights without recorded sleep are silently omitted, not 404."New value: +"First bedtime date YYYY-MM-DD, inclusive (the date the person went to bed, not woke up — see get_sleep). Must be ≤ to. Nights without recorded sleep are silently omitted, not 404." - changed
Input schema / properties / to / descriptionPrevious value: -"Last wake-up date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer ranges ≤ 30 days for responsiveness."New value: +"Last bedtime date YYYY-MM-DD, inclusive (the date the person went to bed, not woke up — see get_sleep). Future dates are accepted but produce no entries. Prefer ranges ≤ 30 days for responsiveness."
7 tool updates
v0.14.0- Added
export_route - Added
generate_daily_digest - Added
get_upload_status - Added
list_routes - Added
push_interval_guide - Added
push_workout_guide - Added
upload_workout
1 tool update
v0.10.0- Added
get_daily_activity_statistics
11 tool updates
v0.9.2- Changed
export_workout_gpx1 field changed- changed
Input schema / properties / workoutKey / descriptionPrevious value: -"Unique workout identifier from list_workouts."New value: +"Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first. Passing an invalid key throws SuuntoNotFoundError."
- Changed
get_daily_activity1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date YYYY-MM-DD. Example: 2026-04-20."New value: +"Calendar date YYYY-MM-DD. Suunto syncs once daily — today or future dates typically return SuuntoNotFoundError; use yesterday or earlier for reliable results."
- Changed
get_recovery1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Calendar date YYYY-MM-DD. Example: 2026-04-20."New value: +"Calendar date YYYY-MM-DD. Suunto syncs once daily — today or future dates typically return SuuntoNotFoundError; use yesterday or earlier for reliable results."
- Changed
get_sleep1 field changed- changed
Input schema / properties / date / descriptionPrevious value: -"Wake-up date YYYY-MM-DD. Example: 2026-04-20."New value: +"Wake-up date YYYY-MM-DD. Keyed to the morning the session ended, not when it started. Suunto syncs once daily — use yesterday or earlier for reliable results."
- Changed
get_workout1 field changed- changed
Input schema / properties / workoutKey / descriptionPrevious value: -"Unique workout identifier from list_workouts."New value: +"Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first. Passing an invalid key throws SuuntoNotFoundError."
- Changed
get_workout_fit1 field changed- changed
Input schema / properties / workoutKey / descriptionPrevious value: -"Unique workout identifier from list_workouts."New value: +"Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first."
- Changed
get_workout_samples1 field changed- changed
Input schema / properties / workoutKey / descriptionPrevious value: -"Unique workout identifier from list_workouts."New value: +"Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first. Passing an invalid key throws SuuntoNotFoundError."
- Changed
list_daily_activity2 fields changed- changed
Input schema / properties / from / descriptionPrevious value: -"Start date YYYY-MM-DD, inclusive. Example: 2026-04-01."New value: +"Start date YYYY-MM-DD, inclusive. Must be ≤ to. Days without synced data are silently omitted, not 404." - changed
Input schema / properties / to / descriptionPrevious value: -"End date YYYY-MM-DD, inclusive. Example: 2026-04-30."New value: +"End date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer ranges ≤ 30 days for responsiveness."
- Changed
list_recovery2 fields changed- changed
Input schema / properties / from / descriptionPrevious value: -"Start date YYYY-MM-DD, inclusive. Example: 2026-04-01."New value: +"Start date YYYY-MM-DD, inclusive. Must be ≤ to. Days without recovery data are silently omitted, not 404." - changed
Input schema / properties / to / descriptionPrevious value: -"End date YYYY-MM-DD, inclusive. Example: 2026-04-30."New value: +"End date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer ranges ≤ 30 days for responsiveness."
- Changed
list_sleep2 fields changed- changed
Input schema / properties / from / descriptionPrevious value: -"First wake-up date YYYY-MM-DD, inclusive. Example: 2026-04-01."New value: +"First wake-up date YYYY-MM-DD, inclusive. Must be ≤ to. Nights without recorded sleep are silently omitted, not 404." - changed
Input schema / properties / to / descriptionPrevious value: -"Last wake-up date YYYY-MM-DD, inclusive. Example: 2026-04-30."New value: +"Last wake-up date YYYY-MM-DD, inclusive. Future dates are accepted but produce no entries. Prefer ranges ≤ 30 days for responsiveness."
- Changed
list_workouts3 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Maximum number of workouts to return (1–1000). Defaults to 25."New value: +"Maximum number of workouts to return (1–1000). Defaults to 25. Pagination is automatic across API pages; set to 1 for the single most-recent workout." - changed
Input schema / properties / since / descriptionPrevious value: -"ISO 8601 lower bound on startTime (inclusive). Example: 2026-04-01T00:00:00Z."New value: +"ISO 8601 lower bound on startTime (inclusive). Filters on workout start time; omit for all time. Pagination is automatic so since does not affect page size." - changed
Input schema / properties / until / descriptionPrevious value: -"ISO 8601 upper bound on startTime (inclusive)."New value: +"ISO 8601 upper bound on startTime (inclusive). Omit to include workouts up to the present. Combine with since to target a specific window."
7 tool updates
v0.9.1- Changed
get_daily_activity3 fields changed- added
Input schema / properties / date / examplesAdded value: +[ + "2026-04-20" +] - added
Input schema / properties / date / maxLengthAdded value: +10 - added
Input schema / properties / date / minLengthAdded value: +10
- Changed
get_recovery3 fields changed- added
Input schema / properties / date / examplesAdded value: +[ + "2026-04-20" +] - added
Input schema / properties / date / maxLengthAdded value: +10 - added
Input schema / properties / date / minLengthAdded value: +10
- Changed
get_sleep3 fields changed- added
Input schema / properties / date / examplesAdded value: +[ + "2026-04-20" +] - added
Input schema / properties / date / maxLengthAdded value: +10 - added
Input schema / properties / date / minLengthAdded value: +10
- Changed
list_daily_activity6 fields changed- added
Input schema / properties / from / examplesAdded value: +[ + "2026-04-01" +] - added
Input schema / properties / from / maxLengthAdded value: +10 - added
Input schema / properties / from / minLengthAdded value: +10 - added
Input schema / properties / to / examplesAdded value: +[ + "2026-04-30" +] - added
Input schema / properties / to / maxLengthAdded value: +10 - added
Input schema / properties / to / minLengthAdded value: +10
- Changed
list_recovery6 fields changed- added
Input schema / properties / from / examplesAdded value: +[ + "2026-04-01" +] - added
Input schema / properties / from / maxLengthAdded value: +10 - added
Input schema / properties / from / minLengthAdded value: +10 - added
Input schema / properties / to / examplesAdded value: +[ + "2026-04-30" +] - added
Input schema / properties / to / maxLengthAdded value: +10 - added
Input schema / properties / to / minLengthAdded value: +10
- Changed
list_sleep6 fields changed- added
Input schema / properties / from / examplesAdded value: +[ + "2026-04-01" +] - added
Input schema / properties / from / maxLengthAdded value: +10 - added
Input schema / properties / from / minLengthAdded value: +10 - added
Input schema / properties / to / examplesAdded value: +[ + "2026-04-30" +] - added
Input schema / properties / to / maxLengthAdded value: +10 - added
Input schema / properties / to / minLengthAdded value: +10
- Changed
list_workouts2 fields changed- added
Input schema / properties / since / examplesAdded value: +[ + "2026-04-01T00:00:00Z" +] - added
Input schema / properties / until / examplesAdded value: +[ + "2026-04-30T23:59:59Z" +]
11 tool updates
v0.9.0- Changed
export_workout_gpx2 fields changed- added
Input schema / properties / workoutKey / descriptionAdded value: +"Unique workout identifier from list_workouts." - added
Input schema / properties / workoutKey / minLengthAdded value: +1
- Changed
get_daily_activity3 fields changed- changed
Input schema / properties / date / descriptionPrevious value: -"YYYY-MM-DD"New value: +"Calendar date YYYY-MM-DD. Example: 2026-04-20." - added
Input schema / properties / date / formatAdded value: +"date" - added
Input schema / properties / date / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$"
- Changed
get_recovery3 fields changed- changed
Input schema / properties / date / descriptionPrevious value: -"YYYY-MM-DD"New value: +"Calendar date YYYY-MM-DD. Example: 2026-04-20." - added
Input schema / properties / date / formatAdded value: +"date" - added
Input schema / properties / date / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$"
- Changed
get_sleep3 fields changed- changed
Input schema / properties / date / descriptionPrevious value: -"YYYY-MM-DD"New value: +"Wake-up date YYYY-MM-DD. Example: 2026-04-20." - added
Input schema / properties / date / formatAdded value: +"date" - added
Input schema / properties / date / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$"
- Changed
get_workout2 fields changed- added
Input schema / properties / workoutKey / descriptionAdded value: +"Unique workout identifier from list_workouts." - added
Input schema / properties / workoutKey / minLengthAdded value: +1
- Changed
get_workout_fit3 fields changed- changed
Input schema / properties / full / descriptionPrevious value: -"If true, returns ALL parsed records (large). Default false returns a summary + sampled records."New value: +"false (default): return compact summary. true: return all parsed FIT records." - added
Input schema / properties / workoutKey / descriptionAdded value: +"Unique workout identifier from list_workouts." - added
Input schema / properties / workoutKey / minLengthAdded value: +1
- Changed
get_workout_samples2 fields changed- added
Input schema / properties / workoutKey / descriptionAdded value: +"Unique workout identifier from list_workouts." - added
Input schema / properties / workoutKey / minLengthAdded value: +1
- Changed
list_daily_activity6 fields changed- changed
Input schema / properties / from / descriptionPrevious value: -"YYYY-MM-DD (inclusive)"New value: +"Start date YYYY-MM-DD, inclusive. Example: 2026-04-01." - added
Input schema / properties / from / formatAdded value: +"date" - added
Input schema / properties / from / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$" - changed
Input schema / properties / to / descriptionPrevious value: -"YYYY-MM-DD (inclusive)"New value: +"End date YYYY-MM-DD, inclusive. Example: 2026-04-30." - added
Input schema / properties / to / formatAdded value: +"date" - added
Input schema / properties / to / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$"
- Changed
list_recovery6 fields changed- changed
Input schema / properties / from / descriptionPrevious value: -"YYYY-MM-DD"New value: +"Start date YYYY-MM-DD, inclusive. Example: 2026-04-01." - added
Input schema / properties / from / formatAdded value: +"date" - added
Input schema / properties / from / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$" - changed
Input schema / properties / to / descriptionPrevious value: -"YYYY-MM-DD"New value: +"End date YYYY-MM-DD, inclusive. Example: 2026-04-30." - added
Input schema / properties / to / formatAdded value: +"date" - added
Input schema / properties / to / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$"
- Changed
list_sleep6 fields changed- changed
Input schema / properties / from / descriptionPrevious value: -"YYYY-MM-DD"New value: +"First wake-up date YYYY-MM-DD, inclusive. Example: 2026-04-01." - added
Input schema / properties / from / formatAdded value: +"date" - added
Input schema / properties / from / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$" - changed
Input schema / properties / to / descriptionPrevious value: -"YYYY-MM-DD"New value: +"Last wake-up date YYYY-MM-DD, inclusive. Example: 2026-04-30." - added
Input schema / properties / to / formatAdded value: +"date" - added
Input schema / properties / to / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$"
- Changed
list_workouts8 fields changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Max workouts to return."New value: +"Maximum number of workouts to return (1–1000). Defaults to 25." - added
Input schema / properties / limit / maximumAdded value: +1000 - added
Input schema / properties / limit / minimumAdded value: +1 - changed
Input schema / properties / limit / typePrevious value: -"number"New value: +"integer" - changed
Input schema / properties / since / descriptionPrevious value: -"ISO 8601 datetime — only workouts on/after this time."New value: +"ISO 8601 lower bound on startTime (inclusive). Example: 2026-04-01T00:00:00Z." - added
Input schema / properties / since / formatAdded value: +"date-time" - changed
Input schema / properties / until / descriptionPrevious value: -"ISO 8601 datetime — only workouts on/before this time."New value: +"ISO 8601 upper bound on startTime (inclusive)." - added
Input schema / properties / until / formatAdded value: +"date-time"
12 tool updates
v0.1.0- First observed
export_workout_gpx - First observed
get_daily_activity - First observed
get_recovery - First observed
get_sleep - First observed
get_workout - First observed
get_workout_fit - First observed
get_workout_samples - First observed
list_daily_activity - First observed
list_recovery - First observed
list_sleep - First observed
list_subscriptions - First observed
list_workouts
TDQS
Scored across 26 tools
Most tools have clearly distinct resource/action targets, including separate single-day vs range tools and separate workout summary/FIT/lap tools. Some overlap remains between get_daily_snapshot and the granular sleep/recovery/activity/workout tools, and between the various daily-activity tools, but the descriptions explain when to use each.
All tool names use snake_case and follow a predictable verb_noun pattern such as list_workouts, get_workout_laps, upload_route, push_strength_guide, and delete_guide. Minor variation in verbs reflects real differences in operation type rather than inconsistent naming.
26 tools is heavy for the server's surface, with many single/range pairs, multiple guide-push tools, several daily-activity variants, and an aggregate snapshot tool overlapping the granular tools. The domain is broad, but the count is still on the excessive side and likely to increase selection burden.
The surface covers the core Suunto workflows well: workouts, sleep, recovery, activity, routes, guides, uploads, training load, and digest generation. Notable gaps include no deletion or update for routes and no workout deletion, but these are minor for the main read/write use cases.
Maintenance
Related MCP Connectors
Connect Claude to your Intervals.icu watch data for fitness, workout review, and plan writing.
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
WHOOP recovery, strain, sleep and workouts in Claude via official WHOOP OAuth. Free, open source.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceMCP server for Polar Signals Cloud continuous profiling platform, enabling AI assistants to analyze CPU performance, memory usage, and identify optimization opportunities in production systems.9-
- FlicenseNot gradedqualityDmaintenanceEnables ChatGPT to access and analyze personal Garmin health data including daily steps, heart rate, calories, sleep duration, and body battery levels. Collects data via webhook from Garmin devices and provides health insights through natural language queries.2-
- AlicenseAqualityNot gradedmaintenanceEnables interaction with Siemens Polarion requirements management system through natural language. Supports authentication, project management, work item queries, document access, and requirements analysis.9MIT
- AlicenseBqualityDmaintenanceEnables users to query Apple Health metrics, workouts, and trends from CSV files exported via the Health Auto Export app. It allows MCP clients to analyze health data such as heart rate, sleep stages, and activity levels directly from local iCloud Drive storage.34 npm1MIT