suunto-mcp
An MCP server for authoring and managing structured workouts (SuuntoPlus™ Guides) on Suunto watches, with swappable transports (file, Cloud API, or private mobile API) and a coach-oriented domain model that lowers to the display-oriented guide format.
README
suunto-mcp
An MCP server for authoring and managing structured workouts (SuuntoPlus™ Guides) on Suunto, designed so the transport can be swapped without touching the workout model.
Not affiliated with or endorsed by Suunto Oy.
Why it's built this way
There are three possible ways to get a structured workout onto a Suunto watch, and they differ enough that the transport has to be a replaceable part:
| Path | Status | Notes |
|---|---|---|
Cloud API (cloudapi.suunto.com/v2/guides) |
Documented, needs a subscription key | The sanctioned path. Contract fully captured in docs/cloud-api.md. |
Private mobile API (suuntoplus/guides/*) |
Fully mapped and live-verified, reads and writes, unsanctioned | Confirmed by static analysis of the Suunto Android app, then exercised for real: create → duplicate-externalId 409 → update → delete, all against a live account. Full write-up in docs/private-guides-api.md. Genuinely undocumented; needs no new credential — reuses a suuntool session. |
| Local zip | Works today | Emit a validated guide.zip; no auth involved. |
suuntool deliberately isn't one of these paths: it has no guide-creation
capability at all. What it does have is a good, read-only MCP server of its
own — suuntool mcp — for completed activity and wellness data, comments,
reactions, and profile info. This server doesn't wrap or re-expose any of
that: run the two side by side as separate MCP configs
(claude mcp add suunto-mcp -- ... and claude mcp add suuntool -- suuntool mcp)
rather than have this one duplicate a surface suuntool already covers better.
The private backend's use of suuntool's session file (below) is credential
reuse, not functionality overlap — it's the reason suuntool is a prerequisite
for the private backend either way, so adding its own MCP server alongside
this one costs nothing extra.
Its exit codes double as this server's own error taxonomy: codes 2–7
(USAGE/NETWORK/AUTH_EXPIRED/SERVER/NOT_FOUND/FORBIDDEN) are
numerically identical on both, so a session that has expired in suuntool's own
session.json surfaces through the same code as an expired Cloud API token.
The interesting part
The guide format is a display format, not a training format. It has no step
roles, no percentages, no nesting, and a 13-character title budget. So the domain
model is what a coach would write, and src/compile lowers it:
- roles (
warmup/work/rest/…) → titles, notifications and lap marks - durations → a per-step
triggerplus a matching countdown field - every duration/distance trigger grants lap-skip by default — a compound
{type:"or", triggers:[base, {type:"manualLap"}]}pluscreateManualLap:true, confirmed live against a real Runna guide after a user reported this compiler's own workouts couldn't be skipped early. Opt a step out withallowSkip: falseto lock it instead. - pace ranges → m/s, with the bounds inverted (4:15–4:25 /km is 3.77–3.92 m/s)
- cadence → Hertz (180 spm is 3.0)
%HRmax/%FTP→ absolutes, resolved from the athlete profile- nested repeats → flattened, keeping the outer block so the step budget survives
- every string truncated and charset-sanitised for the watch display
Correctness is anchored on Suunto's own published sample guide, which is stored
verbatim in test/fixtures/ and used two ways: to prove the format model accepts
real Suunto output, and as the compiler's target.
Layout
src/domain/ workout model, guide wire format, validator, limits, activity IDs
src/compile/ the lowering compiler, unit conversions, externalId hashing
src/package/ zip packing (manifest.json + guide.json + icon.png)
src/backends/ the GuideBackend port and its implementations
src/mcp/ MCP server
scripts/ APK acquisition and static analysis for the RE track
docs/ captured API contracts and RE findings
Running it
Tool tiers follow suuntool's: read-only by default, --allow-write to create and
update, --allow-destructive on top of that to delete. Gating happens at
registration, so a tool you have not permitted is absent from the listing
entirely rather than present and always refusing.
claude mcp add --scope user suunto-mcp -e SUUNTO_MCP_BACKEND=private -- node /path/to/suunto-mcp/dist/mcp/main.js --allow-write
| Tier | Tools |
|---|---|
| read | preview_workout, list_workouts, describe_backend |
--allow-write |
create_workout, update_workout |
--allow-destructive |
delete_workout |
preview_workout compiles and validates without uploading, and returns the
warnings — start there.
For completed-activity and recovery data, add suuntool's own MCP server as a
separate config rather than expecting this one to cover it:
claude mcp add --scope user suuntool -- suuntool mcp
Configuration
| Variable | Purpose |
|---|---|
SUUNTO_MCP_BACKEND |
file (default), cloud, or private (reuses a suuntool session; see the warning above) |
SUUNTO_MCP_OUTPUT_DIR |
Where the file backend writes; defaults under ~/.local/share |
SUUNTO_OWNER |
Creator name. Must match the OAuth app name for the Cloud API |
SUUNTO_SUBSCRIPTION_KEY |
Ocp-Apim-Subscription-Key, required for cloud |
SUUNTO_ACCESS_TOKEN |
Static bearer token, for trying the API by hand |
SUUNTO_CLIENT_ID / SUUNTO_CLIENT_SECRET |
Enables refresh of the 24h token |
SUUNTO_MAX_HR, SUUNTO_THRESHOLD_HR, SUUNTO_FTP, SUUNTO_REST_HR |
Athlete profile, needed only for %HRmax / %FTP targets |
Configuration is validated at startup and a bad config is a hard exit — an MCP server that starts and then fails every call is much harder to diagnose.
Development
pnpm install
pnpm test
pnpm typecheck
Reverse-engineering track
scripts/pull-apk.sh # pull the APK off a connected Android device
scripts/analyze-apk.sh # stage 1: fast dex string scan
scripts/analyze-apk.sh 2 # stage 2: full jadx decompile, only if needed
apk/ and capture/ are git-ignored and must stay that way — captures contain
session keys and account identifiers.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。