hevy-mcp-server 1.0.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/LICENSE +21 -0
- package/README.md +135 -0
- package/dist/hevy-api.d.ts +147 -0
- package/dist/hevy-api.js +165 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +71 -0
- package/dist/resources.d.ts +8 -0
- package/dist/resources.js +265 -0
- package/dist/tools.d.ts +8 -0
- package/dist/tools.js +610 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Noah Van Hart
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Hevy MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server for integrating with the Hevy workout tracking app API. This server provides tools and resources for managing workouts, routines, and exercise data through the MCP protocol.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Workout Management**: Create, read, and analyze workouts
|
|
8
|
+
- **Routine Management**: Manage workout templates and routines
|
|
9
|
+
- **Exercise Templates**: Access and search exercise databases
|
|
10
|
+
- **Progress Tracking**: Analyze workout trends and progression
|
|
11
|
+
- **Data Resources**: Access workout and fitness data as MCP resources
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Global Installation (Recommended)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g hevy-mcp-server
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Local Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install hevy-mcp-server
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
1. **Set your Hevy API key**
|
|
30
|
+
```bash
|
|
31
|
+
export HEVY_API_KEY=your_hevy_api_key_here
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Run the server**
|
|
35
|
+
```bash
|
|
36
|
+
hevy-mcp-server
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## MCP Configuration
|
|
40
|
+
|
|
41
|
+
### Claude Desktop App
|
|
42
|
+
|
|
43
|
+
Add this to your Claude Desktop configuration file:
|
|
44
|
+
|
|
45
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
46
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"hevy": {
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["hevy-mcp-server"],
|
|
54
|
+
"env": {
|
|
55
|
+
"HEVY_API_KEY": "your_hevy_api_key_here"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Other MCP Clients
|
|
63
|
+
|
|
64
|
+
For other MCP clients, use:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
HEVY_API_KEY=your_key hevy-mcp-server
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## MCP Tools Available
|
|
71
|
+
|
|
72
|
+
### Workout Tools
|
|
73
|
+
- `get_workouts` - Get paginated list of workouts
|
|
74
|
+
- `get_workout` - Get specific workout details
|
|
75
|
+
- `get_all_workouts` - Get all workouts automatically
|
|
76
|
+
- `create_workout` - Create a new workout
|
|
77
|
+
- `get_workout_count` - Get total workout count
|
|
78
|
+
- `analyze_workout_trends` - Analyze workout patterns and progress
|
|
79
|
+
|
|
80
|
+
### Routine Tools
|
|
81
|
+
- `get_routines` - Get paginated list of routines
|
|
82
|
+
- `get_routine` - Get specific routine details
|
|
83
|
+
- `get_all_routines` - Get all routines automatically
|
|
84
|
+
- `create_routine` - Create a new routine
|
|
85
|
+
- `get_routine_folders` - Get routine organization folders
|
|
86
|
+
- `create_routine_folder` - Create new routine folder
|
|
87
|
+
|
|
88
|
+
### Exercise Tools
|
|
89
|
+
- `get_exercise_templates` - Get all available exercises
|
|
90
|
+
- `search_exercise_templates` - Search exercises by name/muscle group
|
|
91
|
+
- `get_exercise_history` - Get exercise performance history
|
|
92
|
+
|
|
93
|
+
## MCP Resources Available
|
|
94
|
+
|
|
95
|
+
- `hevy://workouts` - Complete workout data with statistics
|
|
96
|
+
- `hevy://routines` - All workout routines and templates
|
|
97
|
+
- `hevy://exercise-templates` - Exercise database with muscle groups
|
|
98
|
+
- `hevy://routine-folders` - Routine organization folders
|
|
99
|
+
- `hevy://account-summary` - High-level account statistics
|
|
100
|
+
- `hevy://recent-workouts` - Last 10 workouts with key metrics
|
|
101
|
+
- `hevy://exercise-list` - Simple exercise reference list
|
|
102
|
+
|
|
103
|
+
## API Documentation
|
|
104
|
+
|
|
105
|
+
This server interfaces with the Hevy API v1. You'll need a valid Hevy API key to use this server.
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
### Setup
|
|
110
|
+
```bash
|
|
111
|
+
npm install
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Build
|
|
115
|
+
```bash
|
|
116
|
+
npm run build
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Test
|
|
120
|
+
```bash
|
|
121
|
+
npm test
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Development Mode
|
|
125
|
+
```bash
|
|
126
|
+
npm run dev
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
132
|
+
|
|
133
|
+
## Repository
|
|
134
|
+
|
|
135
|
+
https://github.com/noah-vh/hevy-mcp-server
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hevy API Client for interacting with the Hevy workout tracking API
|
|
3
|
+
* Supports workouts, routines, exercise templates, and more
|
|
4
|
+
*/
|
|
5
|
+
export interface HevySet {
|
|
6
|
+
index: number;
|
|
7
|
+
type: 'warmup' | 'normal' | 'failure' | 'dropset';
|
|
8
|
+
weight_kg: number | null;
|
|
9
|
+
reps: number | null;
|
|
10
|
+
distance_meters: number | null;
|
|
11
|
+
duration_seconds: number | null;
|
|
12
|
+
rpe: number | null;
|
|
13
|
+
custom_metric: number | null;
|
|
14
|
+
}
|
|
15
|
+
export interface HevyRoutineSet extends HevySet {
|
|
16
|
+
rep_range?: {
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface HevyExercise {
|
|
22
|
+
index: number;
|
|
23
|
+
title: string;
|
|
24
|
+
notes: string;
|
|
25
|
+
exercise_template_id: string;
|
|
26
|
+
superset_id: number | null;
|
|
27
|
+
sets: HevySet[];
|
|
28
|
+
}
|
|
29
|
+
export interface HevyRoutineExercise {
|
|
30
|
+
index: number;
|
|
31
|
+
title: string;
|
|
32
|
+
notes: string;
|
|
33
|
+
exercise_template_id: string;
|
|
34
|
+
superset_id: number | null;
|
|
35
|
+
rest_seconds: number | null;
|
|
36
|
+
sets: HevyRoutineSet[];
|
|
37
|
+
}
|
|
38
|
+
export interface HevyWorkout {
|
|
39
|
+
id: string;
|
|
40
|
+
title: string;
|
|
41
|
+
routine_id?: string;
|
|
42
|
+
description: string;
|
|
43
|
+
start_time: string;
|
|
44
|
+
end_time: string | null;
|
|
45
|
+
updated_at: string;
|
|
46
|
+
created_at: string;
|
|
47
|
+
exercises: HevyExercise[];
|
|
48
|
+
}
|
|
49
|
+
export interface HevyRoutine {
|
|
50
|
+
id: string;
|
|
51
|
+
title: string;
|
|
52
|
+
folder_id: number | null;
|
|
53
|
+
updated_at: string;
|
|
54
|
+
created_at: string;
|
|
55
|
+
exercises: HevyRoutineExercise[];
|
|
56
|
+
}
|
|
57
|
+
export interface HevyRoutineFolder {
|
|
58
|
+
id: number;
|
|
59
|
+
index: number;
|
|
60
|
+
title: string;
|
|
61
|
+
updated_at: string;
|
|
62
|
+
created_at: string;
|
|
63
|
+
}
|
|
64
|
+
export interface HevyExerciseTemplate {
|
|
65
|
+
id: string;
|
|
66
|
+
title: string;
|
|
67
|
+
type: string;
|
|
68
|
+
primary_muscle_group: string;
|
|
69
|
+
secondary_muscle_groups: string[];
|
|
70
|
+
is_custom: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface HevyExerciseHistoryEntry {
|
|
73
|
+
workout_id: string;
|
|
74
|
+
workout_title: string;
|
|
75
|
+
workout_start_time: string;
|
|
76
|
+
workout_end_time: string | null;
|
|
77
|
+
exercise_template_id: string;
|
|
78
|
+
weight_kg: number | null;
|
|
79
|
+
reps: number | null;
|
|
80
|
+
distance_meters: number | null;
|
|
81
|
+
duration_seconds: number | null;
|
|
82
|
+
rpe: number | null;
|
|
83
|
+
custom_metric: number | null;
|
|
84
|
+
set_type: string;
|
|
85
|
+
}
|
|
86
|
+
export interface HevyAPIResponse<T> {
|
|
87
|
+
page?: number;
|
|
88
|
+
page_count?: number;
|
|
89
|
+
[key: string]: T | any;
|
|
90
|
+
}
|
|
91
|
+
export declare class HevyAPIClient {
|
|
92
|
+
private apiKey;
|
|
93
|
+
private baseUrl;
|
|
94
|
+
constructor(apiKey: string, baseUrl?: string);
|
|
95
|
+
private makeRequest;
|
|
96
|
+
getWorkouts(page?: number, pageSize?: number): Promise<HevyAPIResponse<HevyWorkout[]>>;
|
|
97
|
+
getWorkout(workoutId: string): Promise<{
|
|
98
|
+
workout: HevyWorkout;
|
|
99
|
+
}>;
|
|
100
|
+
createWorkout(workout: Partial<HevyWorkout>): Promise<{
|
|
101
|
+
workout: HevyWorkout;
|
|
102
|
+
}>;
|
|
103
|
+
updateWorkout(workoutId: string, workout: Partial<HevyWorkout>): Promise<{
|
|
104
|
+
workout: HevyWorkout;
|
|
105
|
+
}>;
|
|
106
|
+
getWorkoutCount(): Promise<{
|
|
107
|
+
workout_count: number;
|
|
108
|
+
}>;
|
|
109
|
+
getWorkoutEvents(page?: number, pageSize?: number, since?: string): Promise<HevyAPIResponse<any[]>>;
|
|
110
|
+
getRoutines(page?: number, pageSize?: number): Promise<HevyAPIResponse<HevyRoutine[]>>;
|
|
111
|
+
getRoutine(routineId: string): Promise<{
|
|
112
|
+
routine: HevyRoutine;
|
|
113
|
+
}>;
|
|
114
|
+
createRoutine(routine: Partial<HevyRoutine>): Promise<{
|
|
115
|
+
routine: HevyRoutine;
|
|
116
|
+
}>;
|
|
117
|
+
updateRoutine(routineId: string, routine: Partial<HevyRoutine>): Promise<{
|
|
118
|
+
routine: HevyRoutine;
|
|
119
|
+
}>;
|
|
120
|
+
getRoutineFolders(page?: number, pageSize?: number): Promise<HevyAPIResponse<HevyRoutineFolder[]>>;
|
|
121
|
+
getRoutineFolder(folderId: string): Promise<{
|
|
122
|
+
routine_folder: HevyRoutineFolder;
|
|
123
|
+
}>;
|
|
124
|
+
createRoutineFolder(folderTitle: string): Promise<{
|
|
125
|
+
routine_folder: HevyRoutineFolder;
|
|
126
|
+
}>;
|
|
127
|
+
getExerciseTemplates(page?: number, pageSize?: number): Promise<HevyAPIResponse<HevyExerciseTemplate[]>>;
|
|
128
|
+
getExerciseTemplate(templateId: string): Promise<{
|
|
129
|
+
exercise_template: HevyExerciseTemplate;
|
|
130
|
+
}>;
|
|
131
|
+
createExerciseTemplate(exercise: {
|
|
132
|
+
title: string;
|
|
133
|
+
exercise_type: string;
|
|
134
|
+
equipment_category: string;
|
|
135
|
+
muscle_group: string;
|
|
136
|
+
other_muscles?: string[];
|
|
137
|
+
}): Promise<{
|
|
138
|
+
id: string;
|
|
139
|
+
}>;
|
|
140
|
+
getExerciseHistory(templateId: string, startDate?: string, endDate?: string): Promise<{
|
|
141
|
+
exercise_history: HevyExerciseHistoryEntry[];
|
|
142
|
+
}>;
|
|
143
|
+
getAllWorkouts(): Promise<HevyWorkout[]>;
|
|
144
|
+
getAllRoutines(): Promise<HevyRoutine[]>;
|
|
145
|
+
getAllExerciseTemplates(): Promise<HevyExerciseTemplate[]>;
|
|
146
|
+
getAllRoutineFolders(): Promise<HevyRoutineFolder[]>;
|
|
147
|
+
}
|
package/dist/hevy-api.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hevy API Client for interacting with the Hevy workout tracking API
|
|
3
|
+
* Supports workouts, routines, exercise templates, and more
|
|
4
|
+
*/
|
|
5
|
+
const HEVY_API_BASE = 'https://api.hevyapp.com/v1';
|
|
6
|
+
export class HevyAPIClient {
|
|
7
|
+
apiKey;
|
|
8
|
+
baseUrl;
|
|
9
|
+
constructor(apiKey, baseUrl = HEVY_API_BASE) {
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.baseUrl = baseUrl;
|
|
12
|
+
}
|
|
13
|
+
async makeRequest(endpoint, options = {}) {
|
|
14
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
15
|
+
const response = await fetch(url, {
|
|
16
|
+
...options,
|
|
17
|
+
headers: {
|
|
18
|
+
'api-key': this.apiKey,
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
...options.headers,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
if (response.status === 429) {
|
|
24
|
+
throw new Error('Rate limit exceeded. Please try again later.');
|
|
25
|
+
}
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const errorText = await response.text();
|
|
28
|
+
throw new Error(`Hevy API error (${response.status}): ${errorText}`);
|
|
29
|
+
}
|
|
30
|
+
return response.json();
|
|
31
|
+
}
|
|
32
|
+
// Workouts
|
|
33
|
+
async getWorkouts(page = 1, pageSize = 10) {
|
|
34
|
+
return this.makeRequest(`/workouts?page=${page}&pageSize=${pageSize}`);
|
|
35
|
+
}
|
|
36
|
+
async getWorkout(workoutId) {
|
|
37
|
+
return this.makeRequest(`/workouts/${workoutId}`);
|
|
38
|
+
}
|
|
39
|
+
async createWorkout(workout) {
|
|
40
|
+
return this.makeRequest('/workouts', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
body: JSON.stringify({ workout }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async updateWorkout(workoutId, workout) {
|
|
46
|
+
return this.makeRequest(`/workouts/${workoutId}`, {
|
|
47
|
+
method: 'PUT',
|
|
48
|
+
body: JSON.stringify({ workout }),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async getWorkoutCount() {
|
|
52
|
+
return this.makeRequest('/workouts/count');
|
|
53
|
+
}
|
|
54
|
+
async getWorkoutEvents(page = 1, pageSize = 10, since = '1970-01-01T00:00:00Z') {
|
|
55
|
+
return this.makeRequest(`/workouts/events?page=${page}&pageSize=${pageSize}&since=${encodeURIComponent(since)}`);
|
|
56
|
+
}
|
|
57
|
+
// Routines
|
|
58
|
+
async getRoutines(page = 1, pageSize = 10) {
|
|
59
|
+
return this.makeRequest(`/routines?page=${page}&pageSize=${pageSize}`);
|
|
60
|
+
}
|
|
61
|
+
async getRoutine(routineId) {
|
|
62
|
+
return this.makeRequest(`/routines/${routineId}`);
|
|
63
|
+
}
|
|
64
|
+
async createRoutine(routine) {
|
|
65
|
+
return this.makeRequest('/routines', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
body: JSON.stringify({ routine }),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async updateRoutine(routineId, routine) {
|
|
71
|
+
return this.makeRequest(`/routines/${routineId}`, {
|
|
72
|
+
method: 'PUT',
|
|
73
|
+
body: JSON.stringify({ routine }),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// Routine Folders
|
|
77
|
+
async getRoutineFolders(page = 1, pageSize = 10) {
|
|
78
|
+
return this.makeRequest(`/routine_folders?page=${page}&pageSize=${pageSize}`);
|
|
79
|
+
}
|
|
80
|
+
async getRoutineFolder(folderId) {
|
|
81
|
+
return this.makeRequest(`/routine_folders/${folderId}`);
|
|
82
|
+
}
|
|
83
|
+
async createRoutineFolder(folderTitle) {
|
|
84
|
+
return this.makeRequest('/routine_folders', {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
body: JSON.stringify({ routine_folder: { title: folderTitle } }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Exercise Templates
|
|
90
|
+
async getExerciseTemplates(page = 1, pageSize = 100) {
|
|
91
|
+
return this.makeRequest(`/exercise_templates?page=${page}&pageSize=${pageSize}`);
|
|
92
|
+
}
|
|
93
|
+
async getExerciseTemplate(templateId) {
|
|
94
|
+
return this.makeRequest(`/exercise_templates/${templateId}`);
|
|
95
|
+
}
|
|
96
|
+
async createExerciseTemplate(exercise) {
|
|
97
|
+
return this.makeRequest('/exercise_templates', {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
body: JSON.stringify({ exercise }),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// Exercise History
|
|
103
|
+
async getExerciseHistory(templateId, startDate, endDate) {
|
|
104
|
+
let url = `/exercise_history/${templateId}`;
|
|
105
|
+
const params = new URLSearchParams();
|
|
106
|
+
if (startDate)
|
|
107
|
+
params.append('start_date', startDate);
|
|
108
|
+
if (endDate)
|
|
109
|
+
params.append('end_date', endDate);
|
|
110
|
+
if (params.toString()) {
|
|
111
|
+
url += `?${params.toString()}`;
|
|
112
|
+
}
|
|
113
|
+
return this.makeRequest(url);
|
|
114
|
+
}
|
|
115
|
+
// Utility methods
|
|
116
|
+
async getAllWorkouts() {
|
|
117
|
+
const firstPage = await this.getWorkouts(1, 10);
|
|
118
|
+
const allWorkouts = [...(firstPage.workouts || [])];
|
|
119
|
+
if (firstPage.page_count && firstPage.page_count > 1) {
|
|
120
|
+
for (let page = 2; page <= firstPage.page_count; page++) {
|
|
121
|
+
const pageData = await this.getWorkouts(page, 10);
|
|
122
|
+
allWorkouts.push(...(pageData.workouts || []));
|
|
123
|
+
// Add delay to respect rate limits
|
|
124
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return allWorkouts;
|
|
128
|
+
}
|
|
129
|
+
async getAllRoutines() {
|
|
130
|
+
const firstPage = await this.getRoutines(1, 10);
|
|
131
|
+
const allRoutines = [...(firstPage.routines || [])];
|
|
132
|
+
if (firstPage.page_count && firstPage.page_count > 1) {
|
|
133
|
+
for (let page = 2; page <= firstPage.page_count; page++) {
|
|
134
|
+
const pageData = await this.getRoutines(page, 10);
|
|
135
|
+
allRoutines.push(...(pageData.routines || []));
|
|
136
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return allRoutines;
|
|
140
|
+
}
|
|
141
|
+
async getAllExerciseTemplates() {
|
|
142
|
+
const firstPage = await this.getExerciseTemplates(1, 100);
|
|
143
|
+
const allTemplates = [...(firstPage.exercise_templates || [])];
|
|
144
|
+
if (firstPage.page_count && firstPage.page_count > 1) {
|
|
145
|
+
for (let page = 2; page <= firstPage.page_count; page++) {
|
|
146
|
+
const pageData = await this.getExerciseTemplates(page, 100);
|
|
147
|
+
allTemplates.push(...(pageData.exercise_templates || []));
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return allTemplates;
|
|
152
|
+
}
|
|
153
|
+
async getAllRoutineFolders() {
|
|
154
|
+
const firstPage = await this.getRoutineFolders(1, 10);
|
|
155
|
+
const allFolders = [...(firstPage.routine_folders || [])];
|
|
156
|
+
if (firstPage.page_count && firstPage.page_count > 1) {
|
|
157
|
+
for (let page = 2; page <= firstPage.page_count; page++) {
|
|
158
|
+
const pageData = await this.getRoutineFolders(page, 10);
|
|
159
|
+
allFolders.push(...(pageData.routine_folders || []));
|
|
160
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return allFolders;
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { HevyAPIClient } from './hevy-api.js';
|
|
6
|
+
import { tools, toolDefinitions } from './tools.js';
|
|
7
|
+
import { resources, resourceDefinitions } from './resources.js';
|
|
8
|
+
const API_KEY = process.env.HEVY_API_KEY;
|
|
9
|
+
if (!API_KEY) {
|
|
10
|
+
console.error('Error: HEVY_API_KEY environment variable is required');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const hevyClient = new HevyAPIClient(API_KEY);
|
|
14
|
+
const server = new Server({
|
|
15
|
+
name: 'hevy-mcp-server',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
}, {
|
|
18
|
+
capabilities: {
|
|
19
|
+
tools: {},
|
|
20
|
+
resources: {},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
// List available tools
|
|
24
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
25
|
+
return {
|
|
26
|
+
tools: toolDefinitions,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
// Handle tool calls
|
|
30
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
31
|
+
const { name, arguments: args } = request.params;
|
|
32
|
+
if (!tools[name]) {
|
|
33
|
+
throw new McpError(ErrorCode.MethodNotFound, `Tool ${name} not found`);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return await tools[name](hevyClient, args);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error(`Error executing tool ${name}:`, error);
|
|
40
|
+
throw new McpError(ErrorCode.InternalError, `Error executing tool: ${error instanceof Error ? error.message : String(error)}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// List available resources
|
|
44
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
45
|
+
return {
|
|
46
|
+
resources: resourceDefinitions,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
// Handle resource reads
|
|
50
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
51
|
+
const { uri } = request.params;
|
|
52
|
+
if (!resources[uri]) {
|
|
53
|
+
throw new McpError(ErrorCode.InvalidRequest, `Resource ${uri} not found`);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return await resources[uri](hevyClient);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error(`Error reading resource ${uri}:`, error);
|
|
60
|
+
throw new McpError(ErrorCode.InternalError, `Error reading resource: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
async function main() {
|
|
64
|
+
const transport = new StdioServerTransport();
|
|
65
|
+
await server.connect(transport);
|
|
66
|
+
console.error('Hevy MCP server running on stdio');
|
|
67
|
+
}
|
|
68
|
+
main().catch((error) => {
|
|
69
|
+
console.error('Server error:', error);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Resources for Hevy API data
|
|
3
|
+
* Provides read-only access to workout and fitness data as resources
|
|
4
|
+
*/
|
|
5
|
+
import { Resource } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { HevyAPIClient } from './hevy-api.js';
|
|
7
|
+
export declare const resourceDefinitions: Resource[];
|
|
8
|
+
export declare const resources: Record<string, (client: HevyAPIClient) => Promise<any>>;
|