intervals-icu-mcp-server 0.1.0
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 +80 -0
- package/dist/client.d.ts +362 -0
- package/dist/client.js +236 -0
- package/dist/format.d.ts +5 -0
- package/dist/format.js +92 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +613 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# intervals-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [Intervals.icu](https://intervals.icu) — connects Claude / Claude Code to your training data.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `get_activities` | List activities with power, HR, training load, etc. |
|
|
10
|
+
| `get_activity_details` | Full metrics for a specific activity |
|
|
11
|
+
| `get_activity_intervals` | Per-interval breakdown (power, HR, speed, elevation) |
|
|
12
|
+
| `get_wellness_data` | Weight, HRV, sleep, CTL/ATL, subjective scores |
|
|
13
|
+
| `get_events` | Calendar events (workouts, races) |
|
|
14
|
+
| `get_event_by_id` | Full details for a specific event |
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
### 1. Install & build
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install
|
|
22
|
+
npm run build
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Configure credentials
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cp .env.example .env
|
|
29
|
+
# Edit .env and fill in your API_KEY and ATHLETE_ID
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- **API_KEY**: Settings → API → Generate key on intervals.icu
|
|
33
|
+
- **ATHLETE_ID**: The `i12345` part of your intervals.icu profile URL
|
|
34
|
+
|
|
35
|
+
### 3. Add to Claude Code
|
|
36
|
+
|
|
37
|
+
Add this to your `~/.claude/settings.json` (or project `.claude/settings.json`):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"intervals": {
|
|
43
|
+
"command": "node",
|
|
44
|
+
"args": ["/Users/spirohov/work/intervals/dist/index.js"],
|
|
45
|
+
"env": {
|
|
46
|
+
"API_KEY": "your_key_here",
|
|
47
|
+
"ATHLETE_ID": "i12345"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or use a `.env` file — the server loads it automatically from the project root.
|
|
55
|
+
|
|
56
|
+
### 4. Add to Claude Desktop
|
|
57
|
+
|
|
58
|
+
In `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"intervals": {
|
|
64
|
+
"command": "node",
|
|
65
|
+
"args": ["/Users/spirohov/work/intervals/dist/index.js"],
|
|
66
|
+
"env": {
|
|
67
|
+
"API_KEY": "your_key_here",
|
|
68
|
+
"ATHLETE_ID": "i12345"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm run dev # watch mode
|
|
79
|
+
npm run inspector # MCP inspector UI
|
|
80
|
+
```
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export interface ClientConfig {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
athleteId: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const ActivitySchema: z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
9
|
+
type: z.ZodOptional<z.ZodString>;
|
|
10
|
+
startTime: z.ZodOptional<z.ZodString>;
|
|
11
|
+
distance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
12
|
+
duration: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
13
|
+
moving_time: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
14
|
+
elevationGain: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
15
|
+
avgPower: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
16
|
+
icu_weighted_avg_watts: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
17
|
+
trainingLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
18
|
+
icu_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
19
|
+
icu_intensity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
20
|
+
avgHr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
21
|
+
max_heartrate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
22
|
+
average_cadence: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
23
|
+
calories: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
24
|
+
average_speed: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
25
|
+
icu_ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
26
|
+
icu_atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
27
|
+
trimp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
28
|
+
average_temp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
29
|
+
perceived_exertion: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
30
|
+
feel: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
31
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
|
+
trainer: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
33
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
34
|
+
id: z.ZodString;
|
|
35
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
36
|
+
type: z.ZodOptional<z.ZodString>;
|
|
37
|
+
startTime: z.ZodOptional<z.ZodString>;
|
|
38
|
+
distance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
39
|
+
duration: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
40
|
+
moving_time: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
41
|
+
elevationGain: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
42
|
+
avgPower: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
43
|
+
icu_weighted_avg_watts: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
44
|
+
trainingLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
45
|
+
icu_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
46
|
+
icu_intensity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
47
|
+
avgHr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
48
|
+
max_heartrate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
49
|
+
average_cadence: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
50
|
+
calories: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
51
|
+
average_speed: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
52
|
+
icu_ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
53
|
+
icu_atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
54
|
+
trimp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
55
|
+
average_temp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
56
|
+
perceived_exertion: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
57
|
+
feel: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
58
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
trainer: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
60
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
61
|
+
id: z.ZodString;
|
|
62
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
63
|
+
type: z.ZodOptional<z.ZodString>;
|
|
64
|
+
startTime: z.ZodOptional<z.ZodString>;
|
|
65
|
+
distance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
66
|
+
duration: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
67
|
+
moving_time: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
68
|
+
elevationGain: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
69
|
+
avgPower: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
70
|
+
icu_weighted_avg_watts: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
71
|
+
trainingLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
72
|
+
icu_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
73
|
+
icu_intensity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
74
|
+
avgHr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
75
|
+
max_heartrate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
76
|
+
average_cadence: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
77
|
+
calories: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
78
|
+
average_speed: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
79
|
+
icu_ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
80
|
+
icu_atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
81
|
+
trimp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
82
|
+
average_temp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
83
|
+
perceived_exertion: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
84
|
+
feel: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
85
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
86
|
+
trainer: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
87
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
88
|
+
export type Activity = z.infer<typeof ActivitySchema>;
|
|
89
|
+
export declare const WellnessSchema: z.ZodObject<{
|
|
90
|
+
id: z.ZodOptional<z.ZodString>;
|
|
91
|
+
ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
92
|
+
atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
93
|
+
weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
94
|
+
restingHR: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
95
|
+
hrv: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
96
|
+
hrvSDNN: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
97
|
+
sleepSecs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
98
|
+
sleepScore: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
99
|
+
sleepQuality: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
100
|
+
readiness: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
101
|
+
soreness: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
102
|
+
fatigue: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
103
|
+
stress: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
104
|
+
mood: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
105
|
+
motivation: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
106
|
+
steps: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
107
|
+
kcalConsumed: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
108
|
+
vo2max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
109
|
+
bodyFat: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
110
|
+
spO2: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
111
|
+
comments: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
112
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
113
|
+
id: z.ZodOptional<z.ZodString>;
|
|
114
|
+
ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
115
|
+
atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
116
|
+
weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
117
|
+
restingHR: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
118
|
+
hrv: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
119
|
+
hrvSDNN: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
120
|
+
sleepSecs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
121
|
+
sleepScore: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
122
|
+
sleepQuality: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
123
|
+
readiness: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
124
|
+
soreness: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
125
|
+
fatigue: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
126
|
+
stress: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
127
|
+
mood: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
128
|
+
motivation: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
129
|
+
steps: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
130
|
+
kcalConsumed: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
131
|
+
vo2max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
132
|
+
bodyFat: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
133
|
+
spO2: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
134
|
+
comments: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
135
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
136
|
+
id: z.ZodOptional<z.ZodString>;
|
|
137
|
+
ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
138
|
+
atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
139
|
+
weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
140
|
+
restingHR: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
141
|
+
hrv: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
142
|
+
hrvSDNN: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
143
|
+
sleepSecs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
144
|
+
sleepScore: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
145
|
+
sleepQuality: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
146
|
+
readiness: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
147
|
+
soreness: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
148
|
+
fatigue: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
149
|
+
stress: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
150
|
+
mood: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
151
|
+
motivation: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
152
|
+
steps: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
153
|
+
kcalConsumed: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
154
|
+
vo2max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
155
|
+
bodyFat: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
156
|
+
spO2: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
157
|
+
comments: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
158
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
159
|
+
export type Wellness = z.infer<typeof WellnessSchema>;
|
|
160
|
+
export declare const EventSchema: z.ZodObject<{
|
|
161
|
+
id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
162
|
+
date: z.ZodOptional<z.ZodString>;
|
|
163
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
164
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
165
|
+
race: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
166
|
+
workout: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
|
|
167
|
+
priority: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
168
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
169
|
+
id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
170
|
+
date: z.ZodOptional<z.ZodString>;
|
|
171
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
172
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
173
|
+
race: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
174
|
+
workout: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
|
|
175
|
+
priority: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
176
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
177
|
+
id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
178
|
+
date: z.ZodOptional<z.ZodString>;
|
|
179
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
180
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
181
|
+
race: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
182
|
+
workout: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
|
|
183
|
+
priority: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
184
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
185
|
+
export type Event = z.infer<typeof EventSchema>;
|
|
186
|
+
export declare const IntervalSchema: z.ZodObject<{
|
|
187
|
+
icu_intervals: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
188
|
+
icu_groups: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
189
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
190
|
+
icu_intervals: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
191
|
+
icu_groups: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
192
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
193
|
+
icu_intervals: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
194
|
+
icu_groups: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
195
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
196
|
+
export interface EventInput {
|
|
197
|
+
name?: string;
|
|
198
|
+
description?: string;
|
|
199
|
+
start_date_local?: string;
|
|
200
|
+
category?: string;
|
|
201
|
+
type?: string;
|
|
202
|
+
race?: boolean;
|
|
203
|
+
distance?: number;
|
|
204
|
+
sub_type?: string;
|
|
205
|
+
}
|
|
206
|
+
export declare const AthleteSchema: z.ZodObject<{
|
|
207
|
+
id: z.ZodOptional<z.ZodString>;
|
|
208
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
209
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
210
|
+
sex: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
211
|
+
weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
212
|
+
dob: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
213
|
+
city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
214
|
+
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
215
|
+
icu_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
216
|
+
icu_lthr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
217
|
+
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
218
|
+
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
219
|
+
icu_vo2max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
220
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
221
|
+
id: z.ZodOptional<z.ZodString>;
|
|
222
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
223
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
224
|
+
sex: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
225
|
+
weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
226
|
+
dob: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
227
|
+
city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
228
|
+
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
229
|
+
icu_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
230
|
+
icu_lthr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
231
|
+
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
232
|
+
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
233
|
+
icu_vo2max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
234
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
235
|
+
id: z.ZodOptional<z.ZodString>;
|
|
236
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
237
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
238
|
+
sex: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
239
|
+
weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
240
|
+
dob: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
241
|
+
city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
242
|
+
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
243
|
+
icu_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
244
|
+
icu_lthr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
245
|
+
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
246
|
+
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
247
|
+
icu_vo2max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
248
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
249
|
+
export type Athlete = z.infer<typeof AthleteSchema>;
|
|
250
|
+
export declare const AthleteSummarySchema: z.ZodObject<{
|
|
251
|
+
ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
252
|
+
atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
253
|
+
tsb: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
254
|
+
rampRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
255
|
+
ctlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
256
|
+
atlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
257
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
258
|
+
ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
259
|
+
atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
260
|
+
tsb: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
261
|
+
rampRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
262
|
+
ctlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
263
|
+
atlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
264
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
265
|
+
ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
266
|
+
atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
267
|
+
tsb: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
268
|
+
rampRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
269
|
+
ctlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
270
|
+
atlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
271
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
272
|
+
export type AthleteSummary = z.infer<typeof AthleteSummarySchema>;
|
|
273
|
+
export declare const ZonesSchema: z.ZodObject<{
|
|
274
|
+
power: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
275
|
+
hr: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
276
|
+
pace: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
277
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
278
|
+
power: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
279
|
+
hr: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
280
|
+
pace: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
281
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
282
|
+
power: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
283
|
+
hr: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
284
|
+
pace: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
|
|
285
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
286
|
+
export type Zones = z.infer<typeof ZonesSchema>;
|
|
287
|
+
export declare const WorkoutSchema: z.ZodObject<{
|
|
288
|
+
id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
289
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
290
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
291
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
292
|
+
created: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
293
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
294
|
+
id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
295
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
296
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
297
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
298
|
+
created: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
299
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
300
|
+
id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
301
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
302
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
303
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
304
|
+
created: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
305
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
306
|
+
export type Workout = z.infer<typeof WorkoutSchema>;
|
|
307
|
+
export interface WorkoutInput {
|
|
308
|
+
name?: string;
|
|
309
|
+
description?: string;
|
|
310
|
+
type?: string;
|
|
311
|
+
folder_id?: number;
|
|
312
|
+
workout_doc?: unknown;
|
|
313
|
+
}
|
|
314
|
+
export interface WellnessInput {
|
|
315
|
+
weight?: number;
|
|
316
|
+
restingHR?: number;
|
|
317
|
+
hrv?: number;
|
|
318
|
+
hrvSDNN?: number;
|
|
319
|
+
sleepSecs?: number;
|
|
320
|
+
sleepScore?: number;
|
|
321
|
+
sleepQuality?: number;
|
|
322
|
+
readiness?: number;
|
|
323
|
+
soreness?: number;
|
|
324
|
+
fatigue?: number;
|
|
325
|
+
stress?: number;
|
|
326
|
+
mood?: number;
|
|
327
|
+
motivation?: number;
|
|
328
|
+
steps?: number;
|
|
329
|
+
kcalConsumed?: number;
|
|
330
|
+
spO2?: number;
|
|
331
|
+
comments?: string;
|
|
332
|
+
}
|
|
333
|
+
export declare function getActivities(config: ClientConfig, params: {
|
|
334
|
+
startDate: string;
|
|
335
|
+
endDate: string;
|
|
336
|
+
limit: number;
|
|
337
|
+
}): Promise<Activity[]>;
|
|
338
|
+
export declare function getActivityDetails(config: ClientConfig, activityId: string): Promise<Activity>;
|
|
339
|
+
export declare function getActivityIntervals(config: ClientConfig, activityId: string): Promise<z.infer<typeof IntervalSchema>>;
|
|
340
|
+
export declare function getWellness(config: ClientConfig, params: {
|
|
341
|
+
startDate: string;
|
|
342
|
+
endDate: string;
|
|
343
|
+
}): Promise<Record<string, Wellness>>;
|
|
344
|
+
export declare function getEvents(config: ClientConfig, params: {
|
|
345
|
+
startDate: string;
|
|
346
|
+
endDate: string;
|
|
347
|
+
}): Promise<Event[]>;
|
|
348
|
+
export declare function getEventById(config: ClientConfig, eventId: string): Promise<Event>;
|
|
349
|
+
export declare function createEvent(config: ClientConfig, input: EventInput): Promise<Event>;
|
|
350
|
+
export declare function updateEvent(config: ClientConfig, eventId: string, input: EventInput): Promise<Event>;
|
|
351
|
+
export declare function deleteEvent(config: ClientConfig, eventId: string): Promise<void>;
|
|
352
|
+
export declare function getAthleteProfile(config: ClientConfig): Promise<Athlete>;
|
|
353
|
+
export declare function getAthleteZones(config: ClientConfig): Promise<Zones>;
|
|
354
|
+
export declare function getAthleteSummary(config: ClientConfig): Promise<AthleteSummary>;
|
|
355
|
+
export declare function getWellnessDay(config: ClientConfig, date: string): Promise<Wellness>;
|
|
356
|
+
export declare function updateWellness(config: ClientConfig, date: string, input: WellnessInput): Promise<Wellness>;
|
|
357
|
+
export declare function getActivityStreams(config: ClientConfig, activityId: string, types?: string[]): Promise<Record<string, unknown[]>>;
|
|
358
|
+
export declare function getActivityPowerCurves(config: ClientConfig, activityId: string): Promise<unknown>;
|
|
359
|
+
export declare function listWorkouts(config: ClientConfig): Promise<Workout[]>;
|
|
360
|
+
export declare function createWorkout(config: ClientConfig, input: WorkoutInput): Promise<Workout>;
|
|
361
|
+
export declare function bulkCreateEvents(config: ClientConfig, events: EventInput[]): Promise<Event[]>;
|
|
362
|
+
export declare function bulkDeleteEvents(config: ClientConfig, eventIds: (string | number)[]): Promise<void>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const BASE_URL = process.env.INTERVALS_API_BASE_URL ?? "https://intervals.icu/api/v1";
|
|
3
|
+
function getAuth(apiKey) {
|
|
4
|
+
return "Basic " + Buffer.from(`API_KEY:${apiKey}`).toString("base64");
|
|
5
|
+
}
|
|
6
|
+
async function request(path, config, params) {
|
|
7
|
+
const url = new URL(`${BASE_URL}${path}`);
|
|
8
|
+
if (params) {
|
|
9
|
+
for (const [k, v] of Object.entries(params)) {
|
|
10
|
+
url.searchParams.set(k, String(v));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const res = await fetch(url.toString(), {
|
|
14
|
+
headers: {
|
|
15
|
+
Authorization: getAuth(config.apiKey),
|
|
16
|
+
Accept: "application/json",
|
|
17
|
+
"User-Agent": "intervals-mcp/0.1.0",
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
const body = await res.text().catch(() => "");
|
|
22
|
+
throw new Error(`Intervals.icu API error ${res.status}: ${body}`);
|
|
23
|
+
}
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
// ---- Schemas ---- //
|
|
27
|
+
export const ActivitySchema = z.object({
|
|
28
|
+
id: z.string(),
|
|
29
|
+
name: z.string().nullable().optional(),
|
|
30
|
+
type: z.string().optional(),
|
|
31
|
+
startTime: z.string().optional(),
|
|
32
|
+
distance: z.number().nullable().optional(),
|
|
33
|
+
duration: z.number().nullable().optional(),
|
|
34
|
+
moving_time: z.number().nullable().optional(),
|
|
35
|
+
elevationGain: z.number().nullable().optional(),
|
|
36
|
+
avgPower: z.number().nullable().optional(),
|
|
37
|
+
icu_weighted_avg_watts: z.number().nullable().optional(),
|
|
38
|
+
trainingLoad: z.number().nullable().optional(),
|
|
39
|
+
icu_ftp: z.number().nullable().optional(),
|
|
40
|
+
icu_intensity: z.number().nullable().optional(),
|
|
41
|
+
avgHr: z.number().nullable().optional(),
|
|
42
|
+
max_heartrate: z.number().nullable().optional(),
|
|
43
|
+
average_cadence: z.number().nullable().optional(),
|
|
44
|
+
calories: z.number().nullable().optional(),
|
|
45
|
+
average_speed: z.number().nullable().optional(),
|
|
46
|
+
icu_ctl: z.number().nullable().optional(),
|
|
47
|
+
icu_atl: z.number().nullable().optional(),
|
|
48
|
+
trimp: z.number().nullable().optional(),
|
|
49
|
+
average_temp: z.number().nullable().optional(),
|
|
50
|
+
perceived_exertion: z.number().nullable().optional(),
|
|
51
|
+
feel: z.number().nullable().optional(),
|
|
52
|
+
description: z.string().nullable().optional(),
|
|
53
|
+
trainer: z.boolean().nullable().optional(),
|
|
54
|
+
}).passthrough();
|
|
55
|
+
export const WellnessSchema = z.object({
|
|
56
|
+
id: z.string().optional(),
|
|
57
|
+
ctl: z.number().nullable().optional(),
|
|
58
|
+
atl: z.number().nullable().optional(),
|
|
59
|
+
weight: z.number().nullable().optional(),
|
|
60
|
+
restingHR: z.number().nullable().optional(),
|
|
61
|
+
hrv: z.number().nullable().optional(),
|
|
62
|
+
hrvSDNN: z.number().nullable().optional(),
|
|
63
|
+
sleepSecs: z.number().nullable().optional(),
|
|
64
|
+
sleepScore: z.number().nullable().optional(),
|
|
65
|
+
sleepQuality: z.number().nullable().optional(),
|
|
66
|
+
readiness: z.number().nullable().optional(),
|
|
67
|
+
soreness: z.number().nullable().optional(),
|
|
68
|
+
fatigue: z.number().nullable().optional(),
|
|
69
|
+
stress: z.number().nullable().optional(),
|
|
70
|
+
mood: z.number().nullable().optional(),
|
|
71
|
+
motivation: z.number().nullable().optional(),
|
|
72
|
+
steps: z.number().nullable().optional(),
|
|
73
|
+
kcalConsumed: z.number().nullable().optional(),
|
|
74
|
+
vo2max: z.number().nullable().optional(),
|
|
75
|
+
bodyFat: z.number().nullable().optional(),
|
|
76
|
+
spO2: z.number().nullable().optional(),
|
|
77
|
+
comments: z.string().nullable().optional(),
|
|
78
|
+
}).passthrough();
|
|
79
|
+
export const EventSchema = z.object({
|
|
80
|
+
id: z.union([z.string(), z.number()]),
|
|
81
|
+
date: z.string().optional(),
|
|
82
|
+
name: z.string().nullable().optional(),
|
|
83
|
+
description: z.string().nullable().optional(),
|
|
84
|
+
race: z.boolean().nullable().optional(),
|
|
85
|
+
workout: z.unknown().nullable().optional(),
|
|
86
|
+
priority: z.string().nullable().optional(),
|
|
87
|
+
}).passthrough();
|
|
88
|
+
export const IntervalSchema = z.object({
|
|
89
|
+
icu_intervals: z.array(z.record(z.unknown())).optional(),
|
|
90
|
+
icu_groups: z.array(z.record(z.unknown())).optional(),
|
|
91
|
+
}).passthrough();
|
|
92
|
+
export const AthleteSchema = z.object({
|
|
93
|
+
id: z.string().optional(),
|
|
94
|
+
name: z.string().nullable().optional(),
|
|
95
|
+
email: z.string().nullable().optional(),
|
|
96
|
+
sex: z.string().nullable().optional(),
|
|
97
|
+
weight: z.number().nullable().optional(),
|
|
98
|
+
dob: z.string().nullable().optional(),
|
|
99
|
+
city: z.string().nullable().optional(),
|
|
100
|
+
country: z.string().nullable().optional(),
|
|
101
|
+
icu_ftp: z.number().nullable().optional(),
|
|
102
|
+
icu_lthr: z.number().nullable().optional(),
|
|
103
|
+
icu_resting_hr: z.number().nullable().optional(),
|
|
104
|
+
icu_weight: z.number().nullable().optional(),
|
|
105
|
+
icu_vo2max: z.number().nullable().optional(),
|
|
106
|
+
}).passthrough();
|
|
107
|
+
export const AthleteSummarySchema = z.object({
|
|
108
|
+
ctl: z.number().nullable().optional(),
|
|
109
|
+
atl: z.number().nullable().optional(),
|
|
110
|
+
tsb: z.number().nullable().optional(),
|
|
111
|
+
rampRate: z.number().nullable().optional(),
|
|
112
|
+
ctlLoad: z.number().nullable().optional(),
|
|
113
|
+
atlLoad: z.number().nullable().optional(),
|
|
114
|
+
}).passthrough();
|
|
115
|
+
export const ZonesSchema = z.object({
|
|
116
|
+
power: z.array(z.record(z.unknown())).optional(),
|
|
117
|
+
hr: z.array(z.record(z.unknown())).optional(),
|
|
118
|
+
pace: z.array(z.record(z.unknown())).optional(),
|
|
119
|
+
}).passthrough();
|
|
120
|
+
export const WorkoutSchema = z.object({
|
|
121
|
+
id: z.union([z.string(), z.number()]).optional(),
|
|
122
|
+
name: z.string().nullable().optional(),
|
|
123
|
+
description: z.string().nullable().optional(),
|
|
124
|
+
type: z.string().nullable().optional(),
|
|
125
|
+
created: z.string().nullable().optional(),
|
|
126
|
+
}).passthrough();
|
|
127
|
+
// ---- API methods ---- //
|
|
128
|
+
export async function getActivities(config, params) {
|
|
129
|
+
const data = await request(`/athlete/${config.athleteId}/activities`, config, { oldest: params.startDate, newest: params.endDate, limit: params.limit });
|
|
130
|
+
return z.array(ActivitySchema).parse(data);
|
|
131
|
+
}
|
|
132
|
+
export async function getActivityDetails(config, activityId) {
|
|
133
|
+
const data = await request(`/activity/${activityId}`, config);
|
|
134
|
+
return ActivitySchema.parse(data);
|
|
135
|
+
}
|
|
136
|
+
export async function getActivityIntervals(config, activityId) {
|
|
137
|
+
const data = await request(`/activity/${activityId}/intervals`, config);
|
|
138
|
+
return IntervalSchema.parse(data);
|
|
139
|
+
}
|
|
140
|
+
export async function getWellness(config, params) {
|
|
141
|
+
const data = await request(`/athlete/${config.athleteId}/wellness`, config, { oldest: params.startDate, newest: params.endDate });
|
|
142
|
+
if (Array.isArray(data)) {
|
|
143
|
+
const result = {};
|
|
144
|
+
for (const entry of data) {
|
|
145
|
+
const parsed = WellnessSchema.parse(entry);
|
|
146
|
+
const key = entry["date"] ?? parsed.id ?? String(Object.keys(result).length);
|
|
147
|
+
result[key] = parsed;
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
return z.record(WellnessSchema).parse(data);
|
|
152
|
+
}
|
|
153
|
+
export async function getEvents(config, params) {
|
|
154
|
+
const data = await request(`/athlete/${config.athleteId}/events`, config, { oldest: params.startDate, newest: params.endDate });
|
|
155
|
+
return z.array(EventSchema).parse(data);
|
|
156
|
+
}
|
|
157
|
+
export async function getEventById(config, eventId) {
|
|
158
|
+
const data = await request(`/athlete/${config.athleteId}/event/${eventId}`, config);
|
|
159
|
+
return EventSchema.parse(data);
|
|
160
|
+
}
|
|
161
|
+
async function mutate(method, path, config, body) {
|
|
162
|
+
const url = `${BASE_URL}${path}`;
|
|
163
|
+
const res = await fetch(url, {
|
|
164
|
+
method,
|
|
165
|
+
headers: {
|
|
166
|
+
Authorization: getAuth(config.apiKey),
|
|
167
|
+
Accept: "application/json",
|
|
168
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
169
|
+
"User-Agent": "intervals-mcp/0.1.0",
|
|
170
|
+
},
|
|
171
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
172
|
+
});
|
|
173
|
+
if (!res.ok) {
|
|
174
|
+
const text = await res.text().catch(() => "");
|
|
175
|
+
throw new Error(`Intervals.icu API error ${res.status}: ${text}`);
|
|
176
|
+
}
|
|
177
|
+
if (method === "DELETE")
|
|
178
|
+
return undefined;
|
|
179
|
+
return res.json();
|
|
180
|
+
}
|
|
181
|
+
export async function createEvent(config, input) {
|
|
182
|
+
const data = await mutate("POST", `/athlete/${config.athleteId}/events`, config, input);
|
|
183
|
+
return EventSchema.parse(data);
|
|
184
|
+
}
|
|
185
|
+
export async function updateEvent(config, eventId, input) {
|
|
186
|
+
const data = await mutate("PUT", `/athlete/${config.athleteId}/events/${eventId}`, config, input);
|
|
187
|
+
return EventSchema.parse(data);
|
|
188
|
+
}
|
|
189
|
+
export async function deleteEvent(config, eventId) {
|
|
190
|
+
await mutate("DELETE", `/athlete/${config.athleteId}/events/${eventId}`, config);
|
|
191
|
+
}
|
|
192
|
+
export async function getAthleteProfile(config) {
|
|
193
|
+
const data = await request(`/athlete/${config.athleteId}`, config);
|
|
194
|
+
return AthleteSchema.parse(data);
|
|
195
|
+
}
|
|
196
|
+
export async function getAthleteZones(config) {
|
|
197
|
+
const data = await request(`/athlete/${config.athleteId}/zones`, config);
|
|
198
|
+
return ZonesSchema.parse(data);
|
|
199
|
+
}
|
|
200
|
+
export async function getAthleteSummary(config) {
|
|
201
|
+
const data = await request(`/athlete/${config.athleteId}/summary`, config);
|
|
202
|
+
return AthleteSummarySchema.parse(data);
|
|
203
|
+
}
|
|
204
|
+
export async function getWellnessDay(config, date) {
|
|
205
|
+
const data = await request(`/athlete/${config.athleteId}/wellness/${date}`, config);
|
|
206
|
+
return WellnessSchema.parse(data);
|
|
207
|
+
}
|
|
208
|
+
export async function updateWellness(config, date, input) {
|
|
209
|
+
const data = await mutate("PUT", `/athlete/${config.athleteId}/wellness/${date}`, config, { ...input, id: date });
|
|
210
|
+
return WellnessSchema.parse(data);
|
|
211
|
+
}
|
|
212
|
+
export async function getActivityStreams(config, activityId, types) {
|
|
213
|
+
const params = {};
|
|
214
|
+
if (types?.length)
|
|
215
|
+
params["types"] = types.join(",");
|
|
216
|
+
const data = await request(`/activity/${activityId}/streams.json`, config, params);
|
|
217
|
+
return data;
|
|
218
|
+
}
|
|
219
|
+
export async function getActivityPowerCurves(config, activityId) {
|
|
220
|
+
return request(`/activity/${activityId}/power-curves`, config);
|
|
221
|
+
}
|
|
222
|
+
export async function listWorkouts(config) {
|
|
223
|
+
const data = await request(`/athlete/${config.athleteId}/workouts`, config);
|
|
224
|
+
return z.array(WorkoutSchema).parse(data);
|
|
225
|
+
}
|
|
226
|
+
export async function createWorkout(config, input) {
|
|
227
|
+
const data = await mutate("POST", `/athlete/${config.athleteId}/workouts`, config, input);
|
|
228
|
+
return WorkoutSchema.parse(data);
|
|
229
|
+
}
|
|
230
|
+
export async function bulkCreateEvents(config, events) {
|
|
231
|
+
const data = await mutate("POST", `/athlete/${config.athleteId}/events/bulk`, config, events);
|
|
232
|
+
return z.array(EventSchema).parse(data);
|
|
233
|
+
}
|
|
234
|
+
export async function bulkDeleteEvents(config, eventIds) {
|
|
235
|
+
await mutate("POST", `/athlete/${config.athleteId}/events/bulk-delete`, config, eventIds);
|
|
236
|
+
}
|