finalsurge-mcp
# finalsurge-mcp
A local MCP server that turns the coach's free-text workouts in Final Surge into structured workouts in the Final Surge builder. Final Surge then syncs them to the watch.
This project is not affiliated with Final Surge. It calls the undocumented endpoints of the Final Surge web app with your own account, and a change on their side can break it at any time.
## Use it
Requirements: macOS, [uv](https://docs.astral.sh/uv/), Claude Desktop, and a Final Surge account linked to your watch.
```bash
git clone https://github.com/pavelzaitsau/finalsurge-mcp.git
cd finalsurge-mcp
uv sync
FS_EMAIL=you@example.com uv run finalsurge-mcp --set-password
```
The last command stores the Final Surge password in the macOS Keychain under the service `finalsurge-mcp`.
Add the server to `~/Library/Application Support/Claude/claude_desktop_config.json` and restart Claude Desktop. Use the full path from `which uv` and the absolute path of the clone:
```json
"finalsurge": {
"command": "/opt/homebrew/bin/uv",
"args": ["--directory", "/absolute/path/to/finalsurge-mcp", "run", "finalsurge-mcp"],
"env": { "FS_EMAIL": "you@example.com" }
}
```
`src/finalsurge_mcp/rules.md` holds the author's own conventions: paces, heart rate and the coach's phrasing. Copy it, adjust it to your training, and point `FS_RULES_FILE` at your copy.
Then ask Claude: "Build this week's workouts from Final Surge and show me the preview."
Claude reads the rules, builds the steps and shows a preview with a `preview_token`. After approval, Claude saves the steps with that token. The watch receives the workout after the Final Surge app syncs.
## What it does not do
- It does not talk to the watch. Final Surge owns the sync to Apple Watch, Garmin and Coros.
- It does not parse the coach's text. Claude converts the text, and the server rejects steps that break a hard rule. See [ADR-0002](docs/adr/0002-claude-parses-server-validates.md).
- It does not use a public API. Final Surge has none, and the server calls the endpoints of the web app. See [the API notes](docs/final-surge-api.md).
- It does not overwrite a workout that already has steps, unless the request says so. An overwrite keeps a backup that `restore_backup` puts back.
## Configuration
| Setting | Default | Effect |
| --- | --- | --- |
| `FS_EMAIL` | none, required | Final Surge login, and the Keychain account name |
| `FS_PASSWORD` | Keychain | Password; overrides the Keychain entry. Avoid it in `claude_desktop_config.json`, which stores it as plain text |
| `FS_TOKEN` | none | Session token; skips the login |
| `FS_USER_KEY` | from the login or `/Profile` | Final Surge user key |
| `FS_RULES_FILE` | `src/finalsurge_mcp/rules.md` | Rules file returned to Claude |
| `FS_CONFIG_DIR` | `~/.config/finalsurge-mcp` | Token cache (mode 0600, tied to `FS_EMAIL`) and `backups/` |
| `FS_BASE_URL` | `https://beta.finalsurge.com/api` | API root |
A change to `rules.md` applies on the next call. A change to the Python code applies after a restart of Claude Desktop.
## Tools
| Tool | What it does |
| --- | --- |
| `list_workouts(start_date, end_date, only_unstructured)` | Lists calendar workouts with the coach's text |
| `get_parsing_rules()` | Returns `rules.md` |
| `get_examples(limit, days_back)` | Returns past coach texts with the steps built by hand, and flags the ones that break the current rules |
| `get_zones()` | Returns the heart-rate zones of the Final Surge profile, for reference |
| `preview_structured_workout(name, steps)` | Checks the steps, renders them and returns a `preview_token`; writes nothing |
| `save_structured_workout(workout_key, date, name, steps, preview_token, overwrite)` | Writes the previewed steps to the builder and reads them back |
| `restore_backup(backup_file)` | Puts back the steps that an overwrite replaced |
| `get_structured_workout(workout_key)` | Returns the steps in the builder |
## Troubleshooting
| Symptom | Cause | Fix |
| --- | --- | --- |
| `Login failed` | Wrong password, or Final Surge asks for a captcha | Run `--set-password` again, or set `FS_TOKEN` from `localStorage.getItem('auth-token')` on beta.finalsurge.com |
| A workout of the first day is missing | Final Surge excludes the start date of a range | Already handled; update the server if it reappears |
| A rule change has no effect | The change is in Python code | Restart Claude Desktop |
| `... is in the past` | The server saves only today and later | Pick a current workout |
| `preview_token does not match` | The steps changed after the preview | Preview again and approve the new text |
| `verified: false` after a save | The save succeeded, the read-back failed | Call `get_structured_workout`; do not overwrite |
## Development
```bash
scripts/check.sh
```
The script runs the tests, the Markdown gate (`markdownlint-cli2`, through `npx`) and the docs style gate (`vale`). GitHub Actions runs the same script on every push and pull request.
Enable the commit-message hook once per clone:
```bash
git config core.hooksPath .githooks
```
## More
- [Conversion rules](src/finalsurge_mcp/rules.md): read before changing how Claude builds a workout.
- [Final Surge API notes](docs/final-surge-api.md): read when an endpoint stops working.
- [Decisions](docs/adr/): read before changing the architecture.
- [Changelog](CHANGELOG.md): what changed in each version.
## License
[MIT](LICENSE)
TDQS
Scored across 8 tools
Most tools have clearly distinct roles: list_workouts reads the calendar, get_structured_workout reads the builder, preview/save/restore form an unambiguous write pipeline. The only mild overlap is between get_parsing_rules and get_examples, which are both 'reference material to read before building,' though their descriptions clarify the difference (rules vs. concrete examples).
All eight tools follow a strict snake_case verb_noun pattern: list_workouts, get_zones, get_parsing_rules, get_examples, get_structured_workout, preview_structured_workout, save_structured_workout, restore_backup. The shared 'structured_workout' stem across preview/save/get reinforces the pipeline.
Eight tools is well-scoped for a focused workout-builder server, with no filler. Each tool earns its place: calendar read, three reference reads, builder read, validation, write, and undo.
The build → preview → save → restore lifecycle is fully covered, including safety nets like backups and overwrite protection. Minor gaps exist: there is no way to create a new planned workout or clear steps without overwriting with new ones, but these are workable around within the stated workflow.