# Suunto MCP (npm · suunto-mcp)

Suunto watch data (workouts, sleep, recovery, 24/7 activity) for Claude and other AI assistants.

- Trust score: 89/100 (high trust)
- Change this week: +55
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-16

## Components

- npm · `suunto-mcp`: 89/100 (this document), [markdown](https://verifymcp.io/servers/googlarz-suunto-mcp/suunto-mcp.md), [page](https://verifymcp.io/servers/googlarz-suunto-mcp/suunto-mcp)

## Channel facts

- Registry: `npm`
- Package: `suunto-mcp`
- Version: `0.19.2`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-16.

- **Supply Chain Security**: 98/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - 34 of 110 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 100/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to googlarz/suunto-mcp).
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 0 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 78/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 7029 tokens (~226/item across 31 items; 26 tools + 5 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 57/100
  - Stability observed for 17 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 99/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 96% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 1 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 27 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the Suunto MCP server?

Suunto MCP runs locally as an npm package, launched with npx -y suunto-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add googlarz-suunto-mcp -- npx -y suunto-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "googlarz-suunto-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "suunto-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "googlarz-suunto-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "suunto-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add googlarz-suunto-mcp -- npx -y suunto-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "googlarz-suunto-mcp": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "suunto-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add googlarz-suunto-mcp --command npx --arg -y --arg suunto-mcp
```

### Hermes

```yaml
mcp_servers:
  googlarz-suunto-mcp:
    command: "npx"
    args: ["-y", "suunto-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "googlarz-suunto-mcp": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "suunto-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add googlarz-suunto-mcp -t stdio -c npx -a -y suunto-mcp
```

### Other

```json
{
  "mcpServers": {
    "googlarz-suunto-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "suunto-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-10-16 (score 89, +16)

- [security improvement] Known CVEs: unverified → pass
- [security improvement] Source repository: unverified → pass
- [functional improvement] Dependency health: unverified → 0.87

### 2026-10-15 (score 73, −15)

- [security regression] Provenance: pass → unverified
- [security regression] Stability: 0.50 → unverified
- [security regression] Known CVEs: pass → unverified
- [security regression] Tool safety: pass → unverified
- [functional regression] Capabilities: pass → unverified
- [functional regression] Dependency health: 0.87 → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional regression] Schema quality: 100 → unverified
- [functional] Package version: 0.19.0 → 0.19.2
- [functional] Package version: 0.19.0 → 0.19.1

### 2026-10-14 (score 88, +39)

- [security improvement] Injection markers: unverified → pass
- [functional regression] Schema quality: 201 → 223
- [functional improvement] Tool coverage: unverified → 100
- [functional improvement] Stability: unverified → 0.50
- [functional improvement] Schema quality: unverified → 100
- [functional improvement] MCP protocol: unverified → pass
- [functional] Package version: 0.15.2 → 0.19.0

### 2026-10-12 (score 49, +15)

- [security improvement] Malware scan: unverified → pass

### 2026-10-09 (score 34, 0)

- [functional improvement] Security disclosure: fail → pass
- [functional] Package version: 0.18.0 → 0.19.0

### 2026-10-06 (score 34, −49)

- [security regression] Malware scan: pass → unverified
- [security regression] Tool safety: pass → unverified
- [security regression] Stability: 0.17 → unverified
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional regression] Schema quality: 100 → unverified
- [functional] Package version: 0.15.2 → 0.18.0

### 2026-10-03 (score 83, +16)

- [security improvement] Malware scan: unverified → pass

### 2026-10-02 (score 67, −14)

- [security regression] Malware scan: pass → unverified
- [functional improvement] Stability: unverified → 0.10
- [functional] Package version: 0.15.1 → 0.15.2

## MCP tools (26)

### `list_workouts` (~338 tokens)

List workouts

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.

Input parameters:

- `limit` (integer): 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` (string): 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` (string): ISO 8601 upper bound on startTime (inclusive). Omit to include workouts up to the present. Combine with since to target a specific window.

### `get_workout` (~185 tokens)

Get workout

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.

Input parameters:

- `workoutKey` (string, required): Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first. Passing an invalid key throws SuuntoNotFoundError.

### `get_workout_samples` (~157 tokens)

Get workout samples

UNAVAILABLE — Suunto's API gateway currently rejects this endpoint (/v2/workout/samples) with 401 OperationNotFound on the account it was tested with (September 2026), so the call fails with an 'endpoint unavailable' error; it is not an authentication problem. Use get_workout_fit with full=true for record-level data (heart rate etc.), or get_workout_laps for laps. Kept so the tool starts working again if Suunto restores the endpoint. Read-only.

Input parameters:

- `workoutKey` (string, required): Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first. Passing an invalid key throws SuuntoNotFoundError.

### `get_workout_fit` (~226 tokens)

Get workout FIT data

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.

Input parameters:

- `full` (boolean): false (default): return compact summary. true: return all parsed FIT records.
- `workoutKey` (string, required): Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first.

### `get_daily_snapshot` (~710 tokens)

Get daily snapshot

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 fetch…

Input parameters:

- `date` (string, required): 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 toda…
- `to` (string): 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…

### `get_workout_laps` (~719 tokens)

Get workout laps

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 '<restSec>s target ·…

Input parameters:

- `workoutKey` (string, required): The 24-character workoutKey returned by list_workouts. Anything else fails with a not-found error without calling Suunto.

### `export_workout_gpx` (~151 tokens)

Export workout as GPX

UNAVAILABLE — Suunto's API gateway currently rejects this endpoint (/v2/workout/exportGpx) with 401 OperationNotFound on the account it was tested with (September 2026), so the call fails with an 'endpoint unavailable' error; it is not an authentication problem. Would return the workout's GPS route as a GPX 1.1 XML string. Kept so the tool starts working again if Suunto restores the endpoint. Read-only.

Input parameters:

- `workoutKey` (string, required): Opaque server-assigned string returned by list_workouts. Not guessable or constructable — always discover via list_workouts first. Passing an invalid key throws SuuntoNotFoundError.

### `get_daily_activity` (~222 tokens)

Get daily activity

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.

Input parameters:

- `date` (string, required): 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 par…

### `list_daily_activity` (~224 tokens)

List daily activity

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.

Input parameters:

- `from` (string, required): Start date YYYY-MM-DD, inclusive. Must be ≤ to. Days without synced data are silently omitted, not 404.
- `to` (string, required): 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).

### `get_sleep` (~431 tokens)

Get sleep

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.

Input parameters:

- `date` (string, required): 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…

### `list_sleep` (~341 tokens)

List sleep

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.

Input parameters:

- `from` (string, required): 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.
- `to` (string, required): 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.

### `get_recovery` (~237 tokens)

Get recovery

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.

Input parameters:

- `date` (string, required): 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 par…

### `list_recovery` (~216 tokens)

List recovery

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.

Input parameters:

- `from` (string, required): Start date YYYY-MM-DD, inclusive. Must be ≤ to. Days without recovery data are silently omitted, not 404.
- `to` (string, required): 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.

### `get_daily_activity_statistics` (~341 tokens)

Get daily activity statistics

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.

Input parameters:

- `enddate` (string, required): 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` (string, required): 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 becau…

### `list_subscriptions` (~103 tokens)

List webhook subscriptions

UNAVAILABLE — Suunto's API gateway currently rejects this endpoint (/v2/subscriptions) with 401 OperationNotFound on the account it was tested with (September 2026), so the call fails with an 'endpoint unavailable' error rather than returning a list. Would return the active webhook subscriptions as an array of { id, eventType, callbackUrl, createdAt }. Kept so the tool starts working again if Suunto restores the endpoint. Read-only.

### `list_routes` (~54 tokens)

List routes

Returns all routes saved in the user's Suunto account. Each route: id, description, visibility, distance (m), start/end coordinates, waypoint count. Use export_route to get the GPX track for navigation. Read-only.

### `export_route` (~70 tokens)

Export route as GPX

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.

Input parameters:

- `routeId` (string, required): Route ID returned by list_routes.

### `upload_route` (~355 tokens)

Import GPX route

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 <rte> or <trk> element in the file becomes its own route; the route name comes from its <name> (or <desc>), and waypoints are created from <wpt> 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.

Input parameters:

- `activities` (array): 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.
- `filePath` (string, required): Absolute path to the .gpx file on disk.

### `upload_workout` (~197 tokens)

Upload workout file

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). Returns an uploadId you can poll with get_upload_status. 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.

Input parameters:

- `comment` (string): Longer notes for the workout. Optional.
- `description` (string): Short workout title shown in the Suunto app. Optional.
- `filePath` (string, required): Absolute path to the .fit or .gpx file on disk.
- `privacy` (string): Visibility. DEFAULT uses the account's default setting.

### `push_workout_guide` (~230 tokens)

Push workout guide to watch

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. Write operation.

Input parameters:

- `date` (string, required): Session date YYYY-MM-DD.
- `exercises` (array, required)
- `guideId` (string): If provided, updates this existing guide instead of creating a new one.
- `title` (string, required): Short session name shown in the Suunto app, e.g. 'Push A'.

### `push_interval_guide` (~270 tokens)

Push interval guide to watch

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. Write operation.

Input parameters:

- `blocks` (array, required): 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…
- `date` (string, required): Session date YYYY-MM-DD.
- `guideId` (string): If provided, updates this existing guide instead of creating a new one.
- `title` (string, required): Short session name shown in the Suunto app, e.g. '4x4 VO2max'.

### `push_strength_guide` (~542 tokens)

Push strength guide to watch

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. Write operation.

Input parameters:

- `date` (string, required): Session date YYYY-MM-DD.
- `exercises` (array, required)
- `guideId` (string): If provided, updates this existing guide instead of creating a new one.
- `lapGranularity` (string): '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 ste…
- `restMode` (string): 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 lapGra…
- `title` (string, required): Short session name shown in the Suunto app, e.g. 'Push A'.

### `list_guides` (~91 tokens)

List SuuntoPlus guides

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.

### `delete_guide` (~93 tokens)

Delete SuuntoPlus guide

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).

Input parameters:

- `guideId` (string, required): Guide id, from list_guides or from a previous push_*_guide response.

### `get_upload_status` (~78 tokens)

Get workout upload status

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.

Input parameters:

- `uploadId` (string, required): Upload ID returned by upload_workout.

### `generate_daily_digest` (~358 tokens)

Generate daily health digest

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).

Input parameters:

- `date` (string, required): Calendar date YYYY-MM-DD to summarize. Use yesterday or earlier — today's data is usually partial until the watch has synced.
- `seedAtl` (number): Same as seedCtl but for Fatigue (ATL). Only used on the very first digest ever run.
- `seedCtl` (number): 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 t…

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/googlarz-suunto-mcp/suunto-mcp#diagnostics

## Score history

- 2026-10-16: 89
- 2026-10-15: 73
- 2026-10-14: 88
- 2026-10-13: 49
- 2026-10-12: 49
- 2026-10-09: 34
- 2026-10-06: 34
- 2026-10-04: 83
- 2026-10-03: 83
- 2026-10-02: 67
- 2026-10-01: 81
- 2026-09-30: 81
- 2026-09-29: 66

## Common questions

### What is the Suunto MCP server?

Suunto MCP is listed in the public MCP registry as io.github.googlarz/suunto-mcp. Suunto watch data (workouts, sleep, recovery, 24/7 activity) for Claude and other AI assistants. This page covers its npm package (suunto-mcp).

### Is the Suunto MCP server safe to use?

Suunto MCP scores 89 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 16 October 2026. It declares no install or post-install scripts. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Suunto MCP server expose?

Suunto MCP exposes 26 tools: list_workouts, get_workout, get_workout_samples, get_workout_fit, get_daily_snapshot, and 21 more. Their descriptions and schemas cost roughly 6,939 tokens of context every time the server is loaded.

### Is the Suunto MCP server still maintained?

Suunto MCP is still listed as active in the MCP registry. We last reached this channel on 16 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Suunto MCP server under?

Suunto MCP declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/suunto-mcp
- Socket report: https://socket.dev/npm/package/suunto-mcp
- Repository: https://github.com/googlarz/suunto-mcp
- Changelog RSS feed: https://verifymcp.io/servers/googlarz-suunto-mcp/suunto-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/googlarz-suunto-mcp/suunto-mcp.json
- HTML version of this page: https://verifymcp.io/servers/googlarz-suunto-mcp/suunto-mcp
