ergzone-mcp 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/client.mjs +11 -3
- package/src/tools.mjs +39 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ You need a Concept2 Logbook account (the one you use on log.concept2.com).
|
|
|
14
14
|
|
|
15
15
|
### Claude Desktop — one-click (no terminal)
|
|
16
16
|
|
|
17
|
-
1. Download `ergzone-mcp.mcpb`
|
|
17
|
+
1. [Download `ergzone-mcp.mcpb`](https://github.com/malveo/ergzone-mcp/releases/latest/download/ergzone-mcp.mcpb) (direct link, always the latest).
|
|
18
18
|
2. Double-click it (or drag it into Claude Desktop → Settings → Extensions).
|
|
19
19
|
3. Type your Logbook email and password in the form, and make sure the extension is **enabled**. Done.
|
|
20
20
|
|
|
@@ -57,6 +57,7 @@ Prefer not to store your password? Use `ERGZONE_SESSION_TOKEN` instead (copied f
|
|
|
57
57
|
| Tool | What it does |
|
|
58
58
|
|------|------|
|
|
59
59
|
| `auth_check` | who am I |
|
|
60
|
+
| `update_profile` | set max HR, resting HR, weight, weight unit |
|
|
60
61
|
| `list_workouts` / `get_workout` | browse workouts |
|
|
61
62
|
| `create_workout` / `update_workout` / `delete_workout` | manage workouts |
|
|
62
63
|
| `build_intervals` | preview intervals before saving |
|
|
@@ -73,6 +74,7 @@ Just talk to Claude in plain language:
|
|
|
73
74
|
- **Create** — *"Create a workout 'Tuesday tempo' with 3 × 2 min at 22 spm, 1 min rest."*
|
|
74
75
|
- **Progressive intensity** — *"Build a workout: 2 blocks of 1-2-1-3-1-4 minutes, each interval 0.1s/500m faster than the previous, 4 min rest between blocks."*
|
|
75
76
|
- **Results** — *"Show my last few ErgZone results."*
|
|
77
|
+
- **Profile** — *"Set my max heart rate to 186."*
|
|
76
78
|
- **Analysis** — *"Analyze my last result: pace, SPI and HR zone per interval."*
|
|
77
79
|
- **Stats** — *"How many meters did I row this month?"*
|
|
78
80
|
- **Tidy up** — *"Delete the workout 'Tuesday tempo'."*
|
package/package.json
CHANGED
package/src/client.mjs
CHANGED
|
@@ -109,12 +109,20 @@ export function todayISO() {
|
|
|
109
109
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// A real ErgZone track id is a UUID. This guards against an unresolved MCPB
|
|
113
|
+
// placeholder (e.g. the literal "${user_config.track_id}" when the user leaves
|
|
114
|
+
// the field blank), which is truthy and would otherwise poison resolution and
|
|
115
|
+
// disable the auto-detect fallback.
|
|
116
|
+
const isValidTrackId = (v) =>
|
|
117
|
+
typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v.trim());
|
|
118
|
+
|
|
112
119
|
// Resolve the track to use, in order: explicit arg -> ERGZONE_TRACK_ID -> auto-discover
|
|
113
|
-
// the user's personal "My Workouts" track.
|
|
120
|
+
// the user's personal "My Workouts" track. Invalid/placeholder values are ignored
|
|
121
|
+
// so the auto-detect path still runs. Discovery result is cached in memory.
|
|
114
122
|
let trackIdCache = null;
|
|
115
123
|
export async function resolveTrackId(explicit) {
|
|
116
|
-
if (explicit) return explicit;
|
|
117
|
-
if (process.env.ERGZONE_TRACK_ID) return process.env.ERGZONE_TRACK_ID;
|
|
124
|
+
if (isValidTrackId(explicit)) return explicit.trim();
|
|
125
|
+
if (isValidTrackId(process.env.ERGZONE_TRACK_ID)) return process.env.ERGZONE_TRACK_ID.trim();
|
|
118
126
|
if (trackIdCache) return trackIdCache;
|
|
119
127
|
|
|
120
128
|
const data = await gql('query{ tracks(onlyAdmin:true){ id name trackMode type isOwner } }');
|
package/src/tools.mjs
CHANGED
|
@@ -45,6 +45,45 @@ export const TOOLS = [
|
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
|
|
48
|
+
{
|
|
49
|
+
name: 'update_profile',
|
|
50
|
+
description:
|
|
51
|
+
'Update your profile settings: max HR, resting HR, weight, weight unit. Only the fields you pass are changed (others are left untouched). Sets these on the account so analyze_result and the HR zones use them automatically.',
|
|
52
|
+
write: true,
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
maxHeartRate: { type: 'number', description: 'Max heart rate (bpm), integer' },
|
|
57
|
+
restingHeartRate: { type: 'number', description: 'Resting heart rate (bpm), integer' },
|
|
58
|
+
weight: { type: 'number', description: 'Body weight, integer in the chosen weightUnit' },
|
|
59
|
+
weightUnit: { type: 'string', enum: ['kg', 'lbs'], description: 'Weight unit: kg or lbs' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
async handler(args) {
|
|
63
|
+
const settings = {};
|
|
64
|
+
// Send only provided fields so unspecified ones are not overwritten.
|
|
65
|
+
// maxHeartRate / restingHeartRate accept null to clear the value.
|
|
66
|
+
if ('maxHeartRate' in args) settings.maxHeartRate = args.maxHeartRate == null ? null : Math.round(args.maxHeartRate);
|
|
67
|
+
if ('restingHeartRate' in args) settings.restingHeartRate = args.restingHeartRate == null ? null : Math.round(args.restingHeartRate);
|
|
68
|
+
if ('weight' in args) settings.weight = args.weight == null ? null : Math.round(args.weight);
|
|
69
|
+
if ('weightUnit' in args) settings.weightUnit = args.weightUnit;
|
|
70
|
+
|
|
71
|
+
if (Object.keys(settings).length === 0) {
|
|
72
|
+
throw new Error('Nothing to update: pass at least one of maxHeartRate, restingHeartRate, weight, weightUnit.');
|
|
73
|
+
}
|
|
74
|
+
if (settings.weightUnit != null && !['kg', 'lbs'].includes(settings.weightUnit)) {
|
|
75
|
+
throw new Error('weightUnit must be "kg" or "lbs".');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const d = await gql(
|
|
79
|
+
`mutation($s:SettingsInput!){ settingsUpdate(settings:$s){ id name email maxHeartRate restingHeartRate weight weightUnit } }`,
|
|
80
|
+
{ s: settings },
|
|
81
|
+
);
|
|
82
|
+
if (!d.settingsUpdate) throw new Error('Profile update failed.');
|
|
83
|
+
return { updated: d.settingsUpdate };
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
|
|
48
87
|
{
|
|
49
88
|
name: 'list_workouts',
|
|
50
89
|
description: 'List the workouts in a track (defaults to your "My Workouts" track, auto-detected). Optional filters: search, limit.',
|